PIVX Core  5.6.99
P2P Digital Currency
masternodeswidget.cpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2022 The PIVX 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 "masternodeswidget.h"
6 #include "ui_masternodeswidget.h"
7 
8 #include "coincontrol.h"
10 #include "mninfodialog.h"
11 #include "mnrow.h"
12 #include "qtutils.h"
13 
14 #include "clientmodel.h"
15 #include "guiutil.h"
16 #include "mnmodel.h"
17 #include "optionbutton.h"
18 #include "qt/walletmodel.h"
19 
20 #define DECORATION_SIZE 65
21 #define NUM_ITEMS 3
22 #define REQUEST_START_ALL 1
23 #define REQUEST_START_MISSING 2
24 
25 class MNHolder : public FurListRow<QWidget*>
26 {
27 public:
28  explicit MNHolder(bool _isLightTheme) : FurListRow(), isLightTheme(_isLightTheme) {}
29 
30  MNRow* createHolder(int pos) override
31  {
32  if (!cachedRow) cachedRow = new MNRow();
33  return cachedRow;
34  }
35 
36  void init(QWidget* holder,const QModelIndex &index, bool isHovered, bool isSelected) const override
37  {
38  MNRow* row = static_cast<MNRow*>(holder);
39  QString label = index.data(Qt::DisplayRole).toString();
40  QString address = index.sibling(index.row(), MNModel::ADDRESS).data(Qt::DisplayRole).toString();
41  QString status = index.sibling(index.row(), MNModel::STATUS).data(Qt::DisplayRole).toString();
42  bool wasCollateralAccepted = index.sibling(index.row(), MNModel::WAS_COLLATERAL_ACCEPTED).data(Qt::DisplayRole).toBool();
43  row->updateView("Address: " + address, label, status, wasCollateralAccepted);
44  }
45 
46  QColor rectColor(bool isHovered, bool isSelected) override
47  {
48  return getRowColor(isLightTheme, isHovered, isSelected);
49  }
50 
51  ~MNHolder() override{}
52 
54  MNRow* cachedRow = nullptr;
55 };
56 
58  PWidget(parent),
59  ui(new Ui::MasterNodesWidget),
60  isLoading(false)
61 {
62  ui->setupUi(this);
63 
66  new MNHolder(isLightTheme()),
67  this
68  );
69 
70  this->setStyleSheet(parent->styleSheet());
71 
72  /* Containers */
73  setCssProperty(ui->left, "container");
74  ui->left->setContentsMargins(0,20,0,20);
75  setCssProperty(ui->right, "container-right");
76  ui->right->setContentsMargins(20,20,20,20);
77 
78  /* Light Font */
79  QFont fontLight;
80  fontLight.setWeight(QFont::Light);
81 
82  /* Title */
83  setCssTitleScreen(ui->labelTitle);
84  ui->labelTitle->setFont(fontLight);
85  setCssSubtitleScreen(ui->labelSubtitle1);
86 
87  /* Buttons */
88  setCssBtnPrimary(ui->pushButtonSave);
89  setCssBtnPrimary(ui->pushButtonStartAll);
90  setCssBtnPrimary(ui->pushButtonStartMissing);
91 
92  /* Coin control */
94 
95  /* Options */
96  ui->btnAbout->setTitleClassAndText("btn-title-grey", tr("What is a Masternode?"));
97  ui->btnAbout->setSubTitleClassAndText("text-subtitle", tr("FAQ explaining what Masternodes are"));
98  ui->btnAboutController->setTitleClassAndText("btn-title-grey", tr("What is a Controller?"));
99  ui->btnAboutController->setSubTitleClassAndText("text-subtitle", tr("FAQ explaining what is a Masternode Controller"));
100  ui->btnCoinControl->setTitleClassAndText("btn-title-grey", "Coin Control");
101  ui->btnCoinControl->setSubTitleClassAndText("text-subtitle", "Select the source of coins to create a Masternode");
102 
103  setCssProperty(ui->listMn, "container");
104  ui->listMn->setItemDelegate(delegate);
105  ui->listMn->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE));
106  ui->listMn->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2));
107  ui->listMn->setAttribute(Qt::WA_MacShowFocusRect, false);
108  ui->listMn->setSelectionBehavior(QAbstractItemView::SelectRows);
109 
110  ui->emptyContainer->setVisible(false);
111  setCssProperty(ui->pushImgEmpty, "img-empty-master");
112  setCssProperty(ui->labelEmpty, "text-empty");
113 
114  connect(ui->pushButtonSave, &QPushButton::clicked, this, &MasterNodesWidget::onCreateMNClicked);
115  connect(ui->pushButtonStartAll, &QPushButton::clicked, [this]() {
116  onStartAllClicked(REQUEST_START_ALL);
117  });
118  connect(ui->pushButtonStartMissing, &QPushButton::clicked, [this]() {
119  onStartAllClicked(REQUEST_START_MISSING);
120  });
121  connect(ui->listMn, &QListView::clicked, this, &MasterNodesWidget::onMNClicked);
122  connect(ui->btnAbout, &OptionButton::clicked, [this](){window->openFAQ(SettingsFaqWidget::Section::MASTERNODE);});
123  connect(ui->btnAboutController, &OptionButton::clicked, [this](){window->openFAQ(SettingsFaqWidget::Section::MNCONTROLLER);});
124  connect(ui->btnCoinControl, &OptionButton::clicked, this, &MasterNodesWidget::onCoinControlClicked);
125 }
126 
127 void MasterNodesWidget::showEvent(QShowEvent *event)
128 {
129  if (mnModel) mnModel->updateMNList();
130  if (!timer) {
131  timer = new QTimer(this);
132  connect(timer, &QTimer::timeout, [this]() {mnModel->updateMNList();});
133  }
134  timer->start(30000);
135 }
136 
137 void MasterNodesWidget::hideEvent(QHideEvent *event)
138 {
139  if (timer) timer->stop();
140 }
141 
143 {
144  mnModel = _mnModel;
145  ui->listMn->setModel(mnModel);
146  ui->listMn->setModelColumn(AddressTableModel::Label);
147  updateListState();
148 }
149 
151 {
152  bool show = mnModel->rowCount() > 0;
153  ui->listMn->setVisible(show);
154  ui->emptyContainer->setVisible(!show);
155  ui->pushButtonStartAll->setVisible(show);
156 }
157 
158 void MasterNodesWidget::onMNClicked(const QModelIndex& _index)
159 {
160  ui->listMn->setCurrentIndex(_index);
161  QRect rect = ui->listMn->visualRect(_index);
162  QPoint pos = rect.topRight();
163  pos.setX(pos.x() - (DECORATION_SIZE * 2));
164  pos.setY(pos.y() + (DECORATION_SIZE * 1.5));
165  if (!this->menu) {
166  this->menu = new TooltipMenu(window, this);
167  this->menu->setEditBtnText(tr("Start"));
168  this->menu->setDeleteBtnText(tr("Delete"));
169  this->menu->setCopyBtnText(tr("Info"));
170  connect(this->menu, &TooltipMenu::message, this, &AddressesWidget::message);
174  this->menu->adjustSize();
175  } else {
176  this->menu->hide();
177  }
178  this->index = _index;
179  menu->move(pos);
180  menu->show();
181 
182  // Back to regular status
183  ui->listMn->scrollTo(index);
184  ui->listMn->clearSelection();
185  ui->listMn->setFocus();
186 }
187 
189 {
190  bool isTierTwoSync = mnModel->isMNsNetworkSynced();
191  if (!isTierTwoSync) inform(tr("Please wait until the node is fully synced"));
192  return isTierTwoSync;
193 }
194 
196 {
197  if (walletModel) {
198  if (!walletModel->isRegTestNetwork() && !checkMNsNetwork()) return;
199  if (index.sibling(index.row(), MNModel::WAS_COLLATERAL_ACCEPTED).data(Qt::DisplayRole).toBool()) {
200  // Start MN
201  QString strAlias = this->index.data(Qt::DisplayRole).toString();
202  if (ask(tr("Start Masternode"), tr("Are you sure you want to start masternode %1?\n").arg(strAlias))) {
204  if (!ctx.isValid()) {
205  // Unlock wallet was cancelled
206  inform(tr("Cannot edit masternode, wallet locked"));
207  return;
208  }
209  startAlias(strAlias);
210  }
211  } else {
212  inform(tr("Cannot start masternode, the collateral transaction has not been confirmed by the network yet.\n"
213  "Please wait few more minutes (masternode collaterals require %1 confirmations).").arg(mnModel->getMasternodeCollateralMinConf()));
214  }
215  }
216 }
217 
218 void MasterNodesWidget::startAlias(const QString& strAlias)
219 {
220  QString strStatusHtml;
221  strStatusHtml += "Alias: " + strAlias + " ";
222 
223  int failed_amount = 0;
224  int success_amount = 0;
225  std::string alias = strAlias.toStdString();
226  std::string strError;
227  mnModel->startAllLegacyMNs(false, failed_amount, success_amount, &alias, &strError);
228  if (failed_amount > 0) {
229  strStatusHtml = tr("failed to start.\nError: %1").arg(QString::fromStdString(strError));
230  } else if (success_amount > 0) {
231  strStatusHtml = tr("successfully started");
232  }
233  // update UI and notify
234  updateModelAndInform(strStatusHtml);
235 }
236 
237 void MasterNodesWidget::updateModelAndInform(const QString& informText)
238 {
240  inform(informText);
241 }
242 
244 {
245  if (!Params().IsRegTestNet() && !checkMNsNetwork()) return; // skip on RegNet: so we can test even if tier two not synced
246 
247  if (isLoading) {
248  inform(tr("Background task is being executed, please wait"));
249  } else {
250  std::unique_ptr<WalletModel::UnlockContext> pctx = std::make_unique<WalletModel::UnlockContext>(walletModel->requestUnlock());
251  if (!pctx->isValid()) {
252  warn(tr("Start ALL masternodes failed"), tr("Wallet unlock cancelled"));
253  return;
254  }
255  isLoading = true;
256  if (!execute(type, std::move(pctx))) {
257  isLoading = false;
258  inform(tr("Cannot perform Masternodes start"));
259  }
260  }
261 }
262 
263 bool MasterNodesWidget::startAll(QString& failText, bool onlyMissing)
264 {
265  int amountOfMnFailed = 0;
266  int amountOfMnStarted = 0;
267  mnModel->startAllLegacyMNs(onlyMissing, amountOfMnFailed, amountOfMnStarted);
268  if (amountOfMnFailed > 0) {
269  failText = tr("%1 Masternodes failed to start, %2 started").arg(amountOfMnFailed).arg(amountOfMnStarted);
270  return false;
271  }
272  return true;
273 }
274 
276 {
277  bool isStartMissing = type == REQUEST_START_MISSING;
278  if (type == REQUEST_START_ALL || isStartMissing) {
279  QString failText;
280  QString inform = startAll(failText, isStartMissing) ? tr("All Masternodes started!") : failText;
281  QMetaObject::invokeMethod(this, "updateModelAndInform", Qt::QueuedConnection,
282  Q_ARG(QString, inform));
283  }
284 
285  isLoading = false;
286 }
287 
288 void MasterNodesWidget::onError(QString error, int type)
289 {
290  if (type == REQUEST_START_ALL) {
291  QMetaObject::invokeMethod(this, "inform", Qt::QueuedConnection,
292  Q_ARG(QString, "Error starting all Masternodes"));
293  }
294 }
295 
297 {
299  if (!ctx.isValid()) {
300  // Unlock wallet was cancelled
301  inform(tr("Cannot show Masternode information, wallet locked"));
302  return;
303  }
304  showHideOp(true);
305  MnInfoDialog* dialog = new MnInfoDialog(window);
306  QString label = index.data(Qt::DisplayRole).toString();
307  QString address = index.sibling(index.row(), MNModel::ADDRESS).data(Qt::DisplayRole).toString();
308  QString status = index.sibling(index.row(), MNModel::STATUS).data(Qt::DisplayRole).toString();
309  QString txId = index.sibling(index.row(), MNModel::COLLATERAL_ID).data(Qt::DisplayRole).toString();
310  QString outIndex = index.sibling(index.row(), MNModel::COLLATERAL_OUT_INDEX).data(Qt::DisplayRole).toString();
311  QString pubKey = index.sibling(index.row(), MNModel::PUB_KEY).data(Qt::DisplayRole).toString();
312  dialog->setData(pubKey, label, address, txId, outIndex, status);
313  dialog->adjustSize();
314  showDialog(dialog, 3, 17);
315  if (dialog->exportMN) {
316  if (ask(tr("Remote Masternode Data"),
317  tr("You are just about to export the required data to run a Masternode\non a remote server to your clipboard.\n\n\n"
318  "You will only have to paste the data in the pivx.conf file\nof your remote server and start it, "
319  "then start the Masternode using\nthis controller wallet (select the Masternode in the list and press \"start\").\n"
320  ))) {
321  // export data
322  QString exportedMN = "masternode=1\n"
323  "externalip=" + address.left(address.lastIndexOf(":")) + "\n" +
324  "masternodeaddr=" + address + + "\n" +
325  "masternodeprivkey=" + index.sibling(index.row(), MNModel::PRIV_KEY).data(Qt::DisplayRole).toString() + "\n";
326  GUIUtil::setClipboard(exportedMN);
327  inform(tr("Masternode data copied to the clipboard."));
328  }
329  }
330 
331  dialog->deleteLater();
332 }
333 
335 {
336  QString txId = index.sibling(index.row(), MNModel::COLLATERAL_ID).data(Qt::DisplayRole).toString();
337  QString outIndex = index.sibling(index.row(), MNModel::COLLATERAL_OUT_INDEX).data(Qt::DisplayRole).toString();
338  QString qAliasString = index.data(Qt::DisplayRole).toString();
339 
340  bool convertOK = false;
341  unsigned int indexOut = outIndex.toUInt(&convertOK);
342  if (!convertOK) {
343  inform(tr("Invalid collateral output index"));
344  return;
345  }
346 
347  if (!ask(tr("Delete Masternode"), tr("You are just about to delete Masternode:\n%1\n\nAre you sure?").arg(qAliasString))) {
348  return;
349  }
350 
351  QString errorStr;
352  if (!mnModel->removeLegacyMN(qAliasString.toStdString(), txId.toStdString(), indexOut, errorStr)) {
353  inform(errorStr);
354  return;
355  }
356  // Update list
358  updateListState();
359 }
360 
362 {
364  if (!ctx.isValid()) {
365  // Unlock wallet was cancelled
366  inform(tr("Cannot create Masternode controller, wallet locked"));
367  return;
368  }
369 
370  CAmount mnCollateralAmount = mnModel->getMNCollateralRequiredAmount();
371  if (walletModel->getBalance() <= mnCollateralAmount) {
372  inform(tr("Not enough balance to create a masternode, %1 required.")
373  .arg(GUIUtil::formatBalance(mnCollateralAmount, BitcoinUnits::PIV)));
374  return;
375  }
376 
378  std::vector<OutPointWrapper> coins;
380  CAmount selectedBalance = 0;
381  for (const auto& coin : coins) {
382  selectedBalance += coin.value;
383  }
384  if (selectedBalance <= mnCollateralAmount) {
385  inform(tr("Not enough coins selected to create a masternode, %1 required.")
386  .arg(GUIUtil::formatBalance(mnCollateralAmount, BitcoinUnits::PIV)));
387  return;
388  }
390  }
391 
392  showHideOp(true);
394  if (openDialogWithOpaqueBackgroundY(dialog, window, 5, 7)) {
395  if (dialog->isOk) {
396  // Update list
397  mnModel->addMn(dialog->mnEntry);
398  updateListState();
399  // add mn
400  inform(dialog->returnStr);
401  } else {
402  warn(tr("Error creating masternode"), dialog->returnStr);
403  }
404  }
405  dialog->deleteLater();
407 }
408 
410 {
411  static_cast<MNHolder*>(this->delegate->getRowFactory())->isLightTheme = isLightTheme;
412 }
413 
415 {
419  coinControlDialog->exec();
420  ui->btnCoinControl->setActive(coinControlDialog->coinControl->HasSelected());
421 }
422 
424 {
427  ui->btnCoinControl->setActive(false);
428 }
429 
431 {
432  delete ui;
433 }
int64_t CAmount
Amount in PIV (Can be negative)
Definition: amount.h:13
false
Definition: bls_dkg.cpp:151
const CChainParams & Params()
Return the currently selected parameters.
@ Label
User specified label.
bool HasSelected() const
Definition: coincontrol.h:66
void ListSelected(std::vector< OutPointWrapper > &vOutpoints) const
Definition: coincontrol.h:91
void SetNull()
Definition: coincontrol.h:55
void setSelectionType(bool isTransparent)
void setModel(WalletModel *model)
CCoinControl * coinControl
QColor rectColor(bool isHovered, bool isSelected) override
MNRow * createHolder(int pos) override
void init(QWidget *holder, const QModelIndex &index, bool isHovered, bool isSelected) const override
MNRow * cachedRow
MNHolder(bool _isLightTheme)
~MNHolder() override
bool removeMn(const QModelIndex &index)
Definition: mnmodel.cpp:140
CAmount getMNCollateralRequiredAmount()
Definition: mnmodel.cpp:200
void startAllLegacyMNs(bool onlyMissing, int &amountOfMnFailed, int &amountOfMnStarted, std::string *aliasFilter=nullptr, std::string *error_ret=nullptr)
Definition: mnmodel.cpp:286
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Definition: mnmodel.cpp:55
bool addMn(CMasternodeConfig::CMasternodeEntry *entry)
Definition: mnmodel.cpp:151
bool isMNsNetworkSynced()
Definition: mnmodel.cpp:190
int getMasternodeCollateralMinConf()
Definition: mnmodel.cpp:205
void resetCoinControl()
Definition: mnmodel.cpp:513
bool removeLegacyMN(const std::string &alias_to_remove, const std::string &tx_id, unsigned int out_index, QString &ret_error)
Definition: mnmodel.cpp:417
void updateMNList()
Definition: mnmodel.cpp:30
@ PRIV_KEY
Definition: mnmodel.h:36
@ STATUS
Node status.
Definition: mnmodel.h:31
@ WAS_COLLATERAL_ACCEPTED
Definition: mnmodel.h:37
@ COLLATERAL_ID
Definition: mnmodel.h:34
@ PUB_KEY
Definition: mnmodel.h:33
@ COLLATERAL_OUT_INDEX
Definition: mnmodel.h:35
@ ADDRESS
Node address.
Definition: mnmodel.h:29
void setCoinControl(CCoinControl *coinControl)
Definition: mnmodel.cpp:508
Definition: mnrow.h:15
void updateView(QString address, const QString &label, QString status, bool wasCollateralAccepted)
Definition: mnrow.cpp:21
CMasternodeConfig::CMasternodeEntry * mnEntry
TooltipMenu * menu
void changeTheme(bool isLightTheme, QString &theme) override
void hideEvent(QHideEvent *event) override
CoinControlDialog * coinControlDialog
void run(int type) override
void onStartAllClicked(int type)
void setMNModel(MNModel *_mnModel)
std::atomic< bool > isLoading
void onMNClicked(const QModelIndex &index)
void startAlias(const QString &strAlias)
bool startAll(QString &failedMN, bool onlyMissing)
MasterNodesWidget(PIVXGUI *parent=nullptr)
void showEvent(QShowEvent *event) override
Ui::MasterNodesWidget * ui
void updateModelAndInform(const QString &informText)
FurAbstractListItemDelegate * delegate
void onError(QString error, int type) override
void setData(const QString &_pubKey, const QString &name, const QString &address, const QString &_txId, const QString &outputIndex, const QString &status)
void clicked()
PIVX GUI main class.
Definition: pivxgui.h:46
PIVXGUI * window
Definition: pwidget.h:59
void inform(const QString &message)
Definition: pwidget.cpp:45
WalletModel * walletModel
Definition: pwidget.h:61
void showDialog(QDialog *dialog, int xDiv=3, int yDiv=5)
Definition: pwidget.cpp:62
void warn(const QString &title, const QString &message)
Definition: pwidget.cpp:50
bool execute(int type, std::unique_ptr< WalletModel::UnlockContext > pctx=nullptr)
Definition: pwidget.cpp:94
void showHideOp(bool show)
Definition: pwidget.cpp:40
void message(const QString &title, const QString &body, unsigned int style, bool *ret=nullptr)
bool ask(const QString &title, const QString &message)
Definition: pwidget.cpp:55
void setDeleteBtnText(const QString &btnText)
Definition: tooltipmenu.cpp:30
void setEditBtnText(const QString &btnText)
Definition: tooltipmenu.cpp:26
void onDeleteClicked()
void setCopyBtnText(const QString &btnText)
Definition: tooltipmenu.cpp:34
void onEditClicked()
void onCopyClicked()
CAmount getBalance(const CCoinControl *coinControl=nullptr, bool fIncludeDelegated=true, bool fUnlockedOnly=false, bool fIncludeShielded=true) const
UnlockContext requestUnlock()
bool isRegTestNetwork() const
Definition: walletmodel.cpp:81
#define REQUEST_START_ALL
#define REQUEST_START_MISSING
#define DECORATION_SIZE
#define NUM_ITEMS
QString formatBalance(CAmount amount, int nDisplayUnit, bool isZpiv)
Definition: guiutil.cpp:119
void setClipboard(const QString &str)
Definition: guiutil.cpp:670
bool isLightTheme()
Definition: qtutils.cpp:210
bool openDialogWithOpaqueBackgroundY(QDialog *widget, PIVXGUI *gui, double posX, int posY, bool hideOpaqueBackground)
Definition: qtutils.cpp:59
void setCssTitleScreen(QLabel *label)
Definition: qtutils.cpp:324
void setCssProperty(std::initializer_list< QWidget * > args, const QString &value)
Definition: qtutils.cpp:334
QColor getRowColor(bool isLightTheme, bool isHovered, bool isSelected)
Definition: qtutils.cpp:233
void setCssSubtitleScreen(QWidget *wid)
Definition: qtutils.cpp:329
void setCssBtnPrimary(QPushButton *btn, bool forceUpdate)
Definition: qtutils.cpp:302
bool error(const char *fmt, const Args &... args)
Definition: system.h:77