mirror of
https://github.com/dashpay/dash.git
synced 2024-12-28 05:23:01 +01:00
e0d9dc18a8
-BEGIN VERIFY SCRIPT- sed -i -E -e 's/\.\.\."\)(\.|,|\)| )/…"\)\1/' -- $(git ls-files -- 'src' ':(exclude)src/qt/dashstrings.cpp' ':(exclude)src/immer/') sed -i -e 's/\.\.\.\\"/…\\"/' src/qt/sendcoinsdialog.cpp sed -i -e 's|\.\.\.</string>|…</string>|' src/qt/forms/*.ui sed -i -e 's|\.\.\.)</string>|…)</string>|' src/qt/forms/sendcoinsdialog.ui -END VERIFY SCRIPT-
397 lines
16 KiB
C++
397 lines
16 KiB
C++
// Copyright (c) 2016-2023 The Dash Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <qt/masternodelist.h>
|
|
#include <qt/forms/ui_masternodelist.h>
|
|
|
|
#include <evo/deterministicmns.h>
|
|
#include <qt/clientmodel.h>
|
|
#include <clientversion.h>
|
|
#include <coins.h>
|
|
#include <qt/guiutil.h>
|
|
#include <netbase.h>
|
|
#include <qt/walletmodel.h>
|
|
|
|
#include <univalue.h>
|
|
|
|
#include <QMessageBox>
|
|
#include <QTableWidgetItem>
|
|
#include <QtGui/QClipboard>
|
|
|
|
template <typename T>
|
|
class CMasternodeListWidgetItem : public QTableWidgetItem
|
|
{
|
|
T itemData;
|
|
|
|
public:
|
|
explicit CMasternodeListWidgetItem(const QString& text, const T& data, int type = Type) :
|
|
QTableWidgetItem(text, type),
|
|
itemData(data) {}
|
|
|
|
bool operator<(const QTableWidgetItem& other) const override
|
|
{
|
|
return itemData < ((CMasternodeListWidgetItem*)&other)->itemData;
|
|
}
|
|
};
|
|
|
|
MasternodeList::MasternodeList(QWidget* parent) :
|
|
QWidget(parent),
|
|
ui(new Ui::MasternodeList)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
GUIUtil::setFont({ui->label_count_2,
|
|
ui->countLabelDIP3
|
|
}, GUIUtil::FontWeight::Bold, 14);
|
|
GUIUtil::setFont({ui->label_filter_2}, GUIUtil::FontWeight::Normal, 15);
|
|
|
|
int columnAddressWidth = 200;
|
|
int columnTypeWidth = 160;
|
|
int columnStatusWidth = 80;
|
|
int columnPoSeScoreWidth = 80;
|
|
int columnRegisteredWidth = 80;
|
|
int columnLastPaidWidth = 80;
|
|
int columnNextPaymentWidth = 100;
|
|
int columnPayeeWidth = 130;
|
|
int columnOperatorRewardWidth = 130;
|
|
int columnCollateralWidth = 130;
|
|
int columnOwnerWidth = 130;
|
|
int columnVotingWidth = 130;
|
|
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_SERVICE, columnAddressWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_TYPE, columnTypeWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_STATUS, columnStatusWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_POSE, columnPoSeScoreWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_REGISTERED, columnRegisteredWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_LAST_PAYMENT, columnLastPaidWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_NEXT_PAYMENT, columnNextPaymentWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_PAYOUT_ADDRESS, columnPayeeWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_OPERATOR_REWARD, columnOperatorRewardWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_COLLATERAL_ADDRESS, columnCollateralWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_OWNER_ADDRESS, columnOwnerWidth);
|
|
ui->tableWidgetMasternodesDIP3->setColumnWidth(COLUMN_VOTING_ADDRESS, columnVotingWidth);
|
|
|
|
// dummy column for proTxHash
|
|
ui->tableWidgetMasternodesDIP3->insertColumn(COLUMN_PROTX_HASH);
|
|
ui->tableWidgetMasternodesDIP3->setColumnHidden(COLUMN_PROTX_HASH, true);
|
|
|
|
ui->tableWidgetMasternodesDIP3->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
ui->tableWidgetMasternodesDIP3->verticalHeader()->setVisible(false);
|
|
|
|
ui->checkBoxMyMasternodesOnly->setEnabled(false);
|
|
|
|
QAction* copyProTxHashAction = new QAction(tr("Copy ProTx Hash"), this);
|
|
QAction* copyCollateralOutpointAction = new QAction(tr("Copy Collateral Outpoint"), this);
|
|
contextMenuDIP3 = new QMenu(this);
|
|
contextMenuDIP3->addAction(copyProTxHashAction);
|
|
contextMenuDIP3->addAction(copyCollateralOutpointAction);
|
|
connect(ui->tableWidgetMasternodesDIP3, &QTableWidget::customContextMenuRequested, this, &MasternodeList::showContextMenuDIP3);
|
|
connect(ui->tableWidgetMasternodesDIP3, &QTableWidget::doubleClicked, this, &MasternodeList::extraInfoDIP3_clicked);
|
|
connect(copyProTxHashAction, &QAction::triggered, this, &MasternodeList::copyProTxHash_clicked);
|
|
connect(copyCollateralOutpointAction, &QAction::triggered, this, &MasternodeList::copyCollateralOutpoint_clicked);
|
|
|
|
timer = new QTimer(this);
|
|
connect(timer, &QTimer::timeout, this, &MasternodeList::updateDIP3ListScheduled);
|
|
timer->start(1000);
|
|
|
|
GUIUtil::updateFonts();
|
|
}
|
|
|
|
MasternodeList::~MasternodeList()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
void MasternodeList::setClientModel(ClientModel* model)
|
|
{
|
|
this->clientModel = model;
|
|
if (model) {
|
|
// try to update list when masternode count changes
|
|
connect(clientModel, &ClientModel::masternodeListChanged, this, &MasternodeList::handleMasternodeListChanged);
|
|
}
|
|
}
|
|
|
|
void MasternodeList::setWalletModel(WalletModel* model)
|
|
{
|
|
this->walletModel = model;
|
|
ui->checkBoxMyMasternodesOnly->setEnabled(model != nullptr);
|
|
}
|
|
|
|
void MasternodeList::showContextMenuDIP3(const QPoint& point)
|
|
{
|
|
QTableWidgetItem* item = ui->tableWidgetMasternodesDIP3->itemAt(point);
|
|
if (item) contextMenuDIP3->exec(QCursor::pos());
|
|
}
|
|
|
|
void MasternodeList::handleMasternodeListChanged()
|
|
{
|
|
LOCK(cs_dip3list);
|
|
mnListChanged = true;
|
|
}
|
|
|
|
void MasternodeList::updateDIP3ListScheduled()
|
|
{
|
|
TRY_LOCK(cs_dip3list, fLockAcquired);
|
|
if (!fLockAcquired) return;
|
|
|
|
if (!clientModel || clientModel->node().shutdownRequested()) {
|
|
return;
|
|
}
|
|
|
|
// To prevent high cpu usage update only once in MASTERNODELIST_FILTER_COOLDOWN_SECONDS seconds
|
|
// after filter was last changed unless we want to force the update.
|
|
if (fFilterUpdatedDIP3) {
|
|
int64_t nSecondsToWait = nTimeFilterUpdatedDIP3 - GetTime() + MASTERNODELIST_FILTER_COOLDOWN_SECONDS;
|
|
ui->countLabelDIP3->setText(tr("Please wait…") + " " + QString::number(nSecondsToWait));
|
|
|
|
if (nSecondsToWait <= 0) {
|
|
updateDIP3List();
|
|
fFilterUpdatedDIP3 = false;
|
|
}
|
|
} else if (mnListChanged) {
|
|
int64_t nMnListUpdateSecods = clientModel->masternodeSync().isBlockchainSynced() ? MASTERNODELIST_UPDATE_SECONDS : MASTERNODELIST_UPDATE_SECONDS * 10;
|
|
int64_t nSecondsToWait = nTimeUpdatedDIP3 - GetTime() + nMnListUpdateSecods;
|
|
|
|
if (nSecondsToWait <= 0) {
|
|
updateDIP3List();
|
|
mnListChanged = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MasternodeList::updateDIP3List()
|
|
{
|
|
if (!clientModel || clientModel->node().shutdownRequested()) {
|
|
return;
|
|
}
|
|
|
|
auto [mnList, pindex] = clientModel->getMasternodeList();
|
|
auto projectedPayees = mnList.GetProjectedMNPayees(pindex);
|
|
|
|
if (projectedPayees.empty() && mnList.GetValidMNsCount() > 0) {
|
|
// GetProjectedMNPayees failed to provide results for a list with valid mns.
|
|
// Keep current list and let it try again later.
|
|
return;
|
|
}
|
|
|
|
std::map<uint256, CTxDestination> mapCollateralDests;
|
|
|
|
{
|
|
// Get all UTXOs for each MN collateral in one go so that we can reduce locking overhead for cs_main
|
|
// We also do this outside of the below Qt list update loop to reduce cs_main locking time to a minimum
|
|
mnList.ForEachMN(false, [&](auto& dmn) {
|
|
CTxDestination collateralDest;
|
|
Coin coin;
|
|
if (clientModel->node().getUnspentOutput(dmn.collateralOutpoint, coin) && ExtractDestination(coin.out.scriptPubKey, collateralDest)) {
|
|
mapCollateralDests.emplace(dmn.proTxHash, collateralDest);
|
|
}
|
|
});
|
|
}
|
|
|
|
LOCK(cs_dip3list);
|
|
|
|
QString strToFilter;
|
|
ui->countLabelDIP3->setText(tr("Updating…"));
|
|
ui->tableWidgetMasternodesDIP3->setSortingEnabled(false);
|
|
ui->tableWidgetMasternodesDIP3->clearContents();
|
|
ui->tableWidgetMasternodesDIP3->setRowCount(0);
|
|
|
|
nTimeUpdatedDIP3 = GetTime();
|
|
|
|
std::map<uint256, int> nextPayments;
|
|
for (size_t i = 0; i < projectedPayees.size(); i++) {
|
|
const auto& dmn = projectedPayees[i];
|
|
nextPayments.emplace(dmn->proTxHash, mnList.GetHeight() + (int)i + 1);
|
|
}
|
|
|
|
std::set<COutPoint> setOutpts;
|
|
if (walletModel && ui->checkBoxMyMasternodesOnly->isChecked()) {
|
|
std::vector<COutPoint> vOutpts;
|
|
walletModel->wallet().listProTxCoins(vOutpts);
|
|
for (const auto& outpt : vOutpts) {
|
|
setOutpts.emplace(outpt);
|
|
}
|
|
}
|
|
|
|
mnList.ForEachMN(false, [&](auto& dmn) {
|
|
if (walletModel && ui->checkBoxMyMasternodesOnly->isChecked()) {
|
|
bool fMyMasternode = setOutpts.count(dmn.collateralOutpoint) ||
|
|
walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDOwner)) ||
|
|
walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDVoting)) ||
|
|
walletModel->wallet().isSpendable(dmn.pdmnState->scriptPayout) ||
|
|
walletModel->wallet().isSpendable(dmn.pdmnState->scriptOperatorPayout);
|
|
if (!fMyMasternode) return;
|
|
}
|
|
// populate list
|
|
// Address, Protocol, Status, Active Seconds, Last Seen, Pub Key
|
|
auto addr_key = dmn.pdmnState->addr.GetKey();
|
|
QByteArray addr_ba(reinterpret_cast<const char*>(addr_key.data()), addr_key.size());
|
|
QTableWidgetItem* addressItem = new CMasternodeListWidgetItem<QByteArray>(QString::fromStdString(dmn.pdmnState->addr.ToString()), addr_ba);
|
|
QTableWidgetItem* typeItem = new QTableWidgetItem(QString::fromStdString(std::string(GetMnType(dmn.nType).description)));
|
|
QTableWidgetItem* statusItem = new QTableWidgetItem(dmn.pdmnState->IsBanned() ? tr("POSE_BANNED") : tr("ENABLED"));
|
|
QTableWidgetItem* PoSeScoreItem = new CMasternodeListWidgetItem<int>(QString::number(dmn.pdmnState->nPoSePenalty), dmn.pdmnState->nPoSePenalty);
|
|
QTableWidgetItem* registeredItem = new CMasternodeListWidgetItem<int>(QString::number(dmn.pdmnState->nRegisteredHeight), dmn.pdmnState->nRegisteredHeight);
|
|
QTableWidgetItem* lastPaidItem = new CMasternodeListWidgetItem<int>(QString::number(dmn.pdmnState->nLastPaidHeight), dmn.pdmnState->nLastPaidHeight);
|
|
|
|
QString strNextPayment = "UNKNOWN";
|
|
int nNextPayment = 0;
|
|
if (nextPayments.count(dmn.proTxHash)) {
|
|
nNextPayment = nextPayments[dmn.proTxHash];
|
|
strNextPayment = QString::number(nNextPayment);
|
|
}
|
|
QTableWidgetItem* nextPaymentItem = new CMasternodeListWidgetItem<int>(strNextPayment, nNextPayment);
|
|
|
|
CTxDestination payeeDest;
|
|
QString payeeStr = tr("UNKNOWN");
|
|
if (ExtractDestination(dmn.pdmnState->scriptPayout, payeeDest)) {
|
|
payeeStr = QString::fromStdString(EncodeDestination(payeeDest));
|
|
}
|
|
QTableWidgetItem* payeeItem = new QTableWidgetItem(payeeStr);
|
|
|
|
QString operatorRewardStr = tr("NONE");
|
|
if (dmn.nOperatorReward) {
|
|
operatorRewardStr = QString::number(dmn.nOperatorReward / 100.0, 'f', 2) + "% ";
|
|
|
|
if (dmn.pdmnState->scriptOperatorPayout != CScript()) {
|
|
CTxDestination operatorDest;
|
|
if (ExtractDestination(dmn.pdmnState->scriptOperatorPayout, operatorDest)) {
|
|
operatorRewardStr += tr("to %1").arg(QString::fromStdString(EncodeDestination(operatorDest)));
|
|
} else {
|
|
operatorRewardStr += tr("to UNKNOWN");
|
|
}
|
|
} else {
|
|
operatorRewardStr += tr("but not claimed");
|
|
}
|
|
}
|
|
QTableWidgetItem* operatorRewardItem = new CMasternodeListWidgetItem<uint16_t>(operatorRewardStr, dmn.nOperatorReward);
|
|
|
|
QString collateralStr = tr("UNKNOWN");
|
|
auto collateralDestIt = mapCollateralDests.find(dmn.proTxHash);
|
|
if (collateralDestIt != mapCollateralDests.end()) {
|
|
collateralStr = QString::fromStdString(EncodeDestination(collateralDestIt->second));
|
|
}
|
|
QTableWidgetItem* collateralItem = new QTableWidgetItem(collateralStr);
|
|
|
|
QString ownerStr = QString::fromStdString(EncodeDestination(PKHash(dmn.pdmnState->keyIDOwner)));
|
|
QTableWidgetItem* ownerItem = new QTableWidgetItem(ownerStr);
|
|
|
|
QString votingStr = QString::fromStdString(EncodeDestination(PKHash(dmn.pdmnState->keyIDVoting)));
|
|
QTableWidgetItem* votingItem = new QTableWidgetItem(votingStr);
|
|
|
|
QTableWidgetItem* proTxHashItem = new QTableWidgetItem(QString::fromStdString(dmn.proTxHash.ToString()));
|
|
|
|
if (strCurrentFilterDIP3 != "") {
|
|
strToFilter = addressItem->text() + " " +
|
|
typeItem->text() + " " +
|
|
statusItem->text() + " " +
|
|
PoSeScoreItem->text() + " " +
|
|
registeredItem->text() + " " +
|
|
lastPaidItem->text() + " " +
|
|
nextPaymentItem->text() + " " +
|
|
payeeItem->text() + " " +
|
|
operatorRewardItem->text() + " " +
|
|
collateralItem->text() + " " +
|
|
ownerItem->text() + " " +
|
|
votingItem->text() + " " +
|
|
proTxHashItem->text();
|
|
if (!strToFilter.contains(strCurrentFilterDIP3)) return;
|
|
}
|
|
|
|
ui->tableWidgetMasternodesDIP3->insertRow(0);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_SERVICE, addressItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_TYPE, typeItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_STATUS, statusItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_POSE, PoSeScoreItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_REGISTERED, registeredItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_LAST_PAYMENT, lastPaidItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_NEXT_PAYMENT, nextPaymentItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_PAYOUT_ADDRESS, payeeItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_OPERATOR_REWARD, operatorRewardItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_COLLATERAL_ADDRESS, collateralItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_OWNER_ADDRESS, ownerItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_VOTING_ADDRESS, votingItem);
|
|
ui->tableWidgetMasternodesDIP3->setItem(0, COLUMN_PROTX_HASH, proTxHashItem);
|
|
});
|
|
|
|
ui->countLabelDIP3->setText(QString::number(ui->tableWidgetMasternodesDIP3->rowCount()));
|
|
ui->tableWidgetMasternodesDIP3->setSortingEnabled(true);
|
|
}
|
|
|
|
void MasternodeList::on_filterLineEditDIP3_textChanged(const QString& strFilterIn)
|
|
{
|
|
strCurrentFilterDIP3 = strFilterIn;
|
|
nTimeFilterUpdatedDIP3 = GetTime();
|
|
fFilterUpdatedDIP3 = true;
|
|
ui->countLabelDIP3->setText(tr("Please wait…") + " " + QString::number(MASTERNODELIST_FILTER_COOLDOWN_SECONDS));
|
|
}
|
|
|
|
void MasternodeList::on_checkBoxMyMasternodesOnly_stateChanged(int state)
|
|
{
|
|
// no cooldown
|
|
nTimeFilterUpdatedDIP3 = GetTime() - MASTERNODELIST_FILTER_COOLDOWN_SECONDS;
|
|
fFilterUpdatedDIP3 = true;
|
|
}
|
|
|
|
CDeterministicMNCPtr MasternodeList::GetSelectedDIP3MN()
|
|
{
|
|
if (!clientModel) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::string strProTxHash;
|
|
{
|
|
LOCK(cs_dip3list);
|
|
|
|
QItemSelectionModel* selectionModel = ui->tableWidgetMasternodesDIP3->selectionModel();
|
|
QModelIndexList selected = selectionModel->selectedRows();
|
|
|
|
if (selected.count() == 0) return nullptr;
|
|
|
|
QModelIndex index = selected.at(0);
|
|
int nSelectedRow = index.row();
|
|
strProTxHash = ui->tableWidgetMasternodesDIP3->item(nSelectedRow, COLUMN_PROTX_HASH)->text().toStdString();
|
|
}
|
|
|
|
uint256 proTxHash;
|
|
proTxHash.SetHex(strProTxHash);
|
|
|
|
return clientModel->getMasternodeList().first.GetMN(proTxHash);;
|
|
}
|
|
|
|
void MasternodeList::extraInfoDIP3_clicked()
|
|
{
|
|
auto dmn = GetSelectedDIP3MN();
|
|
if (!dmn) {
|
|
return;
|
|
}
|
|
|
|
UniValue json = dmn->ToJson();
|
|
|
|
// Title of popup window
|
|
QString strWindowtitle = tr("Additional information for DIP3 Masternode %1").arg(QString::fromStdString(dmn->proTxHash.ToString()));
|
|
QString strText = QString::fromStdString(json.write(2));
|
|
|
|
QMessageBox::information(this, strWindowtitle, strText);
|
|
}
|
|
|
|
void MasternodeList::copyProTxHash_clicked()
|
|
{
|
|
auto dmn = GetSelectedDIP3MN();
|
|
if (!dmn) {
|
|
return;
|
|
}
|
|
|
|
QApplication::clipboard()->setText(QString::fromStdString(dmn->proTxHash.ToString()));
|
|
}
|
|
|
|
void MasternodeList::copyCollateralOutpoint_clicked()
|
|
{
|
|
auto dmn = GetSelectedDIP3MN();
|
|
if (!dmn) {
|
|
return;
|
|
}
|
|
|
|
QApplication::clipboard()->setText(QString::fromStdString(dmn->collateralOutpoint.ToStringShort()));
|
|
}
|