walletcontroller.cpp raw

   1  // Copyright (c) 2019-present 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  
   7  #include <qt/askpassphrasedialog.h>
   8  #include <qt/clientmodel.h>
   9  #include <qt/createwalletdialog.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;
  34  using wallet::WALLET_FLAG_BLANK_WALLET;
  35  using wallet::WALLET_FLAG_DESCRIPTORS;
  36  using wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS;
  37  using wallet::WALLET_FLAG_EXTERNAL_SIGNER;
  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.
  61  WalletController::~WalletController()
  62  {
  63      m_activity_thread->quit();
  64      m_activity_thread->wait();
  65      delete m_activity_worker;
  66  }
  67  
  68  std::map<std::string, std::pair<bool, std::string>> WalletController::listWalletDir() const
  69  {
  70      QMutexLocker locker(&m_mutex);
  71      std::map<std::string, std::pair<bool, std::string>> wallets;
  72      for (const auto& [name, format] : m_node.walletLoader().listWalletDir()) {
  73          wallets[name] = std::make_pair(false, format);
  74      }
  75      for (WalletModel* wallet_model : m_wallets) {
  76          auto it = wallets.find(wallet_model->wallet().getWalletName());
  77          if (it != wallets.end()) it->second.first = true;
  78      }
  79      return wallets;
  80  }
  81  
  82  void WalletController::removeWallet(WalletModel* wallet_model)
  83  {
  84      // Once the wallet is successfully removed from the node, the model will emit the 'WalletModel::unload' signal.
  85      // This signal is already connected and will complete the removal of the view from the GUI.
  86      // Look at 'WalletController::getOrCreateWallet' for the signal connection.
  87      wallet_model->wallet().remove();
  88  }
  89  
  90  void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent)
  91  {
  92      QMessageBox box(parent);
  93      box.setWindowTitle(tr("Close wallet"));
  94      box.setText(tr("Are you sure you wish to close the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName())));
  95      box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled."));
  96      box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
  97      box.setDefaultButton(QMessageBox::Yes);
  98      if (box.exec() != QMessageBox::Yes) return;
  99  
 100      removeWallet(wallet_model);
 101  }
 102  
 103  void WalletController::closeAllWallets(QWidget* parent)
 104  {
 105      QMessageBox::StandardButton button = QMessageBox::question(parent, tr("Close all wallets"),
 106          tr("Are you sure you wish to close all wallets?"),
 107          QMessageBox::Yes|QMessageBox::Cancel,
 108          QMessageBox::Yes);
 109      if (button != QMessageBox::Yes) return;
 110  
 111      QMutexLocker locker(&m_mutex);
 112      for (WalletModel* wallet_model : m_wallets) {
 113          removeWallet(wallet_model);
 114      }
 115  }
 116  
 117  WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet)
 118  {
 119      QMutexLocker locker(&m_mutex);
 120  
 121      // Return model instance if exists.
 122      if (!m_wallets.empty()) {
 123          std::string name = wallet->getWalletName();
 124          for (WalletModel* wallet_model : m_wallets) {
 125              if (wallet_model->wallet().getWalletName() == name) {
 126                  return wallet_model;
 127              }
 128          }
 129      }
 130  
 131      // Instantiate model and register it.
 132      WalletModel* wallet_model = new WalletModel(std::move(wallet), m_client_model, m_platform_style,
 133                                                  nullptr /* required for the following moveToThread() call */);
 134  
 135      // Move WalletModel object to the thread that created the WalletController
 136      // object (GUI main thread), instead of the current thread, which could be
 137      // an outside wallet thread or RPC thread sending a LoadWallet notification.
 138      // This ensures queued signals sent to the WalletModel object will be
 139      // handled on the GUI event loop.
 140      wallet_model->moveToThread(thread());
 141      // setParent(parent) must be called in the thread which created the parent object. More details in #18948.
 142      QMetaObject::invokeMethod(this, [wallet_model, this] {
 143          wallet_model->setParent(this);
 144      }, GUIUtil::blockingGUIThreadConnection());
 145  
 146      m_wallets.push_back(wallet_model);
 147  
 148      // WalletModel::startPollBalance needs to be called in a thread managed by
 149      // Qt because of startTimer. Considering the current thread can be a RPC
 150      // thread, better delegate the calling to Qt with Qt::AutoConnection.
 151      const bool called = QMetaObject::invokeMethod(wallet_model, "startPollBalance");
 152      assert(called);
 153  
 154      connect(wallet_model, &WalletModel::unload, this, [this, wallet_model] {
 155          // Defer removeAndDeleteWallet when no modal widget is actively waiting for an action.
 156          // TODO: remove this workaround by removing usage of QDialog::exec.
 157          QWidget* active_dialog = QApplication::activeModalWidget();
 158          if (active_dialog && dynamic_cast<QProgressDialog*>(active_dialog) == nullptr) {
 159              connect(qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() {
 160                  if (!QApplication::activeModalWidget()) {
 161                      removeAndDeleteWallet(wallet_model);
 162                  }
 163              }, Qt::QueuedConnection);
 164          } else {
 165              removeAndDeleteWallet(wallet_model);
 166          }
 167      }, Qt::QueuedConnection);
 168  
 169      // Re-emit coinsSent signal from wallet model.
 170      connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);
 171  
 172      Q_EMIT walletAdded(wallet_model);
 173  
 174      return wallet_model;
 175  }
 176  
 177  void WalletController::removeAndDeleteWallet(WalletModel* wallet_model)
 178  {
 179      // Unregister wallet model.
 180      {
 181          QMutexLocker locker(&m_mutex);
 182          m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model));
 183      }
 184      Q_EMIT walletRemoved(wallet_model);
 185      // Currently this can trigger the unload since the model can hold the last
 186      // CWallet shared pointer.
 187      delete wallet_model;
 188  }
 189  
 190  WalletControllerActivity::WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget)
 191      : QObject(wallet_controller)
 192      , m_wallet_controller(wallet_controller)
 193      , m_parent_widget(parent_widget)
 194  {
 195      connect(this, &WalletControllerActivity::finished, this, &QObject::deleteLater);
 196  }
 197  
 198  void WalletControllerActivity::showProgressDialog(const QString& title_text, const QString& label_text, bool show_minimized)
 199  {
 200      auto progress_dialog = new QProgressDialog(m_parent_widget);
 201      progress_dialog->setAttribute(Qt::WA_DeleteOnClose);
 202      connect(this, &WalletControllerActivity::finished, progress_dialog, &QWidget::close);
 203  
 204      progress_dialog->setWindowTitle(title_text);
 205      progress_dialog->setLabelText(label_text);
 206      progress_dialog->setRange(0, 0);
 207      progress_dialog->setCancelButton(nullptr);
 208      progress_dialog->setWindowModality(Qt::ApplicationModal);
 209      GUIUtil::PolishProgressDialog(progress_dialog);
 210      // The setValue call forces QProgressDialog to start the internal duration estimation.
 211      // See details in https://bugreports.qt.io/browse/QTBUG-47042.
 212      progress_dialog->setValue(0);
 213      // When requested, launch dialog minimized
 214      if (show_minimized) progress_dialog->showMinimized();
 215  }
 216  
 217  CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
 218      : WalletControllerActivity(wallet_controller, parent_widget)
 219  {
 220      m_passphrase.reserve(MAX_PASSPHRASE_SIZE);
 221  }
 222  
 223  CreateWalletActivity::~CreateWalletActivity()
 224  {
 225      delete m_create_wallet_dialog;
 226      delete m_passphrase_dialog;
 227  }
 228  
 229  void CreateWalletActivity::askPassphrase()
 230  {
 231      m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase);
 232      m_passphrase_dialog->setWindowModality(Qt::ApplicationModal);
 233      m_passphrase_dialog->show();
 234  
 235      connect(m_passphrase_dialog, &QObject::destroyed, [this] {
 236          m_passphrase_dialog = nullptr;
 237      });
 238      connect(m_passphrase_dialog, &QDialog::accepted, [this] {
 239          createWallet();
 240      });
 241      connect(m_passphrase_dialog, &QDialog::rejected, [this] {
 242          Q_EMIT finished();
 243      });
 244  }
 245  
 246  void CreateWalletActivity::createWallet()
 247  {
 248      showProgressDialog(
 249          //: Title of window indicating the progress of creation of a new wallet.
 250          tr("Create Wallet"),
 251          /*: Descriptive text of the create wallet progress window which indicates
 252              to the user which wallet is currently being created. */
 253          tr("Creating Wallet <b>%1</b>…").arg(m_create_wallet_dialog->walletName().toHtmlEscaped()));
 254  
 255      std::string name = m_create_wallet_dialog->walletName().toStdString();
 256      uint64_t flags = 0;
 257      // Enable descriptors by default.
 258      flags |= WALLET_FLAG_DESCRIPTORS;
 259      if (m_create_wallet_dialog->isDisablePrivateKeysChecked()) {
 260          flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
 261      }
 262      if (m_create_wallet_dialog->isMakeBlankWalletChecked()) {
 263          flags |= WALLET_FLAG_BLANK_WALLET;
 264      }
 265      if (m_create_wallet_dialog->isExternalSignerChecked()) {
 266          flags |= WALLET_FLAG_EXTERNAL_SIGNER;
 267      }
 268  
 269      QTimer::singleShot(500ms, worker(), [this, name, flags] {
 270          auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
 271  
 272          if (wallet) {
 273              m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
 274          } else {
 275              m_error_message = util::ErrorString(wallet);
 276          }
 277  
 278          QTimer::singleShot(500ms, this, &CreateWalletActivity::finish);
 279      });
 280  }
 281  
 282  void CreateWalletActivity::finish()
 283  {
 284      if (!m_error_message.empty()) {
 285          QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message.translated));
 286      } else if (!m_warning_message.empty()) {
 287          QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
 288      }
 289  
 290      if (m_wallet_model) Q_EMIT created(m_wallet_model);
 291  
 292      Q_EMIT finished();
 293  }
 294  
 295  void CreateWalletActivity::create()
 296  {
 297      m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget);
 298  
 299      std::vector<std::unique_ptr<interfaces::ExternalSigner>> signers;
 300      try {
 301          signers = node().listExternalSigners();
 302      } catch (const std::runtime_error& e) {
 303          QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
 304      }
 305      if (signers.size() > 1) {
 306          QMessageBox::critical(nullptr, tr("Too many external signers found"), QString::fromStdString("More than one external signer found. Please connect only one at a time."));
 307          signers.clear();
 308      }
 309      m_create_wallet_dialog->setSigners(signers);
 310  
 311      m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
 312      m_create_wallet_dialog->show();
 313  
 314      connect(m_create_wallet_dialog, &QObject::destroyed, [this] {
 315          m_create_wallet_dialog = nullptr;
 316      });
 317      connect(m_create_wallet_dialog, &QDialog::rejected, [this] {
 318          Q_EMIT finished();
 319      });
 320      connect(m_create_wallet_dialog, &QDialog::accepted, [this] {
 321          if (m_create_wallet_dialog->isEncryptWalletChecked()) {
 322              askPassphrase();
 323          } else {
 324              createWallet();
 325          }
 326      });
 327  }
 328  
 329  OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
 330      : WalletControllerActivity(wallet_controller, parent_widget)
 331  {
 332  }
 333  
 334  void OpenWalletActivity::finish()
 335  {
 336      if (!m_error_message.empty()) {
 337          QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message.translated));
 338      } else if (!m_warning_message.empty()) {
 339          QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
 340      }
 341  
 342      if (m_wallet_model) Q_EMIT opened(m_wallet_model);
 343  
 344      Q_EMIT finished();
 345  }
 346  
 347  void OpenWalletActivity::open(const std::string& path)
 348  {
 349      QString name = GUIUtil::WalletDisplayName(path);
 350  
 351      showProgressDialog(
 352          //: Title of window indicating the progress of opening of a wallet.
 353          tr("Open Wallet"),
 354          /*: Descriptive text of the open wallet progress window which indicates
 355              to the user which wallet is currently being opened. */
 356          tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
 357  
 358      QTimer::singleShot(0, worker(), [this, path] {
 359          auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
 360  
 361          if (wallet) {
 362              m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
 363          } else {
 364              m_error_message = util::ErrorString(wallet);
 365          }
 366  
 367          QTimer::singleShot(0, this, &OpenWalletActivity::finish);
 368      });
 369  }
 370  
 371  LoadWalletsActivity::LoadWalletsActivity(WalletController* wallet_controller, QWidget* parent_widget)
 372      : WalletControllerActivity(wallet_controller, parent_widget)
 373  {
 374  }
 375  
 376  void LoadWalletsActivity::load(bool show_loading_minimized)
 377  {
 378      showProgressDialog(
 379          //: Title of progress window which is displayed when wallets are being loaded.
 380          tr("Load Wallets"),
 381          /*: Descriptive text of the load wallets progress window which indicates to
 382              the user that wallets are currently being loaded.*/
 383          tr("Loading wallets…"),
 384          /*show_minimized=*/show_loading_minimized);
 385  
 386      QTimer::singleShot(0, worker(), [this] {
 387          for (auto& wallet : node().walletLoader().getWallets()) {
 388              m_wallet_controller->getOrCreateWallet(std::move(wallet));
 389          }
 390  
 391          QTimer::singleShot(0, this, [this] { Q_EMIT finished(); });
 392      });
 393  }
 394  
 395  RestoreWalletActivity::RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
 396      : WalletControllerActivity(wallet_controller, parent_widget)
 397  {
 398  }
 399  
 400  void RestoreWalletActivity::restore(const fs::path& backup_file, const std::string& wallet_name)
 401  {
 402      QString name = QString::fromStdString(wallet_name);
 403  
 404      showProgressDialog(
 405          //: Title of progress window which is displayed when wallets are being restored.
 406          tr("Restore Wallet"),
 407          /*: Descriptive text of the restore wallets progress window which indicates to
 408              the user that wallets are currently being restored.*/
 409          tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
 410  
 411      QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
 412          auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message, /*load_after_restore=*/true)};
 413  
 414          if (wallet) {
 415              m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
 416          } else {
 417              m_error_message = util::ErrorString(wallet);
 418          }
 419  
 420          QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
 421      });
 422  }
 423  
 424  void RestoreWalletActivity::finish()
 425  {
 426      if (!m_error_message.empty()) {
 427          //: Title of message box which is displayed when the wallet could not be restored.
 428          QMessageBox::critical(m_parent_widget, tr("Restore wallet failed"), QString::fromStdString(m_error_message.translated));
 429      } else if (!m_warning_message.empty()) {
 430          //: Title of message box which is displayed when the wallet is restored with some warning.
 431          QMessageBox::warning(m_parent_widget, tr("Restore wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
 432      } else {
 433          //: Title of message box which is displayed when the wallet is successfully restored.
 434          QMessageBox::information(m_parent_widget, tr("Restore wallet message"), QString::fromStdString(Untranslated("Wallet restored successfully \n").translated));
 435      }
 436  
 437      if (m_wallet_model) Q_EMIT restored(m_wallet_model);
 438  
 439      Q_EMIT finished();
 440  }
 441  
 442  void MigrateWalletActivity::do_migrate(const std::string& name)
 443  {
 444      SecureString passphrase;
 445      if (node().walletLoader().isEncrypted(name)) {
 446          // Get the passphrase for the wallet
 447          AskPassphraseDialog dlg(AskPassphraseDialog::UnlockMigration, m_parent_widget, &passphrase);
 448          if (dlg.exec() == QDialog::Rejected) return;
 449      }
 450  
 451      showProgressDialog(tr("Migrate Wallet"), tr("Migrating Wallet <b>%1</b>…").arg(GUIUtil::HtmlEscape(name)));
 452  
 453      QTimer::singleShot(0, worker(), [this, name, passphrase] {
 454          auto res{node().walletLoader().migrateWallet(name, passphrase)};
 455  
 456          if (res) {
 457              m_success_message = tr("The wallet '%1' was migrated successfully.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(name)));
 458              if (res->watchonly_wallet_name) {
 459                  m_success_message += QChar(' ') + tr("Watchonly scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->watchonly_wallet_name.value())));
 460              }
 461              if (res->solvables_wallet_name) {
 462                  m_success_message += QChar(' ') + tr("Solvable but not watched scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->solvables_wallet_name.value())));
 463              }
 464              m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet));
 465          } else {
 466              m_error_message = util::ErrorString(res);
 467          }
 468  
 469          QTimer::singleShot(0, this, &MigrateWalletActivity::finish);
 470      });
 471  }
 472  
 473  void MigrateWalletActivity::migrate(const std::string& name)
 474  {
 475      // Warn the user about migration
 476      QMessageBox box(m_parent_widget);
 477      box.setWindowTitle(tr("Migrate wallet"));
 478      box.setText(tr("Are you sure you wish to migrate the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(name))));
 479      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"
 480                  "If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n"
 481                  "If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n"
 482                  "The migration process will create a backup of the wallet before migrating. This backup file will be named "
 483                  "<wallet name>-<timestamp>.legacy.bak and can be found in the directory for this wallet. In the event of "
 484                  "an incorrect migration, the backup can be restored with the \"Restore Wallet\" functionality."));
 485      box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
 486      box.setDefaultButton(QMessageBox::Yes);
 487      if (box.exec() != QMessageBox::Yes) return;
 488  
 489      do_migrate(name);
 490  }
 491  
 492  void MigrateWalletActivity::restore_and_migrate(const fs::path& path, const std::string& wallet_name)
 493  {
 494      // Warn the user about migration
 495      QMessageBox box(m_parent_widget);
 496      box.setWindowTitle(tr("Restore and Migrate wallet"));
 497      box.setText(tr("Are you sure you wish to restore the wallet file <i>%1</i> to <i>%2</i> and migrate it?").arg(GUIUtil::HtmlEscape(fs::PathToString(path)), GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(wallet_name))));
 498      box.setInformativeText(tr("Restoring the wallet will copy the backup file to the wallets directory and place it in the standard "
 499                  "wallet directory layout. The original file will not be modified.\n\n"
 500                  "Migrating the wallet will convert the restored wallet to one or more descriptor wallets. A new wallet backup will need to be made.\n"
 501                  "If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n"
 502                  "If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n"
 503                  "The migration process will create a backup of the wallet before migrating. This backup file will be named "
 504                  "<wallet name>-<timestamp>.legacy.bak and can be found in the directory for this wallet. In the event of "
 505                  "an incorrect migration, the backup can be restored with the \"Restore Wallet\" functionality."));
 506      box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
 507      box.setDefaultButton(QMessageBox::Yes);
 508      if (box.exec() != QMessageBox::Yes) return;
 509  
 510      showProgressDialog(
 511          //: Title of progress window which is displayed when wallets are being restored.
 512          tr("Restore Wallet"),
 513          /*: Descriptive text of the restore wallets progress window which indicates to
 514              the user that wallets are currently being restored.*/
 515          tr("Restoring Wallet <b>%1</b>…").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(wallet_name))));
 516  
 517      QTimer::singleShot(0, worker(), [this, path, wallet_name] {
 518          auto res{node().walletLoader().restoreWallet(path, wallet_name, m_warning_message, /*load_after_restore=*/false)};
 519  
 520          if (!res) {
 521              m_error_message = util::ErrorString(res);
 522              QTimer::singleShot(0, this, &MigrateWalletActivity::finish);
 523              return;
 524          }
 525          QTimer::singleShot(0, this, [this, wallet_name] {
 526              do_migrate(wallet_name);
 527          });
 528      });
 529  }
 530  
 531  void MigrateWalletActivity::finish()
 532  {
 533      if (!m_error_message.empty()) {
 534          QMessageBox::critical(m_parent_widget, tr("Migration failed"), QString::fromStdString(m_error_message.translated));
 535      } else {
 536          QMessageBox::information(m_parent_widget, tr("Migration Successful"), m_success_message);
 537      }
 538  
 539      if (m_wallet_model) Q_EMIT migrated(m_wallet_model);
 540  
 541      Q_EMIT finished();
 542  }
 543