From c7e332d15d78ba41338a62cfd57f64f72db7b32b Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 25 Jan 2022 12:38:08 -0300 Subject: [PATCH 01/82] mnmodel: implement MN wrapper first step --- src/masternodeconfig.cpp | 21 +++-- src/masternodeconfig.h | 30 ++++--- src/qt/pivx/masternodewizarddialog.cpp | 3 +- src/qt/pivx/mnmodel.cpp | 106 +++++++++++++------------ src/qt/pivx/mnmodel.h | 38 +++++++-- 5 files changed, 126 insertions(+), 72 deletions(-) diff --git a/src/masternodeconfig.cpp b/src/masternodeconfig.cpp index c5ce042fed853..026f02387441b 100644 --- a/src/masternodeconfig.cpp +++ b/src/masternodeconfig.cpp @@ -5,16 +5,20 @@ #include "masternodeconfig.h" #include "fs.h" +#include "key_io.h" #include "netbase.h" #include "util/system.h" -#include "guiinterface.h" -#include CMasternodeConfig masternodeConfig; -CMasternodeConfig::CMasternodeEntry* CMasternodeConfig::add(std::string alias, std::string ip, std::string privKey, std::string txHash, std::string outputIndex) +CMasternodeConfig::CMasternodeEntry* CMasternodeConfig::add(std::string alias, + std::string ip, + std::string privKeyStr, + std::string pubKeyStr, + std::string txHash, + std::string outputIndex) { - CMasternodeEntry cme(alias, ip, privKey, txHash, outputIndex); + CMasternodeEntry cme(alias, ip, privKeyStr, pubKeyStr, txHash, outputIndex); entries.push_back(cme); return &(entries[entries.size()-1]); } @@ -94,8 +98,15 @@ bool CMasternodeConfig::read(std::string& strErr) return false; } + CKey secretKey = KeyIO::DecodeSecret(privKey); + if (!secretKey.IsValid()) { + strErr = _("Invalid private key in masternode.conf") + "\n" + + strprintf(_("Line: %d"), linenumber) + "\n\"" + line + "\""; + streamConfig.close(); + return false; + } - add(alias, ip, privKey, txHash, outputIndex); + add(alias, ip, privKey, secretKey.GetPubKey().GetHash().GetHex(), txHash, outputIndex); } streamConfig.close(); diff --git a/src/masternodeconfig.h b/src/masternodeconfig.h index 17ea6c5da5a08..e8de8014bb169 100644 --- a/src/masternodeconfig.h +++ b/src/masternodeconfig.h @@ -21,27 +21,39 @@ class CMasternodeConfig private: std::string alias; std::string ip; - std::string privKey; + std::string privkeyStr; + std::string pubkeyStr; std::string txHash; std::string outputIndex; public: - CMasternodeEntry(std::string& _alias, std::string& _ip, std::string& _privKey, std::string& _txHash, std::string& _outputIndex) : - alias(_alias), ip(_ip), privKey(_privKey), txHash(_txHash), outputIndex(_outputIndex) { } + CMasternodeEntry(std::string& _alias, + std::string& _ip, + std::string& _privkeyStr, + std::string& _pubkeyStr, + std::string& _txHash, + std::string& _outputIndex) : + alias(_alias), ip(_ip), privkeyStr(_privkeyStr), pubkeyStr(_pubkeyStr), txHash(_txHash), outputIndex(_outputIndex) { } - const std::string& getAlias() const { return alias; } - const std::string& getOutputIndex() const { return outputIndex; } + std::string getAlias() const { return alias; } + std::string getOutputIndex() const { return outputIndex; } bool castOutputIndex(int& n) const; - const std::string& getPrivKey() const { return privKey; } - const std::string& getTxHash() const { return txHash; } - const std::string& getIp() const { return ip; } + std::string getPrivKey() const { return privkeyStr; } + std::string getPubKeyStr() const { return pubkeyStr; } + std::string getTxHash() const { return txHash; } + std::string getIp() const { return ip; } }; CMasternodeConfig() { entries = std::vector(); } void clear() { LOCK(cs_entries); entries.clear(); } bool read(std::string& strErr); - CMasternodeConfig::CMasternodeEntry* add(std::string alias, std::string ip, std::string privKey, std::string txHash, std::string outputIndex); + CMasternodeConfig::CMasternodeEntry* add(std::string alias, + std::string ip, + std::string privKeyStr, + std::string pubKeyStr, + std::string txHash, + std::string outputIndex); void remove(std::string alias); std::vector getEntries() { LOCK(cs_entries); return entries; } diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 0136adb520741..9c7b79030857c 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -203,6 +203,7 @@ bool MasterNodeWizardDialog::createMN() CKey secret; secret.MakeNewKey(false); std::string mnKeyString = KeyIO::EncodeSecret(secret); + std::string mnPubKeyStr = secret.GetPubKey().GetHash().GetHex(); // Look for a valid collateral utxo COutPoint collateralOut; @@ -226,7 +227,7 @@ bool MasterNodeWizardDialog::createMN() } } - mnEntry = mnModel->createLegacyMN(collateralOut, alias, ipAddress, port, mnKeyString, returnStr); + mnEntry = mnModel->createLegacyMN(collateralOut, alias, ipAddress, port, mnKeyString, mnPubKeyStr, returnStr); if (!mnEntry) { // error str set inside createLegacyMN return false; diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 82130dab2b3c8..cdf4a8b1c1b83 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -33,16 +33,18 @@ void MNModel::updateMNList() collateralTxAccepted.clear(); for (const CMasternodeConfig::CMasternodeEntry& mne : masternodeConfig.getEntries()) { int nIndex; - if (!mne.castOutputIndex(nIndex)) - continue; + if (!mne.castOutputIndex(nIndex)) continue; const uint256& txHash = uint256S(mne.getTxHash()); CTxIn txIn(txHash, uint32_t(nIndex)); CMasternode* pmn = mnodeman.Find(txIn.prevout); - if (!pmn) { - pmn = new CMasternode(); - pmn->vin = txIn; - } - nodes.insert(QString::fromStdString(mne.getAlias()), std::make_pair(QString::fromStdString(mne.getIp()), pmn)); + nodes.append(MasternodeWrapper( + QString::fromStdString(mne.getAlias()), + QString::fromStdString(mne.getIp()), + pmn, + pmn ? pmn->vin.prevout : txIn.prevout, + Optional(QString::fromStdString(mne.getPubKeyStr()))) + ); + if (walletModel) { collateralTxAccepted.insert(mne.getTxHash(), walletModel->getWalletTxDepth(txHash) >= mnMinConf); } @@ -61,51 +63,48 @@ int MNModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; - return 6; + return ColumnIndex::COLUMN_COUNT; } QVariant MNModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) - return QVariant(); + return QVariant(); - // rec could be null, always verify it. - CMasternode* rec = static_cast(index.internalPointer()); - bool isAvailable = rec; int row = index.row(); + const MasternodeWrapper& mnWrapper = nodes.at(row); if (role == Qt::DisplayRole || role == Qt::EditRole) { switch (index.column()) { case ALIAS: - return nodes.uniqueKeys().value(row); + return mnWrapper.label; case ADDRESS: - return nodes.values().value(row).first; + return mnWrapper.ipPort; case PUB_KEY: - return (isAvailable) ? QString::fromStdString(nodes.values().value(row).second->pubKeyMasternode.GetHash().GetHex()) : "Not available"; + return mnWrapper.mnPubKey ? *mnWrapper.mnPubKey : "Not available"; case COLLATERAL_ID: - return (isAvailable) ? QString::fromStdString(rec->vin.prevout.hash.GetHex()) : "Not available"; + return mnWrapper.collateralId ? QString::fromStdString(mnWrapper.collateralId->hash.GetHex()) : "Not available"; case COLLATERAL_OUT_INDEX: - return (isAvailable) ? QString::number(rec->vin.prevout.n) : "Not available"; + return mnWrapper.collateralId ? QString::number(mnWrapper.collateralId->n) : "Not available"; case STATUS: { - std::pair pair = nodes.values().value(row); std::string status = "MISSING"; - if (pair.second) { - status = pair.second->Status(); + if (mnWrapper.masternode) { + status = mnWrapper.masternode->Status(); // Quick workaround to the current Masternode status types. // If the status is REMOVE and there is no pubkey associated to the Masternode // means that the MN is not in the network list and was created in // updateMNList(). Which.. denotes a not started masternode. // This will change in the future with the MasternodeWrapper introduction. - if (status == "REMOVE" && !pair.second->pubKeyCollateralAddress.IsValid()) { + if (status == "REMOVE" && !mnWrapper.masternode->pubKeyCollateralAddress.IsValid()) { return "MISSING"; } } return QString::fromStdString(status); } case PRIV_KEY: { - if (isAvailable) { - for (CMasternodeConfig::CMasternodeEntry mne : masternodeConfig.getEntries()) { - if (mne.getTxHash().compare(rec->vin.prevout.hash.GetHex()) == 0) { + if (mnWrapper.collateralId) { + for (const CMasternodeConfig::CMasternodeEntry& mne : masternodeConfig.getEntries()) { + if (mne.getTxHash() == mnWrapper.collateralId->hash.GetHex()) { return QString::fromStdString(mne.getPrivKey()); } } @@ -113,34 +112,18 @@ QVariant MNModel::data(const QModelIndex &index, int role) const return "Not available"; } case WAS_COLLATERAL_ACCEPTED:{ - return isAvailable && collateralTxAccepted.value(rec->vin.prevout.hash.GetHex()); + return mnWrapper.collateralId && collateralTxAccepted.value(mnWrapper.collateralId->hash.GetHex()); } } } return QVariant(); } -QModelIndex MNModel::index(int row, int column, const QModelIndex& parent) const -{ - Q_UNUSED(parent); - std::pair pair = nodes.values().value(row); - CMasternode* data = pair.second; - if (data) { - return createIndex(row, column, data); - } else if (!pair.first.isEmpty()) { - return createIndex(row, column, nullptr); - } else { - return QModelIndex(); - } -} - - bool MNModel::removeMn(const QModelIndex& modelIndex) { - QString alias = modelIndex.data(Qt::DisplayRole).toString(); int idx = modelIndex.row(); beginRemoveRows(QModelIndex(), idx, idx); - nodes.take(alias); + nodes.removeAt(idx); endRemoveRows(); Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, 5, QModelIndex()) ); return true; @@ -153,17 +136,35 @@ bool MNModel::addMn(CMasternodeConfig::CMasternodeEntry* mne) if (!mne->castOutputIndex(nIndex)) return false; - CMasternode* pmn = mnodeman.Find(COutPoint(uint256S(mne->getTxHash()), uint32_t(nIndex))); - nodes.insert(QString::fromStdString(mne->getAlias()), std::make_pair(QString::fromStdString(mne->getIp()), pmn)); + COutPoint collateralId = COutPoint(uint256S(mne->getTxHash()), uint32_t(nIndex)); + CMasternode* pmn = mnodeman.Find(collateralId); + nodes.append(MasternodeWrapper( + QString::fromStdString(mne->getAlias()), + QString::fromStdString(mne->getIp()), + pmn, pmn ? pmn->vin.prevout : collateralId, + Optional(QString::fromStdString(mne->getPubKeyStr()))) + ); endInsertRows(); return true; } +const MasternodeWrapper* MNModel::getMNWrapper(const QString& mnAlias) +{ + for (const auto& it : nodes) { + if (it.label == mnAlias) { + return ⁢ + } + } + return nullptr; +} + int MNModel::getMNState(const QString& mnAlias) { - QMap>::const_iterator it = nodes.find(mnAlias); - if (it != nodes.end()) return it.value().second->GetActiveState(); - throw std::runtime_error(std::string("Masternode alias not found")); + const MasternodeWrapper* mn = getMNWrapper(mnAlias); + if (!mn) { + throw std::runtime_error(std::string("Masternode alias not found")); + } + return mn->masternode ? mn->masternode->GetActiveState() : -1; } bool MNModel::isMNInactive(const QString& mnAlias) @@ -180,9 +181,11 @@ bool MNModel::isMNActive(const QString& mnAlias) bool MNModel::isMNCollateralMature(const QString& mnAlias) { - QMap>::const_iterator it = nodes.find(mnAlias); - if (it != nodes.end()) return collateralTxAccepted.value(it.value().second->vin.prevout.hash.GetHex()); - throw std::runtime_error(std::string("Masternode alias not found")); + const MasternodeWrapper* mn = getMNWrapper(mnAlias); + if (!mn) { + throw std::runtime_error(std::string("Masternode alias not found")); + } + return mn->collateralId && collateralTxAccepted.value(mn->collateralId->hash.GetHex()); } bool MNModel::isMNsNetworkSynced() @@ -319,6 +322,7 @@ CMasternodeConfig::CMasternodeEntry* MNModel::createLegacyMN(COutPoint& collater std::string& serviceAddr, const std::string& port, const std::string& mnKeyString, + const std::string& mnPubKeyStr, QString& ret_error) { // Update the conf file @@ -405,7 +409,7 @@ CMasternodeConfig::CMasternodeEntry* MNModel::createLegacyMN(COutPoint& collater fs::path pathNewConfFile = AbsPathForConfigVal(fs::path(strConfFile)); rename(pathConfigFile, pathNewConfFile); - auto ret_mn_entry = masternodeConfig.add(alias, serviceAddr+":"+port, mnKeyString, txID, indexOutStr); + auto ret_mn_entry = masternodeConfig.add(alias, serviceAddr+":"+port, mnKeyString, mnPubKeyStr, txID, indexOutStr); // Lock collateral output walletModel->lockCoin(collateralOut); diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index 05c1656cbd74e..0bbac39bf5b6b 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -6,10 +6,33 @@ #define MNMODEL_H #include +#include "amount.h" #include "masternodeconfig.h" -#include "qt/walletmodel.h" +#include "primitives/transaction.h" class CMasternode; +class WalletModel; + +class MasternodeWrapper +{ +public: + explicit MasternodeWrapper( + const QString& _label, + const QString& _ipPortStr, + CMasternode* _masternode, + COutPoint& _collateralId, + const Optional& _mnPubKey) : + label(_label), ipPort(_ipPortStr), masternode(_masternode), collateralId(_collateralId), mnPubKey(_mnPubKey) { }; + + QString label; + QString ipPort; + CMasternode* masternode{nullptr}; + // Cache collateral id and MN pk to be used if 'masternode' is null. + // (Denoting MNs that were not initialized on the conf file or removed from the network list) + // when masternode is not null, the collateralId is directly pointing to masternode.vin.prevout. + Optional collateralId{nullopt}; + Optional mnPubKey{nullopt}; +}; class MNModel : public QAbstractTableModel { @@ -34,13 +57,13 @@ class MNModel : public QAbstractTableModel COLLATERAL_ID = 6, COLLATERAL_OUT_INDEX = 7, PRIV_KEY = 8, - WAS_COLLATERAL_ACCEPTED = 9 + WAS_COLLATERAL_ACCEPTED = 9, + COLUMN_COUNT }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex& parent) const override; bool removeMn(const QModelIndex& index); bool addMn(CMasternodeConfig::CMasternodeEntry* entry); void updateMNList(); @@ -74,15 +97,18 @@ class MNModel : public QAbstractTableModel std::string& serviceAddr, const std::string& port, const std::string& mnKeyString, + const std::string& mnPubKeyStr, QString& ret_error); bool removeLegacyMN(const std::string& alias_to_remove, const std::string& tx_id, unsigned int out_index, QString& ret_error); private: - WalletModel* walletModel; - // alias mn node ---> pair - QMap> nodes; + WalletModel* walletModel{nullptr}; + // alias mn node ---> + QList nodes; QMap collateralTxAccepted; + + const MasternodeWrapper* getMNWrapper(const QString& mnAlias); }; #endif // MNMODEL_H From fce0cc0698dfa3276b156242cfb8b90bc261cffe Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 17 Jan 2022 21:50:42 -0300 Subject: [PATCH 02/82] Move FundSpecialTx function from rpcevo.cpp to specialtx_utils.cpp Decoupling the RPC server errors from the function. --- CMakeLists.txt | 1 + src/Makefile.am | 2 ++ src/evo/specialtx_utils.cpp | 44 +++++++++++++++++++++++++++ src/evo/specialtx_utils.h | 35 ++++++++++++++++++++++ src/rpc/rpcevo.cpp | 59 +++++++------------------------------ 5 files changed, 92 insertions(+), 49 deletions(-) create mode 100644 src/evo/specialtx_utils.cpp create mode 100644 src/evo/specialtx_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 918cbcdca2358..5e279164dbf2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,6 +417,7 @@ set(COMMON_SOURCES ./src/evo/evodb.cpp ./src/evo/providertx.cpp ./src/evo/specialtx_validation.cpp + ./src/evo/specialtx_utils.cpp ./src/llmq/quorums_blockprocessor.cpp ./src/llmq/quorums_commitment.cpp ./src/llmq/quorums_connections.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 717c3a95836af..2cf5c0cd38267 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -190,6 +190,7 @@ BITCOIN_CORE_H = \ evo/evonotificationinterface.h \ evo/providertx.h \ evo/specialtx_validation.h \ + evo/specialtx_utils.h \ flatdb.h \ llmq/quorums_blockprocessor.h \ llmq/quorums_commitment.h \ @@ -366,6 +367,7 @@ libbitcoin_server_a_SOURCES = \ evo/mnauth.cpp \ evo/providertx.cpp \ evo/specialtx_validation.cpp \ + evo/specialtx_utils.cpp \ llmq/quorums_blockprocessor.cpp \ llmq/quorums_commitment.cpp \ llmq/quorums_connections.cpp \ diff --git a/src/evo/specialtx_utils.cpp b/src/evo/specialtx_utils.cpp new file mode 100644 index 0000000000000..54c2b9ceae09c --- /dev/null +++ b/src/evo/specialtx_utils.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2022 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or htts://www.opensource.org/licenses/mit-license.php. + +#include "evo/specialtx_utils.h" + +#include "script/script.h" +#include "wallet/wallet.h" + +#ifdef ENABLE_WALLET + +OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx) +{ + static CTxOut dummyTxOut(0, CScript() << OP_RETURN); + std::vector vecSend; + bool dummyTxOutAdded = false; + + if (tx.vout.empty()) { + // add dummy txout as CreateTransaction requires at least one recipient + tx.vout.emplace_back(dummyTxOut); + dummyTxOutAdded = true; + } + + CAmount nFee; + CFeeRate feeRate = CFeeRate(0); + int nChangePos = -1; + std::string strFailReason; + std::set setSubtractFeeFromOutputs; + if (!pwallet->FundTransaction(tx, nFee, false, feeRate, nChangePos, strFailReason, false, false, {})) { + return {false, strFailReason}; + } + + if (dummyTxOutAdded && tx.vout.size() > 1) { + // FundTransaction added a change output, so we don't need the dummy txout anymore + // Removing it results in slight overpayment of fees, but we ignore this for now (as it's a very low amount) + auto it = std::find(tx.vout.begin(), tx.vout.end(), dummyTxOut); + assert(it != tx.vout.end()); + tx.vout.erase(it); + } + + return {true}; +} + +#endif \ No newline at end of file diff --git a/src/evo/specialtx_utils.h b/src/evo/specialtx_utils.h new file mode 100644 index 0000000000000..389bc22c1a59c --- /dev/null +++ b/src/evo/specialtx_utils.h @@ -0,0 +1,35 @@ +// Copyright (c) 2022 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or htts://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_SPECIALTX_UTILS_H +#define PIVX_SPECIALTX_UTILS_H + +#include "operationresult.h" +#include "primitives/transaction.h" + +template +static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload) +{ + payload.inputsHash = CalcTxInputsHash(tx); +} + +#ifdef ENABLE_WALLET + +class CWallet; +struct CMutableTransaction; + +OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx); + +template +OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx, SpecialTxPayload& payload) +{ + SetTxPayload(tx, payload); + auto res = FundSpecialTx(pwallet, tx); + if (res) UpdateSpecialTxInputsHash(tx, payload); + return res; +} + +#endif // ENABLE_WALLET + +#endif //PIVX_SPECIALTX_UTILS_H diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index b18ae85ad7bf9..7bca4e196315c 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -26,6 +26,7 @@ #ifdef ENABLE_WALLET #include "coincontrol.h" +#include "evo/specialtx_utils.h" #include "wallet/wallet.h" #include "wallet/rpcwallet.h" @@ -145,6 +146,10 @@ std::string GetHelpString(int nParamNum, ProRegParam p) return strprintf(it->second, nParamNum); } +void CheckOpResult(const OperationResult& res) { + if (!res) throw JSONRPCError(RPC_INTERNAL_ERROR, res.getError()); +} + #ifdef ENABLE_WALLET static CKey GetKeyFromWallet(CWallet* pwallet, const CKeyID& keyID) { @@ -260,50 +265,6 @@ static UniValue DmnToJson(const CDeterministicMNCPtr dmn) return ret; } -#ifdef ENABLE_WALLET - -template -static void FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx, SpecialTxPayload& payload) -{ - SetTxPayload(tx, payload); - - static CTxOut dummyTxOut(0, CScript() << OP_RETURN); - std::vector vecSend; - bool dummyTxOutAdded = false; - - if (tx.vout.empty()) { - // add dummy txout as CreateTransaction requires at least one recipient - tx.vout.emplace_back(dummyTxOut); - dummyTxOutAdded = true; - } - - CAmount nFee; - CFeeRate feeRate = CFeeRate(0); - int nChangePos = -1; - std::string strFailReason; - std::set setSubtractFeeFromOutputs; - if (!pwallet->FundTransaction(tx, nFee, false, feeRate, nChangePos, strFailReason, false, false, {})) - throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); - - if (dummyTxOutAdded && tx.vout.size() > 1) { - // FundTransaction added a change output, so we don't need the dummy txout anymore - // Removing it results in slight overpayment of fees, but we ignore this for now (as it's a very low amount) - auto it = std::find(tx.vout.begin(), tx.vout.end(), dummyTxOut); - assert(it != tx.vout.end()); - tx.vout.erase(it); - } - - UpdateSpecialTxInputsHash(tx, payload); -} - -#endif - -template -static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload) -{ - payload.inputsHash = CalcTxInputsHash(tx); -} - template static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key) { @@ -532,7 +493,7 @@ static UniValue ProTxRegister(const JSONRPCRequest& request, bool fSignAndSend) // make sure fee calculation works pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); - FundSpecialTx(pwallet, tx, pl); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); if (fSignAndSend) { SignSpecialTxPayloadByString(pl, keyCollateral); // prove we own the collateral @@ -656,7 +617,7 @@ UniValue protx_register_fund(const JSONRPCRequest& request) tx.nType = CTransaction::TxType::PROREG; tx.vout.emplace_back(collAmt, collateralScript); - FundSpecialTx(pwallet, tx, pl); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); for (uint32_t i = 0; i < tx.vout.size(); i++) { if (tx.vout[i].nValue == collAmt && tx.vout[i].scriptPubKey == collateralScript) { @@ -887,7 +848,7 @@ UniValue protx_update_service(const JSONRPCRequest& request) tx.nVersion = CTransaction::TxVersion::SAPLING; tx.nType = CTransaction::TxType::PROUPSERV; - FundSpecialTx(pwallet, tx, pl); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); SignSpecialTxPayloadByHash(tx, pl, operatorKey); return SignAndSendSpecialTx(pwallet, tx, pl); @@ -961,7 +922,7 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) // make sure fee calculation works pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); - FundSpecialTx(pwallet, tx, pl); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); SignSpecialTxPayloadByHash(tx, pl, ownerKey); return SignAndSendSpecialTx(pwallet, tx, pl); @@ -1027,7 +988,7 @@ UniValue protx_revoke(const JSONRPCRequest& request) tx.nVersion = CTransaction::TxVersion::SAPLING; tx.nType = CTransaction::TxType::PROUPREV; - FundSpecialTx(pwallet, tx, pl); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); SignSpecialTxPayloadByHash(tx, pl, operatorKey); return SignAndSendSpecialTx(pwallet, tx, pl); From 1514e4541ee22c72de496b6d52bfa9d04c183001 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 19 Jan 2022 15:48:54 -0300 Subject: [PATCH 03/82] Move CalcTxInputsHash from specialtx_validation.h to transaction.h And use the `PROTOCOL_VERSION` instead of the `CLIENT_VERSION`. Which should had never been used there as the serialization of inputs/spend_notes cannot be modified changing the `CLIENT_VERSION`, it's a hard-fork that needs a protocol bump. --- src/evo/specialtx_validation.cpp | 15 --------------- src/evo/specialtx_validation.h | 2 -- src/primitives/transaction.cpp | 16 ++++++++++++++++ src/primitives/transaction.h | 2 ++ 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/evo/specialtx_validation.cpp b/src/evo/specialtx_validation.cpp index 56b3691d4967d..fa4cb6a2103be 100644 --- a/src/evo/specialtx_validation.cpp +++ b/src/evo/specialtx_validation.cpp @@ -641,18 +641,3 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex) return true; } -uint256 CalcTxInputsHash(const CTransaction& tx) -{ - CHashWriter hw(CLIENT_VERSION, SER_GETHASH); - // transparent inputs - for (const CTxIn& in: tx.vin) { - hw << in.prevout; - } - // shield inputs - if (tx.hasSaplingData()) { - for (const SpendDescription& sd: tx.sapData->vShieldedSpend) { - hw << sd.nullifier; - } - } - return hw.GetHash(); -} diff --git a/src/evo/specialtx_validation.h b/src/evo/specialtx_validation.h index e06a41e33b847..eb49d2b0676e8 100644 --- a/src/evo/specialtx_validation.h +++ b/src/evo/specialtx_validation.h @@ -36,6 +36,4 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex); // Validate given LLMQ final commitment with the list at pindexQuorum bool VerifyLLMQCommitment(const llmq::CFinalCommitment& qfc, const CBlockIndex* pindexPrev, CValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -uint256 CalcTxInputsHash(const CTransaction& tx); - #endif // PIVX_SPECIALTX_H diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index e2f4d2e95c2fb..2f39b6a62dc41 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -224,3 +224,19 @@ std::string CTransaction::ToString() const ss << " " << out.ToString() << "\n"; return ss.str(); } + +uint256 CalcTxInputsHash(const CTransaction& tx) +{ + CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); + // transparent inputs + for (const CTxIn& in: tx.vin) { + hw << in.prevout; + } + // shield inputs + if (tx.hasSaplingData()) { + for (const SpendDescription& sd: tx.sapData->vShieldedSpend) { + hw << sd.nullifier; + } + } + return hw.GetHash(); +} diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 7177004657d54..a8832158e8398 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -488,4 +488,6 @@ void SetTxPayload(CMutableTransaction& tx, const T& payload) tx.extraPayload.emplace(ds.begin(), ds.end()); } +uint256 CalcTxInputsHash(const CTransaction& tx); + #endif // BITCOIN_PRIMITIVES_TRANSACTION_H From d8cedeea308591584d0c5b1a957a36a2141d9e20 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 21 Jan 2022 13:58:04 -0300 Subject: [PATCH 04/82] Move SignSpecialTxPayload* functions to specialtx_utils --- src/evo/specialtx_utils.cpp | 2 +- src/evo/specialtx_utils.h | 27 +++++++++++++++++++++++++ src/rpc/rpcevo.cpp | 39 ++++--------------------------------- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/evo/specialtx_utils.cpp b/src/evo/specialtx_utils.cpp index 54c2b9ceae09c..1d3a4cb498e37 100644 --- a/src/evo/specialtx_utils.cpp +++ b/src/evo/specialtx_utils.cpp @@ -41,4 +41,4 @@ OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx) return {true}; } -#endif \ No newline at end of file +#endif diff --git a/src/evo/specialtx_utils.h b/src/evo/specialtx_utils.h index 389bc22c1a59c..3c1a85aa3f7ad 100644 --- a/src/evo/specialtx_utils.h +++ b/src/evo/specialtx_utils.h @@ -5,9 +5,36 @@ #ifndef PIVX_SPECIALTX_UTILS_H #define PIVX_SPECIALTX_UTILS_H +#include "bls/bls_wrapper.h" +#include "messagesigner.h" #include "operationresult.h" #include "primitives/transaction.h" +template +OperationResult SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key) +{ + payload.vchSig.clear(); + uint256 hash = ::SerializeHash(payload); + return CHashSigner::SignHash(hash, key, payload.vchSig) ? OperationResult{true} : + OperationResult{false, "failed to sign special tx payload"}; +} + +template +OperationResult SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CBLSSecretKey& key) +{ + payload.sig = key.Sign(::SerializeHash(payload)); + return payload.sig.IsValid() ? OperationResult{true} : OperationResult{false, "failed to sign special tx payload"}; +} + +template +OperationResult SignSpecialTxPayloadByString(SpecialTxPayload& payload, const CKey& key) +{ + payload.vchSig.clear(); + std::string m = payload.MakeSignString(); + return CMessageSigner::SignMessage(m, payload.vchSig, key) ? OperationResult{true} : + OperationResult{false, "failed to sign special tx payload"}; +} + template static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload) { diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 7bca4e196315c..450d0b6db4821 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -265,37 +265,6 @@ static UniValue DmnToJson(const CDeterministicMNCPtr dmn) return ret; } -template -static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key) -{ - payload.vchSig.clear(); - - uint256 hash = ::SerializeHash(payload); - if (!CHashSigner::SignHash(hash, key, payload.vchSig)) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx payload"); - } -} - -template -static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CBLSSecretKey& key) -{ - payload.sig = key.Sign(::SerializeHash(payload)); - if (!payload.sig.IsValid()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx payload"); - } -} - -template -static void SignSpecialTxPayloadByString(SpecialTxPayload& payload, const CKey& key) -{ - payload.vchSig.clear(); - - std::string m = payload.MakeSignString(); - if (!CMessageSigner::SignMessage(m, payload.vchSig, key)) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx payload"); - } -} - static std::string TxInErrorToString(int i, const CTxIn& txin, const std::string& strError) { return strprintf("Input %d (%s): %s", i, txin.prevout.ToStringShort(), strError); @@ -496,7 +465,7 @@ static UniValue ProTxRegister(const JSONRPCRequest& request, bool fSignAndSend) CheckOpResult(FundSpecialTx(pwallet, tx, pl)); if (fSignAndSend) { - SignSpecialTxPayloadByString(pl, keyCollateral); // prove we own the collateral + CheckOpResult(SignSpecialTxPayloadByString(pl, keyCollateral)); // prove we own the collateral // check the payload, add the tx inputs sigs, and send the tx. return SignAndSendSpecialTx(pwallet, tx, pl); } @@ -849,7 +818,7 @@ UniValue protx_update_service(const JSONRPCRequest& request) tx.nType = CTransaction::TxType::PROUPSERV; CheckOpResult(FundSpecialTx(pwallet, tx, pl)); - SignSpecialTxPayloadByHash(tx, pl, operatorKey); + CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); return SignAndSendSpecialTx(pwallet, tx, pl); } @@ -923,7 +892,7 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) // make sure fee calculation works pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); CheckOpResult(FundSpecialTx(pwallet, tx, pl)); - SignSpecialTxPayloadByHash(tx, pl, ownerKey); + CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, ownerKey)); return SignAndSendSpecialTx(pwallet, tx, pl); } @@ -989,7 +958,7 @@ UniValue protx_revoke(const JSONRPCRequest& request) tx.nType = CTransaction::TxType::PROUPREV; CheckOpResult(FundSpecialTx(pwallet, tx, pl)); - SignSpecialTxPayloadByHash(tx, pl, operatorKey); + CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); return SignAndSendSpecialTx(pwallet, tx, pl); } From 9b035647bb9ad12853b9460a001a10dd7ff99a8f Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 21 Jan 2022 13:59:59 -0300 Subject: [PATCH 05/82] RPC: Remove extra special tx validation. Same validation is performed inside TryATMP. --- src/rpc/rpcevo.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 450d0b6db4821..724626db1bb13 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -299,12 +299,6 @@ static std::string SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransact { SetTxPayload(tx, pl); - CValidationState state; - CCoinsViewCache view(pcoinsTip.get()); - if (!WITH_LOCK(cs_main, return CheckSpecialTx(tx, GetChainTip(), &view, state); )) { - throw JSONRPCError(RPC_MISC_ERROR, FormatStateMessage(state)); - } - const OperationResult& sigRes = SignTransaction(pwallet, tx); if (!sigRes) { throw JSONRPCError(RPC_INTERNAL_ERROR, sigRes.getError()); From bc9e8c239fb1114e6ed729c3bc7107a7429d1203 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 21 Jan 2022 14:23:12 -0300 Subject: [PATCH 06/82] RPC: Commit special transaction inside the wallet before relay it to the network. The transaction inputs are always from the wallet. --- src/rpc/rpcevo.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 724626db1bb13..d3cdbe240a8f1 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -304,10 +304,13 @@ static std::string SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransact throw JSONRPCError(RPC_INTERNAL_ERROR, sigRes.getError()); } - TryATMP(tx, false); - const uint256& hashTx = tx.GetHash(); - RelayTx(hashTx); - return hashTx.GetHex(); + CWallet::CommitResult res = pwallet->CommitTransaction(MakeTransactionRef(tx),nullptr, g_connman.get(), nullptr); + CValidationState& state = res.state; + if (state.IsInvalid()) { + throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%s: %s", state.GetRejectReason(), state.GetDebugMessage())); + } + + return tx.GetHash().GetHex(); } // Parses inputs (starting from index paramIdx) and returns ProReg payload From 65b7816ba734df9eba8cba270b83f1631e9b8ddb Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 21 Jan 2022 14:46:49 -0300 Subject: [PATCH 07/82] Encapsulate SignTransaction inside the wallet class. And remove duplicated available inputs validation. The code will perform the same check few lines after the signature inside ATMP. --- src/rpc/rpcevo.cpp | 39 +++++---------------------------------- src/wallet/wallet.cpp | 33 ++++++++++++++++++++++++++++++--- src/wallet/wallet.h | 1 + 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index d3cdbe240a8f1..f94355847d206 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -265,43 +265,15 @@ static UniValue DmnToJson(const CDeterministicMNCPtr dmn) return ret; } -static std::string TxInErrorToString(int i, const CTxIn& txin, const std::string& strError) -{ - return strprintf("Input %d (%s): %s", i, txin.prevout.ToStringShort(), strError); -} - #ifdef ENABLE_WALLET -static OperationResult SignTransaction(CWallet* const pwallet, CMutableTransaction& tx) -{ - LOCK2(cs_main, pwallet->cs_wallet); - const CTransaction txConst(tx); - for (unsigned int i = 0; i < tx.vin.size(); i++) { - CTxIn& txin = tx.vin[i]; - const Coin& coin = pcoinsTip->AccessCoin(txin.prevout); - if (coin.IsSpent()) { - return errorOut(TxInErrorToString(i, txin, "not found or already spent")); - } - SigVersion sv = tx.GetRequiredSigVersion(); - txin.scriptSig.clear(); - SignatureData sigdata; - if (!ProduceSignature(MutableTransactionSignatureCreator(pwallet, &tx, i, coin.out.nValue, SIGHASH_ALL), - coin.out.scriptPubKey, sigdata, sv, false)) { - return errorOut(TxInErrorToString(i, txin, "signature failed")); - } - UpdateTransaction(tx, i, sigdata); - } - return OperationResult(true); -} - template static std::string SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransaction& tx, const SpecialTxPayload& pl) { SetTxPayload(tx, pl); - const OperationResult& sigRes = SignTransaction(pwallet, tx); - if (!sigRes) { - throw JSONRPCError(RPC_INTERNAL_ERROR, sigRes.getError()); + if (!pwallet->SignTransaction(tx)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "signature failed"); } CWallet::CommitResult res = pwallet->CommitTransaction(MakeTransactionRef(tx),nullptr, g_connman.get(), nullptr); @@ -433,10 +405,6 @@ static UniValue ProTxRegister(const JSONRPCRequest& request, bool fSignAndSend) pl.nVersion = ProRegPL::CURRENT_VERSION; pl.collateralOutpoint = COutPoint(collateralHash, (uint32_t)collateralIndex); - CMutableTransaction tx; - tx.nVersion = CTransaction::TxVersion::SAPLING; - tx.nType = CTransaction::TxType::PROREG; - // referencing unspent collateral outpoint Coin coin; if (!WITH_LOCK(cs_main, return pcoinsTip->GetUTXOCoin(pl.collateralOutpoint, coin); )) { @@ -459,6 +427,9 @@ static UniValue ProTxRegister(const JSONRPCRequest& request, bool fSignAndSend) // make sure fee calculation works pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); + CMutableTransaction tx; + tx.nVersion = CTransaction::TxVersion::SAPLING; + tx.nType = CTransaction::TxType::PROREG; CheckOpResult(FundSpecialTx(pwallet, tx, pl)); if (fSignAndSend) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c430877bfa44d..19fc21b4e25f9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2906,6 +2906,34 @@ bool CWallet::CreateBudgetFeeTX(CTransactionRef& tx, const uint256& hash, CReser return true; } +bool CWallet::SignTransaction(CMutableTransaction& tx) +{ + LOCK(cs_wallet); // mapWallet + + // sign the new tx + int nIn = 0; + for (auto& input : tx.vin) { + std::map::const_iterator mi = mapWallet.find(input.prevout.hash); + if (mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) { + return false; + } + const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey; + const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; + SignatureData sigdata; + + if (!ProduceSignature(MutableTransactionSignatureCreator(this, &tx, nIn, amount, SIGHASH_ALL), + scriptPubKey, + sigdata, + tx.GetRequiredSigVersion(), + false /* fColdStake */ )) { + return false; + } + UpdateTransaction(tx, nIn, sigdata); + nIn++; + } + return true; +} + bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set& setSubtractFeeFromOutputs, const CTxDestination& destChange) { std::vector vecSend; @@ -3204,7 +3232,6 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, } if (sign) { - CTransaction txNewConst(txNew); int nIn = 0; for (const auto& coin : setCoins) { const CScript& scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey; @@ -3212,10 +3239,10 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, bool haveKey = coin.first->GetStakeDelegationCredit() > 0; if (!ProduceSignature( - TransactionSignatureCreator(this, &txNewConst, nIn, coin.first->tx->vout[coin.second].nValue, SIGHASH_ALL), + MutableTransactionSignatureCreator(this, &txNew, nIn, coin.first->tx->vout[coin.second].nValue, SIGHASH_ALL), scriptPubKey, sigdata, - txNewConst.GetRequiredSigVersion(), + txNew.GetRequiredSigVersion(), !haveKey // fColdStake )) { strFailReason = _("Signing transaction failed"); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 94e27460a1aca..ba415fa573a4a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1056,6 +1056,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; CAmount GetLegacyBalance(const isminefilter& filter, int minDepth) const; + bool SignTransaction(CMutableTransaction& tx); bool FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set& setSubtractFeeFromOutputs, const CTxDestination& destChange = CNoDestination()); /** * Create a new transaction paying the recipients with a set of coins From 2abcf4ef90e052371bc54f225bdaf39c337b8049 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 21 Jan 2022 15:39:27 -0300 Subject: [PATCH 08/82] RPC: Move SignAndSendSpecialTx to specialtx_utils.h/cpp --- src/evo/specialtx_utils.cpp | 16 ++++++++++++++++ src/evo/specialtx_utils.h | 9 +++++++++ src/rpc/rpcevo.cpp | 36 ++++++++++++------------------------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/evo/specialtx_utils.cpp b/src/evo/specialtx_utils.cpp index 1d3a4cb498e37..3952ccd964969 100644 --- a/src/evo/specialtx_utils.cpp +++ b/src/evo/specialtx_utils.cpp @@ -4,6 +4,7 @@ #include "evo/specialtx_utils.h" +#include "net.h" #include "script/script.h" #include "wallet/wallet.h" @@ -41,4 +42,19 @@ OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx) return {true}; } +OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx) +{ + if (!pwallet->SignTransaction(tx)) { + return {false, "signature failed"}; + } + + CWallet::CommitResult res = pwallet->CommitTransaction(MakeTransactionRef(tx), nullptr, g_connman.get(), nullptr); + CValidationState& state = res.state; + if (state.IsInvalid()) { + return {false, strprintf("%s: %s", state.GetRejectReason(), state.GetDebugMessage())}; + } + + return {true}; +} + #endif diff --git a/src/evo/specialtx_utils.h b/src/evo/specialtx_utils.h index 3c1a85aa3f7ad..0ae914bec4ad8 100644 --- a/src/evo/specialtx_utils.h +++ b/src/evo/specialtx_utils.h @@ -57,6 +57,15 @@ OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx, Special return res; } +OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx); + +template +OperationResult SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransaction& tx, const SpecialTxPayload& pl) +{ + SetTxPayload(tx, pl); + return SignAndSendSpecialTx(pwallet, tx); +} + #endif // ENABLE_WALLET #endif //PIVX_SPECIALTX_UTILS_H diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index f94355847d206..d4f7ef93d9bea 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -267,24 +267,6 @@ static UniValue DmnToJson(const CDeterministicMNCPtr dmn) #ifdef ENABLE_WALLET -template -static std::string SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransaction& tx, const SpecialTxPayload& pl) -{ - SetTxPayload(tx, pl); - - if (!pwallet->SignTransaction(tx)) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "signature failed"); - } - - CWallet::CommitResult res = pwallet->CommitTransaction(MakeTransactionRef(tx),nullptr, g_connman.get(), nullptr); - CValidationState& state = res.state; - if (state.IsInvalid()) { - throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%s: %s", state.GetRejectReason(), state.GetDebugMessage())); - } - - return tx.GetHash().GetHex(); -} - // Parses inputs (starting from index paramIdx) and returns ProReg payload static ProRegPL ParseProRegPLParams(const UniValue& params, unsigned int paramIdx) { @@ -435,7 +417,8 @@ static UniValue ProTxRegister(const JSONRPCRequest& request, bool fSignAndSend) if (fSignAndSend) { CheckOpResult(SignSpecialTxPayloadByString(pl, keyCollateral)); // prove we own the collateral // check the payload, add the tx inputs sigs, and send the tx. - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + return tx.GetHash().GetHex(); } // external signing with collateral key pl.vchSig.clear(); @@ -504,7 +487,8 @@ UniValue protx_register_submit(const JSONRPCRequest& request) pl.vchSig = DecodeBase64(request.params[1].get_str().c_str()); // check the payload, add the tx inputs sigs, and send the tx. - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + return tx.GetHash().GetHex(); } UniValue protx_register_fund(const JSONRPCRequest& request) @@ -566,7 +550,8 @@ UniValue protx_register_fund(const JSONRPCRequest& request) // update payload on tx (with final collateral outpoint) pl.vchSig.clear(); // check the payload, add the tx inputs sigs, and send the tx. - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + return tx.GetHash().GetHex(); } #endif //ENABLE_WALLET @@ -788,7 +773,8 @@ UniValue protx_update_service(const JSONRPCRequest& request) CheckOpResult(FundSpecialTx(pwallet, tx, pl)); CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + return tx.GetHash().GetHex(); } UniValue protx_update_registrar(const JSONRPCRequest& request) @@ -862,7 +848,8 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) CheckOpResult(FundSpecialTx(pwallet, tx, pl)); CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, ownerKey)); - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + return tx.GetHash().GetHex(); } UniValue protx_revoke(const JSONRPCRequest& request) @@ -928,7 +915,8 @@ UniValue protx_revoke(const JSONRPCRequest& request) CheckOpResult(FundSpecialTx(pwallet, tx, pl)); CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + return tx.GetHash().GetHex(); } #endif From 28fd55f867f109a95d67a425790caca0235f57a3 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 23 Jan 2022 11:11:00 -0300 Subject: [PATCH 09/82] RPC: cleanup unneeded variable from parseProReg --- src/rpc/rpcevo.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index d4f7ef93d9bea..363c25fd09529 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -299,7 +299,6 @@ static ProRegPL ParseProRegPLParams(const UniValue& params, unsigned int paramId pl.scriptPayout = GetScriptForDestination(CTxDestination(ParsePubKeyIDFromAddress(strAddPayee))); // operator reward - pl.nOperatorReward = 0; if (params.size() > paramIdx + 5) { int64_t operReward = 0; if (!ParseFixedPoint(params[paramIdx + 5].getValStr(), 2, &operReward)) { From ade22167a3ef388b354d3956274d40d072cbd3cd Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 24 Jan 2022 11:33:33 -0300 Subject: [PATCH 10/82] chainparams: set v6.0 regtest activation height. --- src/chainparams.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 4081b5449055e..3529b4a3c19e1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -590,8 +590,7 @@ class CRegTestParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_0].nActivationHeight = 300; consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 300; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 251; - consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = - Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = 301; /** * The message start string is designed to be unlikely to occur in normal data. From 11c0f34c3731ea4c55ed59d63b607f9043211441 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 24 Jan 2022 12:49:11 -0300 Subject: [PATCH 11/82] Move ParsePubKeyIDFromAddress to specialtx_utils.h/cpp --- src/evo/specialtx_utils.cpp | 20 ++++++++++++++++++++ src/evo/specialtx_utils.h | 2 ++ src/rpc/rpcevo.cpp | 17 +++++------------ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/evo/specialtx_utils.cpp b/src/evo/specialtx_utils.cpp index 3952ccd964969..bafa32bfa8354 100644 --- a/src/evo/specialtx_utils.cpp +++ b/src/evo/specialtx_utils.cpp @@ -58,3 +58,23 @@ OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx) } #endif + +Optional ParsePubKeyIDFromAddress(const std::string& strAddress, std::string& strError) +{ + bool isStaking{false}, isShield{false}; + const CWDestination& cwdest = Standard::DecodeDestination(strAddress, isStaking, isShield); + if (isStaking) { + strError = "cold staking addresses not supported"; + return nullopt; + } + if (isShield) { + strError = "shield addresses not supported"; + return nullopt; + } + const CKeyID* keyID = boost::get(Standard::GetTransparentDestination(cwdest)); + if (!keyID) { + strError = strprintf("invalid PIVX address %s", strAddress); + return nullopt; + } + return *keyID; +} diff --git a/src/evo/specialtx_utils.h b/src/evo/specialtx_utils.h index 0ae914bec4ad8..ac607b1cdc480 100644 --- a/src/evo/specialtx_utils.h +++ b/src/evo/specialtx_utils.h @@ -68,4 +68,6 @@ OperationResult SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransaction #endif // ENABLE_WALLET +Optional ParsePubKeyIDFromAddress(const std::string& strAddress, std::string& strError); + #endif //PIVX_SPECIALTX_UTILS_H diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 363c25fd09529..0d173844eb774 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -203,19 +203,12 @@ static CKey ParsePrivKey(CWallet* pwallet, const std::string &strKeyOrAddress, b static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress) { - bool isStaking{false}, isShield{false}; - const CWDestination& cwdest = Standard::DecodeDestination(strAddress, isStaking, isShield); - if (isStaking) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cold staking addresses not supported"); - } - if (isShield) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "shield addresses not supported"); - } - const CKeyID* keyID = boost::get(Standard::GetTransparentDestination(cwdest)); - if (!keyID) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid PIVX address %s", strAddress)); + std::string strError; + auto ret = ParsePubKeyIDFromAddress(strAddress, strError); + if (!ret) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strError); } - return *keyID; + return *ret; } static CBLSPublicKey ParseBLSPubKey(const CChainParams& params, const std::string& strKey) From f55993bd0c74469164b4ff0c87cccba941d2cc7a Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 23 Jan 2022 11:13:06 -0300 Subject: [PATCH 12/82] GUI: implement basic create DMN process --- src/destination_io.cpp | 2 +- src/destination_io.h | 2 +- src/qt/pivx/masternodeswidget.cpp | 2 - src/qt/pivx/masternodewizarddialog.cpp | 70 ++++++++++---- src/qt/pivx/masternodewizarddialog.h | 1 + src/qt/pivx/mnmodel.cpp | 122 +++++++++++++++++++++++++ src/qt/pivx/mnmodel.h | 15 +++ 7 files changed, 192 insertions(+), 22 deletions(-) diff --git a/src/destination_io.cpp b/src/destination_io.cpp index 5917263f481f1..46a4957c6d148 100644 --- a/src/destination_io.cpp +++ b/src/destination_io.cpp @@ -74,7 +74,7 @@ Destination& Destination::operator=(const Destination& from) } // Returns the key ID if Destination is a transparent "regular" destination -const CKeyID* Destination::getKeyID() +const CKeyID* Destination::getKeyID() const { const CTxDestination* regDest = Standard::GetTransparentDestination(dest); return (regDest) ? boost::get(regDest) : nullptr; diff --git a/src/destination_io.h b/src/destination_io.h index dd1ead8ee4593..787b9a1808146 100644 --- a/src/destination_io.h +++ b/src/destination_io.h @@ -41,7 +41,7 @@ class Destination { Destination& operator=(const Destination& from); // Returns the key ID if Destination is a transparent "regular" destination - const CKeyID* getKeyID(); + const CKeyID* getKeyID() const; // Returns the encoded string address std::string ToString() const; }; diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 8782b9a79ac06..721f97c8dc6a2 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -370,8 +370,6 @@ void MasterNodesWidget::onCreateMNClicked() MasterNodeWizardDialog *dialog = new MasterNodeWizardDialog(walletModel, mnModel, window); if (openDialogWithOpaqueBackgroundY(dialog, window, 5, 7)) { if (dialog->isOk) { - // Update list - mnModel->addMn(dialog->mnEntry); updateListState(); // add mn inform(dialog->returnStr); diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 9c7b79030857c..aaf58291b99b3 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -199,12 +199,6 @@ bool MasterNodeWizardDialog::createMN() std::string ipAddress = addressStr.toStdString(); std::string port = portStr.toStdString(); - // create the mn key - CKey secret; - secret.MakeNewKey(false); - std::string mnKeyString = KeyIO::EncodeSecret(secret); - std::string mnPubKeyStr = secret.GetPubKey().GetHash().GetHex(); - // Look for a valid collateral utxo COutPoint collateralOut; @@ -212,12 +206,7 @@ bool MasterNodeWizardDialog::createMN() if (!walletModel->getMNCollateralCandidate(collateralOut)) { // New receive address auto r = walletModel->getNewAddress(alias); - if (!r) { - // generate address fail - returnStr = tr(r.getError().c_str()); - return false; - } - + if (!r) return errorOut(tr(r.getError().c_str())); if (!mnModel->createMNCollateral(addressLabel, QString::fromStdString(r.getObjResult()->ToString()), collateralOut, @@ -227,13 +216,52 @@ bool MasterNodeWizardDialog::createMN() } } - mnEntry = mnModel->createLegacyMN(collateralOut, alias, ipAddress, port, mnKeyString, mnPubKeyStr, returnStr); - if (!mnEntry) { - // error str set inside createLegacyMN - return false; + if (walletModel->isV6Enforced()) { + // Deterministic + + // For now, create every single key inside the wallet + // later this can be customized by the user. + + // Create owner addr + const auto r = walletModel->getNewAddress("dmn_owner_" + alias); + if (!r) return errorOut(tr(r.getError().c_str())); + const CKeyID* ownerKeyId = r.getObjResult()->getKeyID(); + + // Create payout addr + const auto payoutAddr = walletModel->getNewAddress("dmn_payout_" + alias); + if (!payoutAddr) return errorOut(tr(payoutAddr.getError().c_str())); + const std::string& payoutStr{payoutAddr.getObjResult()->ToString()}; + + // For now, collateral key is always inside the wallet + std::string error_str; + bool res = mnModel->createDMN(alias, + collateralOut, + ipAddress, + port, + ownerKeyId, + nullopt, // generate operator key + nullopt, // use owner key as voting key + {payoutStr}, // use owner key as payout script + error_str, + nullopt, // operator percentage + nullopt); // operator payout script + if (!res) { + return errorOut(tr(error_str.c_str())); + } + returnStr = tr("Deterministic Masternode created! It will appear on your list on the next mined block!"); + } else { + // Legacy + CKey secret; + secret.MakeNewKey(false); + std::string mnKeyString = KeyIO::EncodeSecret(secret); + std::string mnPubKeyStr = secret.GetPubKey().GetHash().GetHex(); + + mnEntry = mnModel->createLegacyMN(collateralOut, alias, ipAddress, port, mnKeyString, mnPubKeyStr, returnStr); + if (!mnEntry) return false; + // Update list + mnModel->addMn(mnEntry); + returnStr = tr("Masternode created! Wait %1 confirmations before starting it.").arg(mnModel->getMasternodeCollateralMinConf()); } - - returnStr = tr("Masternode created! Wait %1 confirmations before starting it.").arg(mnModel->getMasternodeCollateralMinConf()); return true; } @@ -278,6 +306,12 @@ void MasterNodeWizardDialog::inform(const QString& text) openDialog(snackBar, this); } +bool MasterNodeWizardDialog::errorOut(const QString& err) +{ + returnStr = err; + return false; +} + MasterNodeWizardDialog::~MasterNodeWizardDialog() { delete snackBar; diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index 899b34e400dfc..05bf2c5b64cd9 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -49,6 +49,7 @@ private Q_SLOTS: MNModel* mnModel{nullptr}; bool createMN(); void inform(const QString& text); + bool errorOut(const QString& err); }; #endif // MASTERNODEWIZARDDIALOG_H diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index cdf4a8b1c1b83..497c31b49c289 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -4,14 +4,18 @@ #include "qt/pivx/mnmodel.h" +#include "evo/specialtx_utils.h" #include "masternode.h" #include "masternodeman.h" #include "net.h" // for validateMasternodeIP +#include "netbase.h" +#include "operationresult.h" #include "tiertwo/tiertwo_sync_state.h" #include "uint256.h" #include "qt/bitcoinunits.h" #include "qt/optionsmodel.h" #include "qt/pivx/guitransactionsutils.h" +#include "wallet/wallet.h" // TODO: Move to walletModel #include "qt/walletmodel.h" #include "qt/walletmodeltransaction.h" @@ -208,6 +212,124 @@ int MNModel::getMasternodeCollateralMinConf() return Params().GetConsensus().MasternodeCollateralMinConf(); } +static OperationResult createDMNInternal(const COutPoint& collateral, + const CKey& keyCollateral, + const CService& service, + const CKeyID& ownerAddr, + const CBLSPublicKey& operatorPubKey, + const Optional& votingAddr, + const CKeyID& payoutAddr, + const Optional operatorPercentage, + const Optional& operatorPayoutAddr) +{ + ProRegPL pl; + pl.nVersion = ProRegPL::CURRENT_VERSION; + pl.addr = service; + pl.keyIDOwner = ownerAddr; + pl.pubKeyOperator = operatorPubKey; + pl.keyIDVoting = votingAddr ? *votingAddr : pl.keyIDOwner; + pl.collateralOutpoint = collateral; + pl.scriptPayout = GetScriptForDestination(payoutAddr); + if (operatorPercentage) { + pl.nOperatorReward = *operatorPercentage; + pl.scriptOperatorPayout = GetScriptForDestination(*operatorPayoutAddr); + } + // make sure fee calculation works + pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); + + CMutableTransaction tx; + tx.nVersion = CTransaction::TxVersion::SAPLING; + tx.nType = CTransaction::TxType::PROREG; + + auto wallet = vpwallets[0]; // TODO: Move to walletModel + auto res = FundSpecialTx(wallet, tx, pl); + if (!res) return res; + + res = SignSpecialTxPayloadByString(pl, keyCollateral); + if (!res) return res; + + return SignAndSendSpecialTx(wallet, tx, pl); +} + +bool MNModel::createDMN(const std::string& alias, + const COutPoint& collateral, + std::string& serviceAddr, + const std::string& servicePort, + const CKeyID* ownerAddr, + const Optional& operatorPubKey, + const Optional& votingAddr, + const Optional& payoutAddr, + std::string& strError, + const Optional& operatorPercentage, + const Optional& operatorPayoutAddr) +{ + // Parse and validate inputs + + // Different DMN creation types: + // 1. internal. + // 2. external. + // 3. fund. + + auto p_wallet = vpwallets[0]; // TODO: Move to walletModel + const auto& chainparams = Params(); + + // 1) Create the simplest DMN, the collateral was generated by this wallet. + + CService service; + if (!serviceAddr.empty()) { + if (!Lookup(serviceAddr+":"+servicePort, service, chainparams.GetDefaultPort(), false)) { + strError = strprintf("invalid network address %s", serviceAddr); + return false; + } + } + + // Parse payout script + CKeyID payoutKeyId; + if (payoutAddr) { + auto opPayout = ParsePubKeyIDFromAddress(*payoutAddr, strError); + if (!opPayout) return false; + payoutKeyId = *opPayout; + } + + CPubKey pubKeyCollateral; + CKey keyCollateral; + if (!p_wallet->GetMasternodeVinAndKeys(pubKeyCollateral, keyCollateral, collateral, false, strError)) { + return false; + } + + // parse operator pubkey or create one + Optional operatorSk{nullopt}; + CBLSPublicKey operatorPk; + if (operatorPubKey) { + if (!operatorPk.SetHexStr(*operatorPubKey)) { + strError = "invalid operator pubkey"; + return false; + } + } else { + // Stored in masternode conf locally as well + operatorSk = CBLSSecretKey(); + operatorSk->MakeNewKey(); + operatorPk = operatorSk->GetPublicKey(); + } + + auto res = createDMNInternal(collateral, + keyCollateral, + service, + *ownerAddr, + operatorPk, + Optional(*ownerAddr), // voting key + payoutKeyId, // payout script + nullopt, // operator percentage + nullopt); // operator percentage + if (!res) { + strError = res.getError(); + return false; + } + + // All good + return true; +} + bool MNModel::createMNCollateral( const QString& alias, const QString& addr, diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index 0bbac39bf5b6b..32f9262b335ea 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -85,6 +85,21 @@ class MNModel : public QAbstractTableModel CAmount getMNCollateralRequiredAmount(); // Return the specific chain min conf for the collateral tx int getMasternodeCollateralMinConf(); + + bool createDMN(const std::string& alias, + const COutPoint& collateral, + std::string& serviceAddr, + const std::string& servicePort, + const CKeyID* ownerAddr, + const Optional& operatorPubKey, + const Optional& votingAddr, + const Optional& payoutAddr, + std::string& strError, + const Optional& operatorPercentage = nullopt, + const Optional& operatorPayoutAddr = nullopt); + + + // Generates the collateral transaction bool createMNCollateral(const QString& alias, const QString& addr, COutPoint& ret_outpoint, QString& ret_error); // Creates the mnb and broadcast it to the network From 27c8d81e261653a5fee72824c0bbc906ca97afa9 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 26 Jan 2022 22:54:34 -0300 Subject: [PATCH 13/82] Create tiertwo interface, initializing and connecting the local DMNs cache. --- CMakeLists.txt | 1 + src/Makefile.am | 2 + src/evo/deterministicmns.cpp | 3 +- src/evo/deterministicmns.h | 3 +- src/interfaces/tiertwo.cpp | 113 +++++++++++++++++++++++++++++++++++ src/interfaces/tiertwo.h | 60 +++++++++++++++++++ src/tiertwo/init.cpp | 10 ++++ 7 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 src/interfaces/tiertwo.cpp create mode 100644 src/interfaces/tiertwo.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e279164dbf2f..77b93419f4b88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,6 +234,7 @@ set(SERVER_SOURCES ./src/tiertwo/init.cpp ./src/interfaces/handler.cpp ./src/interfaces/wallet.cpp + ./src/interfaces/tiertwo.cpp ./src/dbwrapper.cpp ./src/legacy/validation_zerocoin_legacy.cpp ./src/mapport.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 2cf5c0cd38267..360fa640d3d9a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -214,6 +214,7 @@ BITCOIN_CORE_H = \ init.h \ tiertwo/init.h \ interfaces/handler.h \ + interfaces/tiertwo.h \ interfaces/wallet.h \ invalid.h \ invalid_outpoints.json.h \ @@ -594,6 +595,7 @@ libbitcoin_util_a_SOURCES = \ compat/strnlen.cpp \ fs.cpp \ interfaces/handler.cpp \ + interfaces/tiertwo.cpp \ logging.cpp \ random.cpp \ randomenv.cpp \ diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 2f8bc2f0e3716..24aef42a6d1c1 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -7,7 +7,6 @@ #include "bls/key_io.h" #include "chain.h" -#include "coins.h" #include "chainparams.h" #include "consensus/upgrades.h" #include "consensus/validation.h" @@ -505,6 +504,7 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, const CBlockInde } diff.nHeight = pindex->nHeight; + diff.blockHash = pindex->GetBlockHash(); mnListDiffsCache.emplace(pindex->GetBlockHash(), diff); } catch (const std::exception& e) { LogPrintf("CDeterministicMNManager::%s -- internal error: %s\n", __func__, e.what()); @@ -888,6 +888,7 @@ CDeterministicMNList CDeterministicMNManager::GetListForBlock(const CBlockIndex* } diff.nHeight = pindex->nHeight; + diff.blockHash = pindex->GetBlockHash(); mnListDiffsCache.emplace(pindex->GetBlockHash(), std::move(diff)); listDiffIndexes.emplace_front(pindex); pindex = pindex->pprev; diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index f180294633c41..f5c2f913a14fd 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -495,7 +495,8 @@ class CDeterministicMNList class CDeterministicMNListDiff { public: - int nHeight{-1}; //memory only + int nHeight{-1}; // memory only + uint256 blockHash; // memory only std::vector addedMNs; // keys are all relating to the internalId of MNs diff --git a/src/interfaces/tiertwo.cpp b/src/interfaces/tiertwo.cpp new file mode 100644 index 0000000000000..69936050f5f26 --- /dev/null +++ b/src/interfaces/tiertwo.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2022 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "interfaces/tiertwo.h" +#include "evo/deterministicmns.h" +#include "optional.h" +#include "validation.h" +#include "wallet/wallet.h" + +namespace interfaces { + +std::unique_ptr g_tiertwo; + +std::shared_ptr createDMNViewIfMine(CWallet* pwallet, const CDeterministicMNCPtr& dmn) +{ + bool hasOwnerKey; + bool hasVotingKey; + bool hasPayoutScript; + Optional opOwnerLabel{nullopt}; + Optional opVotingLabel{nullopt}; + Optional opPayoutLabel{nullopt}; + { + LOCK(pwallet->cs_wallet); + hasOwnerKey = pwallet->HaveKey(dmn->pdmnState->keyIDOwner); + hasVotingKey = pwallet->HaveKey(dmn->pdmnState->keyIDVoting); + + CTxDestination dest; + if (ExtractDestination(dmn->pdmnState->scriptPayout, dest)) { + if (auto payoutId = boost::get(&dest)) { + hasPayoutScript = pwallet->HaveKey(*payoutId); + auto payoutLabel = pwallet->GetNameForAddressBookEntry(*payoutId); + if (!payoutLabel.empty()) opPayoutLabel = payoutLabel; + } + } + + auto ownerLabel = pwallet->GetNameForAddressBookEntry(dmn->pdmnState->keyIDOwner); + if (!ownerLabel.empty()) opOwnerLabel = ownerLabel; + + auto votingLabel = pwallet->GetNameForAddressBookEntry(dmn->pdmnState->keyIDVoting); + if (!votingLabel.empty()) opVotingLabel = votingLabel; + } + if (!hasOwnerKey && !hasVotingKey) return nullptr; + + DMNView dmnView; + dmnView.id = dmn->GetInternalId(); + dmnView.proTxHash = dmn->proTxHash; + dmnView.hasOwnerKey = hasOwnerKey; + dmnView.hasVotingKey = hasVotingKey; + dmnView.hasPayoutScript = hasPayoutScript; + dmnView.ownerAddrLabel = opOwnerLabel; + dmnView.votingAddrLabel = opVotingLabel; + dmnView.payoutAddrLabel = opPayoutLabel; + dmnView.isPoSeBanned = dmn->IsPoSeBanned(); + dmnView.service = dmn->pdmnState->addr.IsValid() ? dmn->pdmnState->addr.ToStringIPPort() : ""; + dmnView.collateralOut = dmn->collateralOutpoint; + return std::make_shared(dmnView); +} + +void TierTwo::refreshCache(const CDeterministicMNList& mnList) +{ + if (vpwallets.empty()) return; + CWallet* pwallet = vpwallets[0]; + std::vector> vec_dmns; + mnList.ForEachMN(false, [pwallet, &vec_dmns](const CDeterministicMNCPtr& dmn) { + auto opDMN = createDMNViewIfMine(pwallet, dmn); + if (opDMN) vec_dmns.emplace_back(opDMN); + }); + + LOCK(cs_cache); + m_cached_dmns = vec_dmns; + m_last_block_cached = mnList.GetBlockHash(); +} + +void TierTwo::init() +{ + // Init the DMNs cache + refreshCache(deterministicMNManager->GetListAtChainTip()); +} + +void TierTwo::NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) +{ + if (vpwallets.empty()) return; + // Refresh cache if reorg occurred + if (WITH_LOCK(cs_cache, return m_last_block_cached) != oldMNList.GetBlockHash()) { + refreshCache(oldMNList); + } + + CWallet* pwallet = vpwallets[0]; + LOCK (cs_cache); + + // Remove dmns + for (const auto& removed : diff.removedMns) { + auto it = m_cached_dmns.begin(); + while (it != m_cached_dmns.end()) { + if (it->get()->id == removed) it = m_cached_dmns.erase(it); + else it++; + } + } + + // Add dmns + for (const auto& add : diff.addedMNs) { + auto opDMN = createDMNViewIfMine(pwallet, add); + if (opDMN) m_cached_dmns.emplace_back(opDMN); + } + + // TODO: updated DMNs. + + // Update cached hash + m_last_block_cached = diff.blockHash; +} + +} // end namespace interfaces \ No newline at end of file diff --git a/src/interfaces/tiertwo.h b/src/interfaces/tiertwo.h new file mode 100644 index 0000000000000..64c688a7202a6 --- /dev/null +++ b/src/interfaces/tiertwo.h @@ -0,0 +1,60 @@ +// Copyright (c) 2022 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_INTERFACES_TIERTWO_H +#define PIVX_INTERFACES_TIERTWO_H + +#include "sync.h" +#include "uint256.h" +#include "validationinterface.h" + +#include + +class DMNView { +public: + uint64_t id; // DMN identifier + uint256 proTxHash; + bool hasOwnerKey{false}; + bool hasVotingKey{false}; + bool hasPayoutScript{false}; + bool isPoSeBanned{false}; + + Optional ownerAddrLabel{nullopt}; + Optional votingAddrLabel{nullopt}; + Optional payoutAddrLabel{nullopt}; + + std::string service; + + COutPoint collateralOut; +}; + +namespace interfaces { + +class TierTwo : public CValidationInterface { +// todo: Store a cache of the active DMNs that this wallet knows +// Update state via the validation interface signal MasternodesChanges etc.. +private: + RecursiveMutex cs_cache; + std::vector> m_cached_dmns GUARDED_BY(cs_cache); + uint256 m_last_block_cached GUARDED_BY(cs_cache); + + // Refresh the cached values from 'mnList' + void refreshCache(const CDeterministicMNList& mnList); +public: + // Initialize cache + void init(); + + // Return the DMNs that this wallet "owns". + // future: add filter to return by owner, operator, voter or a combination of them. + std::vector> getKnownDMNs() { return WITH_LOCK(cs_cache, return m_cached_dmns;); }; + + void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) override; +}; + +extern std::unique_ptr g_tiertwo; + +} // end namespace interfaces + + +#endif //PIVX_INTERFACES_TIERTWO_H diff --git a/src/tiertwo/init.cpp b/src/tiertwo/init.cpp index 6148b35cc3048..3c8333d4b0b57 100644 --- a/src/tiertwo/init.cpp +++ b/src/tiertwo/init.cpp @@ -10,6 +10,7 @@ #include "flatdb.h" #include "guiinterface.h" #include "guiinterfaceutil.h" +#include "interfaces/tiertwo.h" #include "masternodeman.h" #include "masternode-payments.h" #include "masternodeconfig.h" @@ -46,6 +47,9 @@ void InitTierTwoInterfaces() { pEvoNotificationInterface = std::make_unique(); RegisterValidationInterface(pEvoNotificationInterface.get()); + + interfaces::g_tiertwo = std::make_unique(); + RegisterValidationInterface(interfaces::g_tiertwo.get()); } void ResetTierTwoInterfaces() @@ -60,6 +64,11 @@ void ResetTierTwoInterfaces() delete activeMasternodeManager; activeMasternodeManager = nullptr; } + + if (interfaces::g_tiertwo) { + UnregisterValidationInterface(interfaces::g_tiertwo.get()); + interfaces::g_tiertwo.reset(); + } } void InitTierTwoPreChainLoad(bool fReindex) @@ -82,6 +91,7 @@ void InitTierTwoChainTip() // force UpdatedBlockTip to initialize nCachedBlockHeight for DS, MN payments and budgets // but don't call it directly to prevent triggering of other listeners like zmq etc. pEvoNotificationInterface->InitializeCurrentBlockTip(); + interfaces::g_tiertwo->init(); } // Sets the last CACHED_BLOCK_HASHES hashes into masternode manager cache From 80811739fec77ddfc1a63deee51815821c73f970 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 Jan 2022 16:34:27 -0300 Subject: [PATCH 14/82] GUI: Connect known DMN to the MN screen Differentiating deterministic from legacy masternodes --- src/Makefile.qt.include | 2 + src/qt/pivx.qrc | 2 + src/qt/pivx/forms/masternodeswidget.ui | 2 +- src/qt/pivx/forms/mnrow.ui | 28 +++++++ src/qt/pivx/masternodeswidget.cpp | 12 ++- src/qt/pivx/mnmodel.cpp | 103 +++++++++++++++++++++---- src/qt/pivx/mnmodel.h | 21 ++++- src/qt/pivx/mnrow.cpp | 8 +- src/qt/pivx/mnrow.h | 6 +- src/qt/pivx/res/img/ic-dmn.svg | 4 + src/qt/pivx/res/img/ic-lmn.svg | 4 + 11 files changed, 169 insertions(+), 23 deletions(-) create mode 100644 src/qt/pivx/res/img/ic-dmn.svg create mode 100644 src/qt/pivx/res/img/ic-lmn.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 58e9bdd99deb8..248a2ebac8fde 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -511,6 +511,8 @@ RES_ICONS = \ qt/pivx/res/img/ic-nav-governance-hover.svg \ qt/pivx/res/img/ic-time.svg \ qt/pivx/res/img/ic-link-hover.svg \ + qt/pivx/res/img/ic-dmn.svg \ + qt/pivx/res/img/ic-lmn.svg \ qt/pivx/res/img/img-empty-governance.svg \ qt/pivx/res/img/img-empty-dark-governance.svg diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index 2fce11e60dd5e..a1ae2f9045b89 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -237,5 +237,7 @@ pivx/res/img/img-empty-governance.svg pivx/res/img/img-empty-dark-governance.svg pivx/res/img/ic-check-block.svg + pivx/res/img/ic-dmn.svg + pivx/res/img/ic-lmn.svg diff --git a/src/qt/pivx/forms/masternodeswidget.ui b/src/qt/pivx/forms/masternodeswidget.ui index d5c980aaa8c4f..07c2e78bc4d64 100644 --- a/src/qt/pivx/forms/masternodeswidget.ui +++ b/src/qt/pivx/forms/masternodeswidget.ui @@ -105,7 +105,7 @@ and vote on the treasury system receiving a periodic reward. 20 - 40 + 30 diff --git a/src/qt/pivx/forms/mnrow.ui b/src/qt/pivx/forms/mnrow.ui index 43d31119fd8b9..02c5024b6679e 100644 --- a/src/qt/pivx/forms/mnrow.ui +++ b/src/qt/pivx/forms/mnrow.ui @@ -50,6 +50,34 @@ 0 + + + + + 32 + 32 + + + + + 36 + 36 + + + + Qt::NoFocus + + + + + + + 32 + 32 + + + + diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 721f97c8dc6a2..275d2c27f565c 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -39,7 +39,8 @@ class MNHolder : public FurListRow QString address = index.sibling(index.row(), MNModel::ADDRESS).data(Qt::DisplayRole).toString(); QString status = index.sibling(index.row(), MNModel::STATUS).data(Qt::DisplayRole).toString(); bool wasCollateralAccepted = index.sibling(index.row(), MNModel::WAS_COLLATERAL_ACCEPTED).data(Qt::DisplayRole).toBool(); - row->updateView("Address: " + address, label, status, wasCollateralAccepted); + uint16_t type = index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt(); + row->updateView("Address: " + address, label, status, wasCollateralAccepted, type); } QColor rectColor(bool isHovered, bool isSelected) override @@ -119,10 +120,15 @@ MasterNodesWidget::MasterNodesWidget(PIVXGUI *parent) : void MasterNodesWidget::showEvent(QShowEvent *event) { - if (mnModel) mnModel->updateMNList(); + if (!mnModel) return; + const auto& updateList = [&](){ + mnModel->updateMNList(); + updateListState(); + }; + updateList(); if (!timer) { timer = new QTimer(this); - connect(timer, &QTimer::timeout, [this]() {mnModel->updateMNList();}); + connect(timer, &QTimer::timeout, updateList); } timer->start(30000); } diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 497c31b49c289..308c492ba191d 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -4,6 +4,7 @@ #include "qt/pivx/mnmodel.h" +#include "interfaces/tiertwo.h" #include "evo/specialtx_utils.h" #include "masternode.h" #include "masternodeman.h" @@ -22,6 +23,25 @@ #include #include +uint16_t MasternodeWrapper::getType() const +{ + if (!dmnView) { + return LEGACY; + } + + uint16_t type = 0; + if (dmnView->hasOwnerKey) { + type |= DMN_OWNER; + } + + if (dmnView->hasVotingKey) { + type |= DMN_VOTER; + } + + // todo: add operator + return type; +} + MNModel::MNModel(QObject *parent) : QAbstractTableModel(parent) {} void MNModel::init() @@ -32,7 +52,6 @@ void MNModel::init() void MNModel::updateMNList() { int mnMinConf = getMasternodeCollateralMinConf(); - int end = nodes.size(); nodes.clear(); collateralTxAccepted.clear(); for (const CMasternodeConfig::CMasternodeEntry& mne : masternodeConfig.getEntries()) { @@ -46,14 +65,48 @@ void MNModel::updateMNList() QString::fromStdString(mne.getIp()), pmn, pmn ? pmn->vin.prevout : txIn.prevout, - Optional(QString::fromStdString(mne.getPubKeyStr()))) + Optional(QString::fromStdString(mne.getPubKeyStr())), + nullptr) // dmn view ); if (walletModel) { collateralTxAccepted.insert(mne.getTxHash(), walletModel->getWalletTxDepth(txHash) >= mnMinConf); } } - Q_EMIT dataChanged(index(0, 0, QModelIndex()), index(end, 5, QModelIndex()) ); + + // Now add DMNs + for (const auto& dmn : interfaces::g_tiertwo->getKnownDMNs()) { + // Try the owner address as "alias", if not found use the payout script, if not, use the voting address, if not use the service. + std::string alias; + if (dmn->hasOwnerKey && dmn->ownerAddrLabel) { + alias = *dmn->ownerAddrLabel; + } else if (dmn->hasPayoutScript && dmn->payoutAddrLabel) { + alias = *dmn->payoutAddrLabel; + } else if (dmn->hasVotingKey && dmn->votingAddrLabel) { + alias = *dmn->votingAddrLabel; + } else if (!dmn->service.empty()) { + alias = dmn->service; + } else { + // future think: could use the proTxHash if no label is found. + alias = "no alias available"; + } + + nodes.append(MasternodeWrapper( + QString::fromStdString(alias), + QString::fromStdString(dmn->service), + nullptr, + dmn->collateralOut, + nullopt, + dmn)); + + if (walletModel) { + const auto& txHash = dmn->collateralOut.hash; + collateralTxAccepted.insert(txHash.GetHex(), walletModel->getWalletTxDepth(txHash) >= mnMinConf); + } + } + + Q_EMIT dataChanged(index(0, 0, QModelIndex()), + index(nodes.size(), ColumnIndex::COLUMN_COUNT, QModelIndex())); } int MNModel::rowCount(const QModelIndex &parent) const @@ -70,6 +123,12 @@ int MNModel::columnCount(const QModelIndex &parent) const return ColumnIndex::COLUMN_COUNT; } +static QString formatTooltip(const MasternodeWrapper& wrapper) +{ + return QObject::tr((wrapper.getType() == MNViewType::LEGACY) ? + "Legacy Masternode\nIt will be disabled after v6.0 enforcement" : + "Deterministic Masternode"); +} QVariant MNModel::data(const QModelIndex &index, int role) const { @@ -78,7 +137,9 @@ QVariant MNModel::data(const QModelIndex &index, int role) const int row = index.row(); const MasternodeWrapper& mnWrapper = nodes.at(row); - if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: { switch (index.column()) { case ALIAS: return mnWrapper.label; @@ -92,15 +153,21 @@ QVariant MNModel::data(const QModelIndex &index, int role) const return mnWrapper.collateralId ? QString::number(mnWrapper.collateralId->n) : "Not available"; case STATUS: { std::string status = "MISSING"; - if (mnWrapper.masternode) { - status = mnWrapper.masternode->Status(); - // Quick workaround to the current Masternode status types. - // If the status is REMOVE and there is no pubkey associated to the Masternode - // means that the MN is not in the network list and was created in - // updateMNList(). Which.. denotes a not started masternode. - // This will change in the future with the MasternodeWrapper introduction. - if (status == "REMOVE" && !mnWrapper.masternode->pubKeyCollateralAddress.IsValid()) { - return "MISSING"; + if (mnWrapper.dmnView) { + // Deterministic MN + status = mnWrapper.dmnView->isPoSeBanned ? "PoSe BANNED" : "ENABLED"; + } else { + // Legacy MN + if (mnWrapper.masternode) { + status = mnWrapper.masternode->Status(); + // Quick workaround to the current Masternode status types. + // If the status is REMOVE and there is no pubkey associated to the Masternode + // means that the MN is not in the network list and was created in + // updateMNList(). Which.. denotes a not started masternode. + // This will change in the future with the MasternodeWrapper introduction. + if (status == "REMOVE" && !mnWrapper.masternode->pubKeyCollateralAddress.IsValid()) { + return "MISSING"; + } } } return QString::fromStdString(status); @@ -118,8 +185,14 @@ QVariant MNModel::data(const QModelIndex &index, int role) const case WAS_COLLATERAL_ACCEPTED:{ return mnWrapper.collateralId && collateralTxAccepted.value(mnWrapper.collateralId->hash.GetHex()); } + case TYPE:{ + return mnWrapper.getType(); + } } } + case Qt::ToolTipRole: + return formatTooltip(mnWrapper); + } // end role switch return QVariant(); } @@ -146,8 +219,8 @@ bool MNModel::addMn(CMasternodeConfig::CMasternodeEntry* mne) QString::fromStdString(mne->getAlias()), QString::fromStdString(mne->getIp()), pmn, pmn ? pmn->vin.prevout : collateralId, - Optional(QString::fromStdString(mne->getPubKeyStr()))) - ); + Optional(QString::fromStdString(mne->getPubKeyStr())), + nullptr)); endInsertRows(); return true; } diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index 32f9262b335ea..4a5117121e2c6 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -11,8 +11,17 @@ #include "primitives/transaction.h" class CMasternode; +class DMNView; class WalletModel; +enum MNViewType : uint16_t +{ + LEGACY = 0, + DMN_OWNER = (1 << 0), + DMN_OPERATOR = (1 << 1), + DMN_VOTER = (1 << 2) +}; + class MasternodeWrapper { public: @@ -21,8 +30,10 @@ class MasternodeWrapper const QString& _ipPortStr, CMasternode* _masternode, COutPoint& _collateralId, - const Optional& _mnPubKey) : - label(_label), ipPort(_ipPortStr), masternode(_masternode), collateralId(_collateralId), mnPubKey(_mnPubKey) { }; + const Optional& _mnPubKey, + const std::shared_ptr& _dmnView) : + label(_label), ipPort(_ipPortStr), masternode(_masternode), + collateralId(_collateralId), mnPubKey(_mnPubKey), dmnView(_dmnView) { }; QString label; QString ipPort; @@ -32,6 +43,11 @@ class MasternodeWrapper // when masternode is not null, the collateralId is directly pointing to masternode.vin.prevout. Optional collateralId{nullopt}; Optional mnPubKey{nullopt}; + + // DMN data + std::shared_ptr dmnView{nullptr}; + + uint16_t getType() const; }; class MNModel : public QAbstractTableModel @@ -58,6 +74,7 @@ class MNModel : public QAbstractTableModel COLLATERAL_OUT_INDEX = 7, PRIV_KEY = 8, WAS_COLLATERAL_ACCEPTED = 9, + TYPE = 10, /**< Whether is from a Legacy or Deterministic MN */ COLUMN_COUNT }; diff --git a/src/qt/pivx/mnrow.cpp b/src/qt/pivx/mnrow.cpp index d24982e3a4e5f..83861a7211e3d 100644 --- a/src/qt/pivx/mnrow.cpp +++ b/src/qt/pivx/mnrow.cpp @@ -4,6 +4,7 @@ #include "qt/pivx/mnrow.h" #include "qt/pivx/forms/ui_mnrow.h" +#include "qt/pivx/mnmodel.h" #include "qt/pivx/qtutils.h" MNRow::MNRow(QWidget *parent) : @@ -17,13 +18,18 @@ MNRow::MNRow(QWidget *parent) : ui->lblDivisory->setStyleSheet("background-color:#bababa;"); } -void MNRow::updateView(QString address, const QString& label, QString status, bool wasCollateralAccepted) +void MNRow::updateView(QString address, + const QString& label, + QString status, + bool wasCollateralAccepted, + uint16_t type) { ui->labelName->setText(label); address = address.size() < 40 ? address : address.left(20) + "..." + address.right(20); ui->labelAddress->setText(address); if (!wasCollateralAccepted) status = tr("Collateral tx not found"); ui->labelDate->setText(tr("Status: %1").arg(status)); + ui->btnIcon->setIcon(QIcon(type == MNViewType::LEGACY ? "://ic-lmn" : "://ic-dmn")); } MNRow::~MNRow() diff --git a/src/qt/pivx/mnrow.h b/src/qt/pivx/mnrow.h index eac8aa514908b..611599c20ffa8 100644 --- a/src/qt/pivx/mnrow.h +++ b/src/qt/pivx/mnrow.h @@ -19,7 +19,11 @@ class MNRow : public QWidget explicit MNRow(QWidget *parent = nullptr); ~MNRow(); - void updateView(QString address, const QString& label, QString status, bool wasCollateralAccepted); + void updateView(QString address, + const QString& label, + QString status, + bool wasCollateralAccepted, + uint16_t type); Q_SIGNALS: void onMenuClicked(); diff --git a/src/qt/pivx/res/img/ic-dmn.svg b/src/qt/pivx/res/img/ic-dmn.svg new file mode 100644 index 0000000000000..f7a7efa7a1b06 --- /dev/null +++ b/src/qt/pivx/res/img/ic-dmn.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/qt/pivx/res/img/ic-lmn.svg b/src/qt/pivx/res/img/ic-lmn.svg new file mode 100644 index 0000000000000..13d648e2098aa --- /dev/null +++ b/src/qt/pivx/res/img/ic-lmn.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file From bebc2cbe607efdd4b2f6e951ca9c468a7b0f218f Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 31 Jan 2022 10:23:53 -0300 Subject: [PATCH 15/82] GUI: Add DMN PoSe banned start. --- src/qt/pivx/masternodeswidget.cpp | 35 ++++++++++++++++++++----------- src/qt/pivx/mnmodel.cpp | 3 +++ src/qt/pivx/mnmodel.h | 1 + src/rpc/rpcevo.cpp | 4 ++-- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 275d2c27f565c..095c93d8d47a2 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -195,21 +195,32 @@ void MasterNodesWidget::onEditMNClicked() { if (walletModel) { if (!walletModel->isRegTestNetwork() && !checkMNsNetwork()) return; - if (index.sibling(index.row(), MNModel::WAS_COLLATERAL_ACCEPTED).data(Qt::DisplayRole).toBool()) { - // Start MN - QString strAlias = this->index.data(Qt::DisplayRole).toString(); - if (ask(tr("Start Masternode"), tr("Are you sure you want to start masternode %1?\n").arg(strAlias))) { - WalletModel::UnlockContext ctx(walletModel->requestUnlock()); - if (!ctx.isValid()) { - // Unlock wallet was cancelled - inform(tr("Cannot edit masternode, wallet locked")); - return; + uint16_t mnType = index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt(); + if (mnType == MNViewType::LEGACY) { + if (index.sibling(index.row(), MNModel::WAS_COLLATERAL_ACCEPTED).data(Qt::DisplayRole).toBool()) { + // Start MN + QString strAlias = this->index.data(Qt::DisplayRole).toString(); + if (ask(tr("Start Masternode"), tr("Are you sure you want to start masternode %1?\n").arg(strAlias))) { + WalletModel::UnlockContext ctx(walletModel->requestUnlock()); + if (!ctx.isValid()) { + // Unlock wallet was cancelled + inform(tr("Cannot edit masternode, wallet locked")); + return; + } + startAlias(strAlias); } - startAlias(strAlias); + } else { + inform(tr( + "Cannot start masternode, the collateral transaction has not been confirmed by the network yet.\n" + "Please wait few more minutes (masternode collaterals require %1 confirmations).").arg( + mnModel->getMasternodeCollateralMinConf())); } } else { - inform(tr("Cannot start masternode, the collateral transaction has not been confirmed by the network yet.\n" - "Please wait few more minutes (masternode collaterals require %1 confirmations).").arg(mnModel->getMasternodeCollateralMinConf())); + // Deterministic + bool isEnabled = index.sibling(index.row(), MNModel::IS_POSE_ENABLED).data(Qt::DisplayRole).toBool(); + if (isEnabled) { + inform(tr("Cannot start an already started Masternode")); + } } } } diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 308c492ba191d..4031403ce64fa 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -188,6 +188,9 @@ QVariant MNModel::data(const QModelIndex &index, int role) const case TYPE:{ return mnWrapper.getType(); } + case IS_POSE_ENABLED:{ + return mnWrapper.dmnView && !mnWrapper.dmnView->isPoSeBanned; + } } } case Qt::ToolTipRole: diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index 4a5117121e2c6..2de0bd04952fe 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -75,6 +75,7 @@ class MNModel : public QAbstractTableModel PRIV_KEY = 8, WAS_COLLATERAL_ACCEPTED = 9, TYPE = 10, /**< Whether is from a Legacy or Deterministic MN */ + IS_POSE_ENABLED = 11, /**< Whether the DMN is enabled or not*/ COLUMN_COUNT }; diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 0d173844eb774..a59c4ccf9e2c2 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -595,8 +595,8 @@ static void AddDMNEntryToList(UniValue& ret, CWallet* pwallet, const CDeterminis // No need to check wallet if not wallet_only and not verbose bool skipWalletCheck = !fFromWallet && !fVerbose; - if (pwallet && !skipWalletCheck) { - LOCK(pwallet->cs_wallet); + if (pwallet && !skipWalletCheck) { // TODO: First can only do the controller/owner DMNs, that is how the GUI works now.. + LOCK2(cs_main, pwallet->cs_wallet); // ERROR, this was locking cs_wallet first, then cs_main in GetTransaction. hasOwnerKey = pwallet->HaveKey(dmn->pdmnState->keyIDOwner); hasVotingKey = pwallet->HaveKey(dmn->pdmnState->keyIDVoting); ownsPayeeScript = CheckWalletOwnsScript(pwallet, dmn->pdmnState->scriptPayout); From 2afe8b334ea38321fe08a78494d5dd3e43e9ea46 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 31 Jan 2022 15:05:27 -0300 Subject: [PATCH 16/82] GUI: Generalize and simplify TooltipMenu --- src/qt/pivx/addresseswidget.cpp | 12 +-- src/qt/pivx/coldstakingwidget.cpp | 57 ++++++------- src/qt/pivx/governancewidget.cpp | 11 +-- src/qt/pivx/masternodeswidget.cpp | 25 +++--- src/qt/pivx/send.cpp | 34 ++++---- src/qt/pivx/send.h | 10 ++- src/qt/pivx/tooltipmenu.cpp | 136 +++++++++++++++--------------- src/qt/pivx/tooltipmenu.h | 56 ++++++------ 8 files changed, 167 insertions(+), 174 deletions(-) diff --git a/src/qt/pivx/addresseswidget.cpp b/src/qt/pivx/addresseswidget.cpp index eb6f4ccd3a64f..8848308678989 100644 --- a/src/qt/pivx/addresseswidget.cpp +++ b/src/qt/pivx/addresseswidget.cpp @@ -141,12 +141,12 @@ void AddressesWidget::handleAddressClicked(const QModelIndex& _index) QModelIndex rIndex = filter->mapToSource(_index); - if (!this->menu) { - this->menu = new TooltipMenu(window, this); - connect(this->menu, &TooltipMenu::message, this, &AddressesWidget::message); - connect(this->menu, &TooltipMenu::onEditClicked, this, &AddressesWidget::onEditClicked); - connect(this->menu, &TooltipMenu::onDeleteClicked, this, &AddressesWidget::onDeleteClicked); - connect(this->menu, &TooltipMenu::onCopyClicked, this, &AddressesWidget::onCopyClicked); + if (!menu) { + menu = new TooltipMenu(window, this); + connect(menu, &TooltipMenu::message, this, &AddressesWidget::message); + menu->addBtn(0, tr("Edit"), [this](){onEditClicked();}); + menu->addBtn(1, tr("Copy"), [this](){onCopyClicked();}); + menu->addBtn(2, tr("Delete"), [this](){onDeleteClicked();}); } else { this->menu->hide(); } diff --git a/src/qt/pivx/coldstakingwidget.cpp b/src/qt/pivx/coldstakingwidget.cpp index b1ed1d41a4963..0186e3d0edec8 100644 --- a/src/qt/pivx/coldstakingwidget.cpp +++ b/src/qt/pivx/coldstakingwidget.cpp @@ -602,13 +602,9 @@ void ColdStakingWidget::handleMyColdAddressClicked(const QModelIndex &_index) if (!menuAddresses) { menuAddresses = new TooltipMenu(window, this); - menuAddresses->setEditBtnText(tr("Copy")); - menuAddresses->setDeleteBtnText(tr("Edit")); - menuAddresses->setCopyBtnVisible(false); - menuAddresses->adjustSize(); connect(menuAddresses, &TooltipMenu::message, this, &AddressesWidget::message); - connect(menuAddresses, &TooltipMenu::onEditClicked, this, &ColdStakingWidget::onAddressCopyClicked); - connect(menuAddresses, &TooltipMenu::onDeleteClicked, this, &ColdStakingWidget::onAddressEditClicked); + menu->addBtn(0, tr("Copy"), [this](){onAddressCopyClicked();}); + menu->addBtn(1, tr("Edit"), [this](){onAddressEditClicked();}); } else { menuAddresses->hide(); } @@ -619,6 +615,13 @@ void ColdStakingWidget::handleMyColdAddressClicked(const QModelIndex &_index) menuAddresses->show(); } +enum SubMenuItems : uint8_t { + STAKE = 0, + BLACKLIST = 1, + EDIT = 2, + COPY = 3 +}; + void ColdStakingWidget::handleAddressClicked(const QModelIndex &rIndex) { bool isReceivedDelegation = rIndex.sibling(rIndex.row(), ColdStakingModel::IS_RECEIVED_DELEGATION).data(Qt::DisplayRole).toBool(); @@ -629,23 +632,17 @@ void ColdStakingWidget::handleAddressClicked(const QModelIndex &rIndex) pos.setX(pos.x() - (DECORATION_SIZE * 2)); pos.setY(pos.y() + (DECORATION_SIZE * 2)); - if (!this->menu) { - this->menu = new TooltipMenu(window, this); - this->menu->setEditBtnText(tr("Stake")); - this->menu->setDeleteBtnText(tr("Blacklist")); - this->menu->setCopyBtnText(tr("Edit Label")); - this->menu->setLastBtnText(tr("Copy owner\naddress"), 40); - this->menu->setLastBtnVisible(true); - this->menu->setMinimumHeight(157); - this->menu->setFixedHeight(157); - this->menu->setMinimumWidth(125); - connect(this->menu, &TooltipMenu::message, this, &AddressesWidget::message); - connect(this->menu, &TooltipMenu::onEditClicked, this, &ColdStakingWidget::onEditClicked); - connect(this->menu, &TooltipMenu::onDeleteClicked, this, &ColdStakingWidget::onDeleteClicked); - connect(this->menu, &TooltipMenu::onCopyClicked, [this](){onLabelClicked();}); - connect(this->menu, &TooltipMenu::onLastClicked, this, &ColdStakingWidget::onCopyOwnerClicked); + if (!menu) { + menu = new TooltipMenu(window, this); + menu->addBtn(SubMenuItems::STAKE, tr("Stake"), [this](){onEditClicked();}); + menu->addBtn(SubMenuItems::BLACKLIST, tr("Blacklist"), [this](){onDeleteClicked();}); + menu->addBtn(SubMenuItems::EDIT, tr("Edit Label"), [this](){onLabelClicked();}); + menu->addBtn(SubMenuItems::COPY, tr("Copy owner\naddress"), [this](){onCopyOwnerClicked();}); + menu->setMinimumHeight(157); + menu->setFixedHeight(157); + menu->setMinimumWidth(125); } else { - this->menu->hide(); + menu->hide(); } this->index = rIndex; @@ -653,16 +650,16 @@ void ColdStakingWidget::handleAddressClicked(const QModelIndex &rIndex) if (isReceivedDelegation) { bool isWhitelisted = rIndex.sibling(rIndex.row(), ColdStakingModel::IS_WHITELISTED).data( Qt::DisplayRole).toBool(); - this->menu->setDeleteBtnVisible(isWhitelisted); - this->menu->setEditBtnVisible(!isWhitelisted); - this->menu->setCopyBtnVisible(true); - this->menu->setMinimumHeight(157); + menu->showHideBtn(SubMenuItems::BLACKLIST, isWhitelisted); + menu->showHideBtn(SubMenuItems::STAKE, !isWhitelisted); + menu->showHideBtn(SubMenuItems::COPY, true); + menu->setMinimumHeight(157); } else { // owner side - this->menu->setDeleteBtnVisible(false); - this->menu->setEditBtnVisible(false); - this->menu->setCopyBtnVisible(false); - this->menu->setMinimumHeight(60); + menu->showHideBtn(SubMenuItems::BLACKLIST, false); + menu->showHideBtn(SubMenuItems::STAKE, false); + menu->showHideBtn(SubMenuItems::COPY, false); + menu->setMinimumHeight(60); } this->menu->adjustSize(); diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 07e8eacfca69e..1f8a3f3598719 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -188,15 +188,12 @@ void GovernanceWidget::onMenuClicked(ProposalCard* card) { if (!propMenu) { propMenu = new TooltipMenu(window, this); - propMenu->setCopyBtnText(tr("Copy Url")); - propMenu->setEditBtnText(tr("Open Url")); - propMenu->setDeleteBtnText(tr("More Info")); + connect(propMenu, &TooltipMenu::message, this, &GovernanceWidget::message); + propMenu->addBtn(0, tr("Copy Url"), [this](){onCopyUrl();}); + propMenu->addBtn(1, tr("Open Url"), [this](){onOpenClicked();}); + propMenu->addBtn(2, tr("More Info"), [this](){onMoreInfoClicked();}); propMenu->setMaximumWidth(propMenu->maximumWidth() + 5); propMenu->setFixedWidth(propMenu->width() + 5); - connect(propMenu, &TooltipMenu::message, this, &GovernanceWidget::message); - connect(propMenu, &TooltipMenu::onCopyClicked, this, &GovernanceWidget::onCopyUrl); - connect(propMenu, &TooltipMenu::onEditClicked, this, &GovernanceWidget::onOpenClicked); - connect(propMenu, &TooltipMenu::onDeleteClicked, this, &GovernanceWidget::onMoreInfoClicked); } else { propMenu->hide(); } diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 095c93d8d47a2..9e1505bec8e20 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -159,22 +159,19 @@ void MasterNodesWidget::onMNClicked(const QModelIndex& _index) ui->listMn->setCurrentIndex(_index); QRect rect = ui->listMn->visualRect(_index); QPoint pos = rect.topRight(); - pos.setX(pos.x() - (DECORATION_SIZE * 2)); - pos.setY(pos.y() + (DECORATION_SIZE * 1.5)); - if (!this->menu) { - this->menu = new TooltipMenu(window, this); - this->menu->setEditBtnText(tr("Start")); - this->menu->setDeleteBtnText(tr("Delete")); - this->menu->setCopyBtnText(tr("Info")); - connect(this->menu, &TooltipMenu::message, this, &AddressesWidget::message); - connect(this->menu, &TooltipMenu::onEditClicked, this, &MasterNodesWidget::onEditMNClicked); - connect(this->menu, &TooltipMenu::onDeleteClicked, this, &MasterNodesWidget::onDeleteMNClicked); - connect(this->menu, &TooltipMenu::onCopyClicked, this, &MasterNodesWidget::onInfoMNClicked); - this->menu->adjustSize(); + pos.setX((int) pos.x() - (DECORATION_SIZE * 2)); + pos.setY((int) pos.y() + (DECORATION_SIZE * 1.5)); + if (!menu) { + menu = new TooltipMenu(window, this); + connect(menu, &TooltipMenu::message, this, &AddressesWidget::message); + menu->addBtn(0, tr("Start"), [this](){onEditMNClicked();}); + menu->addBtn(1, tr("Delete"), [this](){onDeleteMNClicked();}); + menu->addBtn(2, tr("Info"), [this](){onInfoMNClicked();}); + menu->adjustSize(); } else { - this->menu->hide(); + menu->hide(); } - this->index = _index; + index = _index; menu->move(pos); menu->show(); diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index f5454a7cf5e23..4d026b08df2c6 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -846,6 +846,12 @@ void SendWidget::onContactsClicked(SendMultiRow* entry) menuContacts->show(); } +// SubMenu items +const uint8_t SUBMENU_MEMO = 0; +const uint8_t SUBMENU_SAVE = 1; +const uint8_t SUBMENU_FEE = 2; +const uint8_t SUBMENU_DELETE = 3; + void SendWidget::onMenuClicked(SendMultiRow* entry) { focusedEntry = entry; @@ -856,23 +862,19 @@ void SendWidget::onMenuClicked(SendMultiRow* entry) pos.setX(pos.x() + (entry->width() - entry->getMenuBtnWidth())); pos.setY(pos.y() + entry->height() + (entry->getMenuBtnWidth())); - if (!this->menu) { - this->menu = new TooltipMenu(window, this); - this->menu->setCopyBtnText(tr("Add Memo")); - this->menu->setEditBtnText(tr("Save contact")); - this->menu->setLastBtnVisible(true); - this->menu->setLastBtnText(tr("Subtract fee")); - this->menu->setMinimumHeight(157); - this->menu->setMinimumSize(this->menu->width() + 30, this->menu->height()); - connect(this->menu, &TooltipMenu::message, this, &AddressesWidget::message); - connect(this->menu, &TooltipMenu::onEditClicked, this, &SendWidget::onContactMultiClicked); - connect(this->menu, &TooltipMenu::onDeleteClicked, this, &SendWidget::onDeleteClicked); - connect(this->menu, &TooltipMenu::onCopyClicked, this, &SendWidget::onEntryMemoClicked); - connect(this->menu, &TooltipMenu::onLastClicked, this, &SendWidget::onSubtractFeeFromAmountChecked); + if (!menu) { + menu = new TooltipMenu(window, this); + menu->addBtn(SUBMENU_MEMO, tr("Add Memo"), [this](){onEntryMemoClicked();}); + menu->addBtn(SUBMENU_SAVE, tr("Save contact"), [this](){onContactMultiClicked();}); + menu->addBtn(SUBMENU_FEE, tr("Subtract fee"), [this](){onSubtractFeeFromAmountChecked();}); + menu->addBtn(SUBMENU_DELETE, tr("Delete"), [this](){onDeleteClicked();}); + menu->setMinimumHeight(157); + menu->setMinimumSize(menu->width() + 30, menu->height()); + connect(menu, &TooltipMenu::message, this, &AddressesWidget::message); } else { - this->menu->hide(); + menu->hide(); } - this->menu->setLastBtnCheckable(true, entry->getSubtractFeeFromAmount()); + menu->setBtnCheckable(SUBMENU_FEE, true, entry->getSubtractFeeFromAmount()); menu->move(pos); menu->show(); } @@ -930,7 +932,7 @@ void SendWidget::onEntryMemoClicked() { if (focusedEntry) { focusedEntry->launchMemoDialog(); - menu->setCopyBtnText(tr("Memo")); + menu->updateLabelForBtn(SUBMENU_MEMO, tr("Memo")); } } diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index 6cf8d6129e919..fc037311704c7 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -78,10 +78,6 @@ private Q_SLOTS: void clearEntries(); void clearAll(bool fClearSettings = true); void onCheckBoxChanged(); - void onContactMultiClicked(); - void onDeleteClicked(); - void onEntryMemoClicked(); - void onSubtractFeeFromAmountChecked(); void onResetCustomOptions(bool fRefreshAmounts); void onResetSettings(); @@ -128,6 +124,12 @@ private Q_SLOTS: void resetChangeAddress(); void hideContactsMenu(); void tryRefreshAmounts(); + + // Menu + void onContactMultiClicked(); + void onDeleteClicked(); + void onEntryMemoClicked(); + void onSubtractFeeFromAmountChecked(); }; #endif // SEND_H diff --git a/src/qt/pivx/tooltipmenu.cpp b/src/qt/pivx/tooltipmenu.cpp index 704b583c04383..dfdf36ba8b7a8 100644 --- a/src/qt/pivx/tooltipmenu.cpp +++ b/src/qt/pivx/tooltipmenu.cpp @@ -1,91 +1,87 @@ -// Copyright (c) 2019 The PIVX developers +// Copyright (c) 2019-2022 The PIVX developers // Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or https://www.opensource.org/licenses/mit-license.php. #include "qt/pivx/tooltipmenu.h" -#include "qt/pivx/forms/ui_tooltipmenu.h" #include "qt/pivx/qtutils.h" #include -TooltipMenu::TooltipMenu(PIVXGUI *_window, QWidget *parent) : - PWidget(_window, parent), - ui(new Ui::TooltipMenu) +TooltipMenu::TooltipMenu(PIVXGUI* _window, QWidget* parent) : + PWidget(_window, parent) { - ui->setupUi(this); - ui->btnLast->setVisible(false); - setCssProperty(ui->container, "container-list-menu"); - setCssProperty({ui->btnCopy, ui->btnDelete, ui->btnEdit, ui->btnLast}, "btn-list-menu"); - connect(ui->btnCopy, &QPushButton::clicked, this, &TooltipMenu::copyClicked); - connect(ui->btnDelete, &QPushButton::clicked, this, &TooltipMenu::deleteClicked); - connect(ui->btnEdit, &QPushButton::clicked, this, &TooltipMenu::editClicked); - connect(ui->btnLast, &QPushButton::clicked, this, &TooltipMenu::lastClicked); + setupUi(); + setCssProperty(container, "container-list-menu"); } -void TooltipMenu::setEditBtnText(const QString& btnText){ - ui->btnEdit->setText(btnText); -} - -void TooltipMenu::setDeleteBtnText(const QString& btnText){ - ui->btnDelete->setText(btnText); -} - -void TooltipMenu::setCopyBtnText(const QString& btnText){ - ui->btnCopy->setText(btnText); -} - -void TooltipMenu::setLastBtnText(const QString& btnText, int minHeight){ - ui->btnLast->setText(btnText); - ui->btnLast->setMinimumHeight(minHeight); -} - -void TooltipMenu::setLastBtnCheckable(bool checkable, bool isChecked) +QPushButton* TooltipMenu::createBtn(const QString& label) { - ui->btnLast->setCheckable(checkable); - ui->btnLast->setChecked(isChecked); -} - -void TooltipMenu::setCopyBtnVisible(bool visible){ - ui->btnCopy->setVisible(visible); -} - -void TooltipMenu::setDeleteBtnVisible(bool visible){ - ui->btnDelete->setVisible(visible); -} - -void TooltipMenu::setEditBtnVisible(bool visible) { - ui->btnEdit->setVisible(visible); -} - -void TooltipMenu::setLastBtnVisible(bool visible) { - ui->btnLast->setVisible(visible); -} - -void TooltipMenu::deleteClicked(){ - hide(); - Q_EMIT onDeleteClicked(); + auto* btn = new QPushButton(container); + QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(btn->sizePolicy().hasHeightForWidth()); + btn->setSizePolicy(sizePolicy); + btn->setMinimumSize(QSize(0, 30)); + btn->setMaximumSize(QSize(16777215, 30)); + btn->setFocusPolicy(Qt::NoFocus); + btn->setText(label); + setCssProperty({btn}, "btn-list-menu"); + return btn; +} + +void TooltipMenu::setupUi() +{ + auto* parent = dynamic_cast(this); + if (parent->objectName().isEmpty()) + parent->setObjectName(QStringLiteral("TooltipMenu")); + parent->resize(90, 110); + parent->setMinimumSize(QSize(90, 110)); + parent->setMaximumSize(QSize(16777215, 140)); + containerLayout = new QVBoxLayout(parent); + containerLayout->setSpacing(0); + containerLayout->setObjectName(QStringLiteral("containerLayout")); + containerLayout->setContentsMargins(0, 0, 0, 0); + container = new QWidget(parent); + container->setObjectName(QStringLiteral("container")); + verticalLayoutBtns = new QVBoxLayout(container); + verticalLayoutBtns->setSpacing(0); + verticalLayoutBtns->setObjectName(QStringLiteral("verticalLayoutBtns")); + verticalLayoutBtns->setContentsMargins(10, 0, 0, 0); + containerLayout->addWidget(container); +} + +void TooltipMenu::showHideBtn(uint8_t id, bool show) +{ + auto it = mapBtns.find(id); + if (it != mapBtns.end()) { + it.value()->setVisible(show); + } } -void TooltipMenu::copyClicked(){ - hide(); - Q_EMIT onCopyClicked(); +void TooltipMenu::updateLabelForBtn(uint8_t id, const QString& btnLabel) +{ + auto it = mapBtns.find(id); + if (it != mapBtns.end()) { + it.value()->setText(btnLabel); + } } -void TooltipMenu::editClicked(){ - hide(); - Q_EMIT onEditClicked(); +void TooltipMenu::setBtnCheckable(uint8_t id, bool checkable, bool isChecked) +{ + auto it = mapBtns.find(id); + if (it != mapBtns.end()) { + auto btn = it.value(); + btn->setCheckable(checkable); + btn->setChecked(isChecked); + } } -void TooltipMenu::lastClicked() { - hide(); - Q_EMIT onLastClicked(); +void TooltipMenu::hideTooltip() +{ + dynamic_cast(this)->hide(); } void TooltipMenu::showEvent(QShowEvent *event){ - QTimer::singleShot(5000, this, &TooltipMenu::hide); -} - -TooltipMenu::~TooltipMenu() -{ - delete ui; + QTimer::singleShot(5000, this, &TooltipMenu::hideTooltip); } diff --git a/src/qt/pivx/tooltipmenu.h b/src/qt/pivx/tooltipmenu.h index 88be0ee3d9dfb..667355b9d7f6f 100644 --- a/src/qt/pivx/tooltipmenu.h +++ b/src/qt/pivx/tooltipmenu.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019 The PIVX developers +// Copyright (c) 2019-2022 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -6,16 +6,17 @@ #define TOOLTIPMENU_H #include "qt/pivx/pwidget.h" -#include +#include +#include +#include #include +#include +#include +#include class PIVXGUI; class WalletModel; -namespace Ui { -class TooltipMenu; -} - QT_BEGIN_NAMESPACE class QModelIndex; QT_END_NAMESPACE @@ -26,35 +27,36 @@ class TooltipMenu : public PWidget public: explicit TooltipMenu(PIVXGUI* _window, QWidget *parent = nullptr); - ~TooltipMenu() override; - virtual void showEvent(QShowEvent *event) override; + void showEvent(QShowEvent *event) override; - void setEditBtnText(const QString& btnText); - void setDeleteBtnText(const QString& btnText); - void setCopyBtnText(const QString& btnText); - void setLastBtnText(const QString& btnText, int minHeight = 30); - void setCopyBtnVisible(bool visible); - void setDeleteBtnVisible(bool visible); - void setEditBtnVisible(bool visible); - void setLastBtnVisible(bool visible); - void setLastBtnCheckable(bool checkable, bool isChecked); + template + void addBtn(uint8_t id, const QString& label, Func func) { + QPushButton* btn = createBtn(label); + connect(btn, &QPushButton::clicked, [this, func](){func(); hideTooltip();}); + verticalLayoutBtns->addWidget(btn, 0, Qt::AlignLeft); + mapBtns.insert(id, btn); + } + void showHideBtn(uint8_t id, bool show); + void updateLabelForBtn(uint8_t id, const QString& newLabel); + void setBtnCheckable(uint8_t id, bool checkable, bool isChecked); Q_SIGNALS: - void onDeleteClicked(); - void onCopyClicked(); - void onEditClicked(); - void onLastClicked(); + void onBtnClicked(const QString& btnLabel); -private Q_SLOTS: - void deleteClicked(); - void copyClicked(); - void editClicked(); - void lastClicked(); +public Q_SLOTS: + void hideTooltip(); private: - Ui::TooltipMenu *ui; QModelIndex index; + QWidget* container{nullptr}; + QVBoxLayout* containerLayout{nullptr}; + QVBoxLayout* verticalLayoutBtns{nullptr}; + QMap mapBtns{}; + + void setupUi(); + + QPushButton* createBtn(const QString& label); }; #endif // TOOLTIPMENU_H From 9b03dd46bf2a34fe7b89ba23324c0db157565fd5 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 31 Jan 2022 16:51:33 -0300 Subject: [PATCH 17/82] Store DMN operator sk within the dmn registration tx (if the key was created locally). Only useful when the owner generates the operator key, so the object can be securely stored inside the wallet.dat encrypted. A future work could create an encryptable db for this specific object. --- src/evo/specialtx_utils.cpp | 4 ++-- src/evo/specialtx_utils.h | 6 +++--- src/qt/pivx/mnmodel.cpp | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/evo/specialtx_utils.cpp b/src/evo/specialtx_utils.cpp index bafa32bfa8354..0f678fe40e2ad 100644 --- a/src/evo/specialtx_utils.cpp +++ b/src/evo/specialtx_utils.cpp @@ -42,13 +42,13 @@ OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx) return {true}; } -OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx) +OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx, std::map* extras) { if (!pwallet->SignTransaction(tx)) { return {false, "signature failed"}; } - CWallet::CommitResult res = pwallet->CommitTransaction(MakeTransactionRef(tx), nullptr, g_connman.get(), nullptr); + CWallet::CommitResult res = pwallet->CommitTransaction(MakeTransactionRef(tx), nullptr, g_connman.get(), extras); CValidationState& state = res.state; if (state.IsInvalid()) { return {false, strprintf("%s: %s", state.GetRejectReason(), state.GetDebugMessage())}; diff --git a/src/evo/specialtx_utils.h b/src/evo/specialtx_utils.h index ac607b1cdc480..ace08fded90ec 100644 --- a/src/evo/specialtx_utils.h +++ b/src/evo/specialtx_utils.h @@ -57,13 +57,13 @@ OperationResult FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx, Special return res; } -OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx); +OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx, std::map* extras = nullptr); template -OperationResult SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransaction& tx, const SpecialTxPayload& pl) +OperationResult SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransaction& tx, const SpecialTxPayload& pl, std::map* extras = nullptr) { SetTxPayload(tx, pl); - return SignAndSendSpecialTx(pwallet, tx); + return SignAndSendSpecialTx(pwallet, tx, extras); } #endif // ENABLE_WALLET diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 4031403ce64fa..8974f7fb49066 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -4,6 +4,7 @@ #include "qt/pivx/mnmodel.h" +#include "bls/key_io.h" #include "interfaces/tiertwo.h" #include "evo/specialtx_utils.h" #include "masternode.h" @@ -295,7 +296,8 @@ static OperationResult createDMNInternal(const COutPoint& collateral, const CBLSPublicKey& operatorPubKey, const Optional& votingAddr, const CKeyID& payoutAddr, - const Optional operatorPercentage, + const Optional& operatorSk, + const Optional& operatorPercentage, const Optional& operatorPayoutAddr) { ProRegPL pl; @@ -324,7 +326,12 @@ static OperationResult createDMNInternal(const COutPoint& collateral, res = SignSpecialTxPayloadByString(pl, keyCollateral); if (!res) return res; - return SignAndSendSpecialTx(wallet, tx, pl); + std::map extraValues; + if (operatorSk) { + // Only if the operator sk was provided + extraValues.emplace("operatorSk", bls::EncodeSecret(Params(), *operatorSk)); + } + return SignAndSendSpecialTx(wallet, tx, pl, &extraValues); } bool MNModel::createDMN(const std::string& alias, @@ -377,12 +384,14 @@ bool MNModel::createDMN(const std::string& alias, Optional operatorSk{nullopt}; CBLSPublicKey operatorPk; if (operatorPubKey) { - if (!operatorPk.SetHexStr(*operatorPubKey)) { + auto opPk = bls::DecodePublic(Params(), *operatorPubKey); + if (!opPk || !opPk->IsValid()) { strError = "invalid operator pubkey"; return false; } + operatorPk = *opPk; } else { - // Stored in masternode conf locally as well + // Stored within the register tx operatorSk = CBLSSecretKey(); operatorSk->MakeNewKey(); operatorPk = operatorSk->GetPublicKey(); @@ -395,6 +404,7 @@ bool MNModel::createDMN(const std::string& alias, operatorPk, Optional(*ownerAddr), // voting key payoutKeyId, // payout script + operatorSk, // only if the operator was provided (or locally created) nullopt, // operator percentage nullopt); // operator percentage if (!res) { From 10856b78f7c5329ec15c215bdb250a01388656e4 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 31 Jan 2022 20:48:38 -0300 Subject: [PATCH 18/82] GUI: Implement and connect owner "kill DMN" functionality. --- src/qt/pivx/masternodeswidget.cpp | 37 +++++++++++++++++------- src/qt/pivx/mnmodel.cpp | 47 +++++++++++++++++++++++++++++++ src/qt/pivx/mnmodel.h | 4 ++- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 9e1505bec8e20..dd2dc51fce1fd 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -343,6 +343,7 @@ void MasterNodesWidget::onDeleteMNClicked() QString txId = index.sibling(index.row(), MNModel::COLLATERAL_ID).data(Qt::DisplayRole).toString(); QString outIndex = index.sibling(index.row(), MNModel::COLLATERAL_OUT_INDEX).data(Qt::DisplayRole).toString(); QString qAliasString = index.data(Qt::DisplayRole).toString(); + bool isLegacy = ((uint16_t) index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt()) == MNViewType::LEGACY; bool convertOK = false; unsigned int indexOut = outIndex.toUInt(&convertOK); @@ -351,18 +352,34 @@ void MasterNodesWidget::onDeleteMNClicked() return; } - if (!ask(tr("Delete Masternode"), tr("You are just about to delete Masternode:\n%1\n\nAre you sure?").arg(qAliasString))) { - return; - } + if (isLegacy) { + if (!ask(tr("Delete Masternode"), tr("You are just about to delete Masternode:\n%1\n\nAre you sure?").arg(qAliasString))) { + return; + } - QString errorStr; - if (!mnModel->removeLegacyMN(qAliasString.toStdString(), txId.toStdString(), indexOut, errorStr)) { - inform(errorStr); - return; + QString errorStr; + if (!mnModel->removeLegacyMN(qAliasString.toStdString(), txId.toStdString(), indexOut, errorStr)) { + inform(errorStr); + return; + } + // Update list + mnModel->removeMn(index); + updateListState(); + } else { + if (!ask(tr("Delete Masternode"), tr("You are just about to spend the collateral\n" + "(creating a transaction to yourself)\n" + "of your Masternode:\n\n%1\n\nAre you sure?") + .arg(qAliasString))) { + return; + } + + auto res = mnModel->killDMN(uint256S(txId.toStdString()), indexOut); + if (!res) { + inform(QString::fromStdString(res.getError())); + return; + } + inform("Deterministic Masternode removed successfully! the change will be reflected on the next mined block"); } - // Update list - mnModel->removeMn(index); - updateListState(); } void MasterNodesWidget::onCreateMNClicked() diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 8974f7fb49066..2c64148f4c2ed 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -5,6 +5,7 @@ #include "qt/pivx/mnmodel.h" #include "bls/key_io.h" +#include "coincontrol.h" #include "interfaces/tiertwo.h" #include "evo/specialtx_utils.h" #include "masternode.h" @@ -204,6 +205,8 @@ bool MNModel::removeMn(const QModelIndex& modelIndex) { int idx = modelIndex.row(); beginRemoveRows(QModelIndex(), idx, idx); + auto mnWrapper = nodes.at(idx); + if (mnWrapper.collateralId) collateralTxAccepted.remove(mnWrapper.collateralId->hash.GetHex()); nodes.removeAt(idx); endRemoveRows(); Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, 5, QModelIndex()) ); @@ -416,6 +419,50 @@ bool MNModel::createDMN(const std::string& alias, return true; } +OperationResult MNModel::killDMN(const uint256& collateralHash, unsigned int outIndex) +{ + auto p_wallet = vpwallets[0]; // TODO: Move to walletModel + const auto& tx = p_wallet->GetWalletTx(collateralHash); + if (!tx || outIndex >= tx->tx->vout.size()) return {false, "collateral not found"}; + const auto& output = tx->tx->vout[outIndex]; + + COutPoint collateral_output(collateralHash, outIndex); + CCoinControl coinControl; + coinControl.Select(collateral_output); + QList recipients; + auto ownAddr = walletModel->getNewAddress(""); + if (!ownAddr) return {false, ownAddr.getError()}; + CAmount amountToSend = output.nValue - CWallet::minTxFee.GetFeePerK(); + recipients.push_back(SendCoinsRecipient{QString::fromStdString(ownAddr.getObjResult()->ToString()), "", amountToSend, ""}); + WalletModelTransaction currentTransaction(recipients); + walletModel->unlockCoin(collateral_output); + WalletModel::SendCoinsReturn prepareStatus = walletModel->prepareTransaction(¤tTransaction, &coinControl, false); + + CClientUIInterface::MessageBoxFlags informType; + QString returnMsg = GuiTransactionsUtils::ProcessSendCoinsReturn( + prepareStatus, + walletModel, + informType, // this flag is not needed + BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), + currentTransaction.getTransactionFee()), + true + ); + + if (prepareStatus.status != WalletModel::OK) { + walletModel->lockCoin(collateral_output); + return {false, returnMsg.toStdString()}; + } + + WalletModel::SendCoinsReturn sendStatus = walletModel->sendCoins(currentTransaction); + returnMsg = GuiTransactionsUtils::ProcessSendCoinsReturn(sendStatus, walletModel, informType); + if (sendStatus.status != WalletModel::OK) { + walletModel->lockCoin(collateral_output); + return {false, returnMsg.toStdString()}; + } + + return {true}; +} + bool MNModel::createMNCollateral( const QString& alias, const QString& addr, diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index 2de0bd04952fe..da5920aaa0cb9 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -8,6 +8,7 @@ #include #include "amount.h" #include "masternodeconfig.h" +#include "operationresult.h" #include "primitives/transaction.h" class CMasternode; @@ -116,7 +117,8 @@ class MNModel : public QAbstractTableModel const Optional& operatorPercentage = nullopt, const Optional& operatorPayoutAddr = nullopt); - + // Completely stops the Masternode spending the collateral + OperationResult killDMN(const uint256& collateralHash, unsigned int outIndex); // Generates the collateral transaction bool createMNCollateral(const QString& alias, const QString& addr, COutPoint& ret_outpoint, QString& ret_error); From 2a1a6733c3f02ab6d34a69436c037f383f9263f0 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 1 Feb 2022 17:25:12 -0300 Subject: [PATCH 19/82] GUI: Implement and connect create DMN summary page --- src/qt/pivx/forms/masternodewizarddialog.ui | 492 +++++++++++++++++++- src/qt/pivx/masternodewizarddialog.cpp | 76 ++- src/qt/pivx/masternodewizarddialog.h | 26 ++ src/qt/pivx/res/css/style_dark.css | 6 + src/qt/pivx/res/css/style_light.css | 6 + 5 files changed, 599 insertions(+), 7 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index e7d8a8f7a02c6..9e26041cc283e 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -639,7 +639,7 @@ 20 - 30 + 15 @@ -1012,6 +1012,491 @@ + + + + 6 + + + 0 + + + 12 + + + 0 + + + 12 + + + + + + 0 + 50 + + + + + 16777215 + 50 + + + + Masternode Created! + + + + + + + + + + + 8 + + + 9 + + + 9 + + + 12 + + + + + Owner Section + + + + + + + + 0 + 0 + + + + + 0 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Main Address + + + + + + + N/A + + + + + + + + + + Qt::LeftToRight + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Payout Address + + + + + + + N/A + + + + + + + + + + + + + + 60 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 3 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Collateral ID + + + + + + + N/A + + + + + + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Collateral Index + + + + + + + N/A + + + + + + + + + + + + + + + + + 8 + + + 9 + + + 12 + + + + + Operator Section + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 3 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Operator Key + + + + + + + N/A + + + + + + + + + + Qt::LeftToRight + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Service + + + + + + + N/A + + + + + + + + + + + + + + 60 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 3 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Payout Address + + + + + + + N/A + + + + + + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Payout Percentage + + + + + + + 0.00 + + + + + + + + + + + + + + + + + @@ -1019,10 +1504,13 @@ Qt::Vertical + + QSizePolicy::Minimum + 20 - 40 + 10 diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index aaf58291b99b3..ad5b1e54a2bd8 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -12,6 +12,7 @@ #include #include +#include static inline QString formatParagraph(const QString& str) { return "

" + str + "

"; @@ -34,6 +35,16 @@ static void initBtn(std::initializer_list args) } } +static void setCardShadow(QWidget* edit) +{ + QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); + shadowEffect->setColor(QColor(77, 77, 77, 30)); + shadowEffect->setXOffset(0); + shadowEffect->setYOffset(4); + shadowEffect->setBlurRadius(6); + edit->setGraphicsEffect(shadowEffect); +} + MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnModel, QWidget *parent) : FocusedDialog(parent), ui(new Ui::MasterNodeWizardDialog), @@ -86,7 +97,7 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM QRegularExpression rx("^(?:(?![\\#\\s]).)*"); ui->lineEditName->setValidator(new QRegularExpressionValidator(rx, ui->lineEditName)); - // Frame 4 + // Frame 3 setCssProperty(ui->labelTitle4, "text-title-dialog"); setCssProperty({ui->labelSubtitleIp, ui->labelSubtitlePort}, "text-title"); setCssSubtitleScreen(ui->labelSubtitleAddressIp); @@ -103,6 +114,18 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM ui->lineEditPort->setText("51472"); } + // Frame 4 + setCssProperty(ui->labelSummary, "text-title-dialog"); + setCssProperty({ui->containerOwner, ui->containerOperator}, "card-governance"); + setCssProperty({ui->labelOwnerSection, ui->labelOperatorSection}, "text-section-title"); + setCssProperty({ui->labelTitleMainAddr, ui->labelTitlePayoutAddr, ui->labelTitleCollateral, ui->labelTitleCollateralIndex, + ui->labelTitleOperatorKey, ui->labelTitleOperatorService, ui->labelTitleOperatorPayout, ui->labelTitleOperatorPercentage, + ui->labelTitleOperatorService}, "text-title-right"); + setCssProperty({ui->labelMainAddr, ui->labelPayoutAddr, ui->labelCollateralIndex, ui->labelCollateralHash, + ui->labelOperatorKey, ui->labelOperatorPayout, ui->labelOperatorPercentage, ui->labelOperatorService}, "text-body2-dialog"); + setCardShadow(ui->containerOwner); + setCardShadow(ui->containerOperator); + // Confirm icons ui->stackedIcon1->addWidget(icConfirm1); ui->stackedIcon3->addWidget(icConfirm3); @@ -128,7 +151,7 @@ void MasterNodeWizardDialog::showEvent(QShowEvent *event) void MasterNodeWizardDialog::accept() { - switch(pos) { + switch (pos) { case 0:{ ui->stackedWidget->setCurrentIndex(1); ui->pushName4->setChecked(false); @@ -164,12 +187,43 @@ void MasterNodeWizardDialog::accept() } icConfirm4->setVisible(true); isOk = createMN(); + if (!isOk) { + inform(returnStr); + return; + } + if (!mnSummary) QDialog::accept(); + ui->pushName4->setChecked(true); + } + case 3: { + ui->btnBack->setVisible(false); + ui->btnNext->setText("CLOSE"); + ui->stackedWidget->setCurrentIndex(3); + setSummary(); + break; + } + case 4: { QDialog::accept(); } } pos++; } +static void setShortText(QLabel* label, const QString& str, int size) +{ + label->setText(str.left(size) + "..." + str.right(size)); +} + +void MasterNodeWizardDialog::setSummary() +{ + assert(mnSummary); + setShortText(ui->labelMainAddr, QString::fromStdString(mnSummary->ownerAddr), 14); + setShortText(ui->labelPayoutAddr, QString::fromStdString(mnSummary->ownerPayoutAddr), 14); + setShortText(ui->labelCollateralHash, QString::fromStdString(mnSummary->collateralOut.hash.GetHex()), 20); + ui->labelCollateralIndex->setText(QString::number(mnSummary->collateralOut.n)); + ui->labelOperatorService->setText(QString::fromStdString(mnSummary->service)); + ui->labelOperatorKey->setText(QString::fromStdString(mnSummary->operatorKey)); +} + bool MasterNodeWizardDialog::createMN() { if (!walletModel) { @@ -223,9 +277,9 @@ bool MasterNodeWizardDialog::createMN() // later this can be customized by the user. // Create owner addr - const auto r = walletModel->getNewAddress("dmn_owner_" + alias); - if (!r) return errorOut(tr(r.getError().c_str())); - const CKeyID* ownerKeyId = r.getObjResult()->getKeyID(); + const auto ownerAddr = walletModel->getNewAddress("dmn_owner_" + alias); + if (!ownerAddr) return errorOut(tr(ownerAddr.getError().c_str())); + const CKeyID* ownerKeyId = ownerAddr.getObjResult()->getKeyID(); // Create payout addr const auto payoutAddr = walletModel->getNewAddress("dmn_payout_" + alias); @@ -248,6 +302,18 @@ bool MasterNodeWizardDialog::createMN() if (!res) { return errorOut(tr(error_str.c_str())); } + + std::string ownerAddrStr = ownerAddr.getObjResult()->ToString(); + mnSummary = std::make_unique(alias, + ipAddress+":"+port, + collateralOut, + ownerAddrStr, + payoutAddr.getObjResult()->ToString(), + "fa3b23b341ccba23ab398befea2321bc46f", // todo: add real operator key.. + ownerAddrStr, // voting key, for now fixed to owner addr + 0, // operator percentage + nullopt); // operator payout + returnStr = tr("Deterministic Masternode created! It will appear on your list on the next mined block!"); } else { // Legacy diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index 05bf2c5b64cd9..c9fb2833ed4f8 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -18,6 +18,30 @@ class MasterNodeWizardDialog; class QPushButton; } +struct MNSummary +{ + MNSummary(const std::string& _alias, const std::string& _service, const COutPoint& _collateralOut, + const std::string& _ownerAddr, const std::string& _ownerPayoutAddr, const std::string& _operatorKey, + const std::string& _votingKey, int _operatorPercentage, + const Optional& _operatorPayoutAddr) : alias(_alias), service(_service), + collateralOut(_collateralOut), + ownerAddr(_ownerAddr), + ownerPayoutAddr(_ownerPayoutAddr), + operatorKey(_operatorKey), votingKey(_votingKey), + operatorPercentage(_operatorPercentage), + operatorPayoutAddr(_operatorPayoutAddr) {} + + std::string alias; + std::string service; + COutPoint collateralOut; + std::string ownerAddr; + std::string ownerPayoutAddr; + std::string operatorKey; + std::string votingKey; + int operatorPercentage{0}; + Optional operatorPayoutAddr; +}; + class MasterNodeWizardDialog : public FocusedDialog, public PWidget::Translator { Q_OBJECT @@ -44,10 +68,12 @@ private Q_SLOTS: QPushButton* icConfirm4; SnackBar *snackBar = nullptr; int pos = 0; + std::unique_ptr mnSummary{nullptr}; WalletModel* walletModel{nullptr}; MNModel* mnModel{nullptr}; bool createMN(); + void setSummary(); void inform(const QString& text); bool errorOut(const QString& err); }; diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 6178b7de511bb..8f6b1b1b018e8 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -1021,6 +1021,12 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:14px; } +*[cssClass="text-section-title"] { + color:#b088ff; + font-size:16px; + font-weight: bold; +} + *[cssClass="text-title-white"] { color:#FFFFFF; font-size:16px; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index fe8d5693bb649..52c601d5366b1 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -1027,6 +1027,12 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:14px; } +*[cssClass="text-section-title"] { + color:#5c4b7d; + font-size:16px; + font-weight: bold; +} + *[cssClass="text-title-white"] { color:#FFFFFF; font-size:16px; From 597f888c31dc88b76fc661ccab1b0b565eb41408 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 2 Feb 2022 13:38:59 -0300 Subject: [PATCH 20/82] GUI: Masternode creation wizard; generalize pages setup, next and back actions. --- src/qt/pivx/forms/masternodewizarddialog.ui | 326 ++++++++++++++++++-- src/qt/pivx/masternodewizarddialog.cpp | 149 +++++---- src/qt/pivx/masternodewizarddialog.h | 12 +- src/qt/pivx/qtutils.cpp | 7 + src/qt/pivx/qtutils.h | 1 + 5 files changed, 405 insertions(+), 90 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index 9e26041cc283e..50abd7cd6ef47 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -234,7 +234,7 @@
- + 22 @@ -280,7 +280,7 @@ 0 - + 22 @@ -313,7 +313,7 @@ - + 0 @@ -335,7 +335,7 @@ - + 22 @@ -381,7 +381,7 @@ 0 - + 22 @@ -413,6 +413,208 @@ + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + + + + + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 0 + + + + + 22 + 22 + + + + + 22 + 22 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 4 + + + true + + + false + + + true + + + + + + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + + + + + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 0 + + + + + 22 + 22 + + + + + 22 + 22 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 5 + + + true + + + false + + + true + + + + + + + + @@ -538,7 +740,7 @@ - + 80 @@ -579,7 +781,7 @@ - + 80 @@ -596,7 +798,7 @@ Qt::NoFocus - Address + Service true @@ -608,6 +810,94 @@ + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 80 + 0 + + + + + 80 + 16777215 + + + + Qt::NoFocus + + + Owner + + + true + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 80 + 0 + + + + + 80 + 16777215 + + + + Qt::NoFocus + + + Operator + + + true + + + false + + + + + Qt::Horizontal @@ -893,13 +1183,13 @@ - Address of the node that must always be online running the actual master node. + Service of the node that must always be online Qt::AlignCenter - true + false @@ -1101,7 +1391,7 @@ - 3 + 4 0 @@ -1139,7 +1429,7 @@ - 3 + 4 0 @@ -1201,7 +1491,7 @@ - 3 + 4 3 @@ -1236,7 +1526,7 @@ - 3 + 4 0 @@ -1320,7 +1610,7 @@ - 3 + 4 3 @@ -1358,7 +1648,7 @@ - 3 + 4 0 @@ -1420,7 +1710,7 @@ - 3 + 4 3 @@ -1455,7 +1745,7 @@ - 3 + 4 0 diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index ad5b1e54a2bd8..94fe2887d44d3 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -22,16 +22,28 @@ static inline QString formatHtmlContent(const QString& str) { return "" + str + ""; } -static void initBtn(std::initializer_list args) +static void initTopBtns(const QList& icons, const QList& numbers, const QList& names) { + assert(icons.size() == names.size() && names.size() == numbers.size()); QSize BUTTON_SIZE = QSize(22, 22); - for (QPushButton* btn : args) { - btn->setMinimumSize(BUTTON_SIZE); - btn->setMaximumSize(BUTTON_SIZE); - btn->move(0, 0); - btn->show(); - btn->raise(); - btn->setVisible(false); + for (int i=0; i < icons.size(); i++) { + auto pushNumber = numbers[i]; + auto pushIcon = icons[i]; + auto pushName = names[i]; + + setCssProperty(pushNumber, "btn-number-check"); + pushNumber->setEnabled(false); + + setCssProperty(pushName, "btn-name-check"); + pushName->setEnabled(false); + + setCssProperty(pushIcon, "ic-step-confirm"); + pushIcon->setMinimumSize(BUTTON_SIZE); + pushIcon->setMaximumSize(BUTTON_SIZE); + pushIcon->move(0, 0); + pushIcon->show(); + pushIcon->raise(); + pushIcon->setVisible(false); } } @@ -48,29 +60,21 @@ static void setCardShadow(QWidget* edit) MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnModel, QWidget *parent) : FocusedDialog(parent), ui(new Ui::MasterNodeWizardDialog), - icConfirm1(new QPushButton(this)), - icConfirm3(new QPushButton(this)), - icConfirm4(new QPushButton(this)), walletModel(model), mnModel(_mnModel) { ui->setupUi(this); - - this->setStyleSheet(parent->styleSheet()); + setStyleSheet(parent->styleSheet()); setCssProperty(ui->frame, "container-dialog"); ui->frame->setContentsMargins(10,10,10,10); - setCssProperty({ui->labelLine1, ui->labelLine3}, "line-purple"); - setCssProperty({ui->groupBoxName, ui->groupContainer}, "container-border"); - setCssProperty({ui->pushNumber1, ui->pushNumber3, ui->pushNumber4}, "btn-number-check"); - setCssProperty({ui->pushName1, ui->pushName3, ui->pushName4}, "btn-name-check"); + isDeterministic = walletModel->isV6Enforced(); - ui->pushNumber1->setEnabled(false); - ui->pushNumber3->setEnabled(false); - ui->pushNumber4->setEnabled(false); - ui->pushName1->setEnabled(false); - ui->pushName3->setEnabled(false); - ui->pushName4->setEnabled(false); + for (int i = 0; i < 5; i++) list_icConfirm.push_back(new QPushButton(this)); + list_pushNumber = {ui->pushNumber1, ui->pushNumber2, ui->pushNumber3, ui->pushNumber4, ui->pushNumber5}; + list_pushName = {ui->pushName1, ui->pushName2, ui->pushName3, ui->pushName4, ui->pushName5}; + setCssProperty({ui->labelLine1, ui->labelLine2, ui->labelLine3, ui->labelLine4}, "line-purple"); + setCssProperty({ui->groupBoxName, ui->groupContainer}, "container-border"); // Frame 1 setCssProperty(ui->labelTitle1, "text-title-dialog"); @@ -127,11 +131,12 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM setCardShadow(ui->containerOperator); // Confirm icons - ui->stackedIcon1->addWidget(icConfirm1); - ui->stackedIcon3->addWidget(icConfirm3); - ui->stackedIcon4->addWidget(icConfirm4); - initBtn({icConfirm1, icConfirm3, icConfirm4}); - setCssProperty({icConfirm1, icConfirm3, icConfirm4}, "ic-step-confirm"); + ui->stackedIcon1->addWidget(list_icConfirm[0]); + ui->stackedIcon2->addWidget(list_icConfirm[1]); + ui->stackedIcon3->addWidget(list_icConfirm[2]); + ui->stackedIcon4->addWidget(list_icConfirm[3]); + ui->stackedIcon5->addWidget(list_icConfirm[4]); + initTopBtns(list_icConfirm, list_pushNumber, list_pushName); // Connect btns setCssBtnPrimary(ui->btnNext); @@ -149,16 +154,27 @@ void MasterNodeWizardDialog::showEvent(QShowEvent *event) if (ui->btnNext) ui->btnNext->setFocus(); } +void setBtnsChecked(QList btns, int start, int end) +{ + for (int i=start; i < btns.size(); i++) { + btns[i]->setChecked(i <= end); + } +} + +void MasterNodeWizardDialog::moveToNextPage(int currentPos, int nextPos) +{ + ui->stackedWidget->setCurrentIndex(nextPos); + list_icConfirm[currentPos]->setVisible(true); + list_pushNumber[nextPos]->setChecked(true); + setBtnsChecked(list_pushName, pos, nextPos); +} + void MasterNodeWizardDialog::accept() { + int nextPos = pos + 1; switch (pos) { - case 0:{ - ui->stackedWidget->setCurrentIndex(1); - ui->pushName4->setChecked(false); - ui->pushName3->setChecked(true); - ui->pushName1->setChecked(true); - icConfirm1->setVisible(true); - ui->pushNumber3->setChecked(true); + case 0: { + moveToNextPage(pos, nextPos); ui->btnBack->setVisible(true); ui->lineEditName->setFocus(); break; @@ -170,13 +186,7 @@ void MasterNodeWizardDialog::accept() return; } setCssEditLine(ui->lineEditName, true, true); - - ui->stackedWidget->setCurrentIndex(2); - ui->pushName4->setChecked(false); - ui->pushName3->setChecked(true); - ui->pushName1->setChecked(true); - icConfirm3->setVisible(true); - ui->pushNumber4->setChecked(true); + moveToNextPage(pos, nextPos); ui->lineEditIpAddress->setFocus(); break; } @@ -185,23 +195,32 @@ void MasterNodeWizardDialog::accept() if (ui->lineEditIpAddress->text().isEmpty()) { return; } - icConfirm4->setVisible(true); - isOk = createMN(); - if (!isOk) { - inform(returnStr); - return; + if (!isDeterministic) { + isOk = createMN(); + QDialog::accept(); + } else { + moveToNextPage(pos, nextPos); } - if (!mnSummary) QDialog::accept(); - ui->pushName4->setChecked(true); + break; } case 3: { + // todo: add owner page + moveToNextPage(pos, nextPos); + break; + } + case 4: { + // todo: add operator page + moveToNextPage(pos, nextPos); + break; + } + case 5: { ui->btnBack->setVisible(false); ui->btnNext->setText("CLOSE"); ui->stackedWidget->setCurrentIndex(3); setSummary(); break; } - case 4: { + case 6: { QDialog::accept(); } } @@ -270,7 +289,7 @@ bool MasterNodeWizardDialog::createMN() } } - if (walletModel->isV6Enforced()) { + if (isDeterministic) { // Deterministic // For now, create every single key inside the wallet @@ -331,33 +350,27 @@ bool MasterNodeWizardDialog::createMN() return true; } +void MasterNodeWizardDialog::moveBack(int backPos) +{ + ui->stackedWidget->setCurrentIndex(backPos); + list_icConfirm[pos]->setVisible(false); + setBtnsChecked(list_pushName, backPos, backPos); + setBtnsChecked(list_pushNumber, backPos, backPos); +} + void MasterNodeWizardDialog::onBackClicked() { if (pos == 0) return; pos--; - switch(pos) { + moveBack(pos); + switch (pos) { case 0:{ - ui->stackedWidget->setCurrentIndex(0); - ui->btnNext->setFocus(); - ui->pushNumber1->setChecked(true); - ui->pushNumber4->setChecked(false); - ui->pushNumber3->setChecked(false); - ui->pushName4->setChecked(false); - ui->pushName3->setChecked(false); - ui->pushName1->setChecked(true); - icConfirm1->setVisible(false); ui->btnBack->setVisible(false); + ui->btnNext->setFocus(); break; } case 1: { - ui->stackedWidget->setCurrentIndex(1); ui->lineEditName->setFocus(); - ui->pushNumber4->setChecked(false); - ui->pushNumber3->setChecked(true); - ui->pushName4->setChecked(false); - ui->pushName3->setChecked(true); - icConfirm3->setVisible(false); - break; } } diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index c9fb2833ed4f8..8f940a40c9609 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -63,12 +63,13 @@ private Q_SLOTS: void onBackClicked(); private: Ui::MasterNodeWizardDialog *ui; - QPushButton* icConfirm1; - QPushButton* icConfirm3; - QPushButton* icConfirm4; - SnackBar *snackBar = nullptr; + QList list_icConfirm{}; + QList list_pushNumber{}; + QList list_pushName{}; + SnackBar* snackBar{nullptr}; int pos = 0; std::unique_ptr mnSummary{nullptr}; + bool isDeterministic{false}; WalletModel* walletModel{nullptr}; MNModel* mnModel{nullptr}; @@ -76,6 +77,9 @@ private Q_SLOTS: void setSummary(); void inform(const QString& text); bool errorOut(const QString& err); + + void moveToNextPage(int currentPos, int nextPos); + void moveBack(int backPos); }; #endif // MASTERNODEWIZARDDIALOG_H diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index ee5adae401e91..379c9a9b69291 100644 --- a/src/qt/pivx/qtutils.cpp +++ b/src/qt/pivx/qtutils.cpp @@ -336,6 +336,13 @@ void setCssProperty(std::initializer_list args, const QString& value) } } +void setCssProperty(const std::list& args, const QString& value) +{ + for (QWidget* w : args) { + setCssProperty(w, value); + } +} + void setCssProperty(QWidget* wid, const QString& value, bool forceUpdate) { if (wid->property("cssClass") == value) return; diff --git a/src/qt/pivx/qtutils.h b/src/qt/pivx/qtutils.h index d1bf4c839d30b..64ad240e1af5a 100644 --- a/src/qt/pivx/qtutils.h +++ b/src/qt/pivx/qtutils.h @@ -71,6 +71,7 @@ void setCssTitleScreen(QLabel* label); void setCssSubtitleScreen(QWidget* wid); void setCssTextBodyDialog(std::initializer_list args); void setCssTextBodyDialog(QWidget* widget); +void setCssProperty(const std::list& args, const QString& value); void setCssProperty(std::initializer_list args, const QString& value); void setCssProperty(QWidget* wid, const QString& value, bool forceUpdate = false); void forceUpdateStyle(QWidget* widget, bool forceUpdate); From 3a8ab8e32aa5a497bc4f6e57b524790756e465e6 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 4 Feb 2022 16:08:07 -0300 Subject: [PATCH 21/82] interfaces:tiertwo, implement isBlsPubKeyValid function. --- src/interfaces/tiertwo.cpp | 8 ++++++++ src/interfaces/tiertwo.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/interfaces/tiertwo.cpp b/src/interfaces/tiertwo.cpp index 69936050f5f26..9727f6fc197fb 100644 --- a/src/interfaces/tiertwo.cpp +++ b/src/interfaces/tiertwo.cpp @@ -3,6 +3,8 @@ // file COPYING or https://www.opensource.org/licenses/mit-license.php. #include "interfaces/tiertwo.h" + +#include "bls/key_io.h" #include "evo/deterministicmns.h" #include "optional.h" #include "validation.h" @@ -12,6 +14,12 @@ namespace interfaces { std::unique_ptr g_tiertwo; +bool TierTwo::isBlsPubKeyValid(const std::string& blsKey) +{ + auto opKey = bls::DecodePublic(Params(), blsKey); + return opKey && opKey->IsValid(); +} + std::shared_ptr createDMNViewIfMine(CWallet* pwallet, const CDeterministicMNCPtr& dmn) { bool hasOwnerKey; diff --git a/src/interfaces/tiertwo.h b/src/interfaces/tiertwo.h index 64c688a7202a6..26aac573dad89 100644 --- a/src/interfaces/tiertwo.h +++ b/src/interfaces/tiertwo.h @@ -45,6 +45,9 @@ class TierTwo : public CValidationInterface { // Initialize cache void init(); + // Return true if the bls key is valid + bool isBlsPubKeyValid(const std::string& blsKey); + // Return the DMNs that this wallet "owns". // future: add filter to return by owner, operator, voter or a combination of them. std::vector> getKnownDMNs() { return WITH_LOCK(cs_cache, return m_cached_dmns;); }; From fa9a9487190bd425ad4421fa3a15080d5bf806ad Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 4 Feb 2022 16:11:21 -0300 Subject: [PATCH 22/82] GUI: Masternode creation wizard dialog, create and connect owner and operator pages. --- src/qt/pivx/forms/masternodewizarddialog.ui | 463 +++++++++++++++++++- src/qt/pivx/masternodeswidget.cpp | 36 +- src/qt/pivx/masternodewizarddialog.cpp | 210 +++++++-- src/qt/pivx/masternodewizarddialog.h | 33 +- src/qt/pivx/mnmodel.cpp | 87 ++-- src/qt/pivx/mnmodel.h | 23 +- src/qt/walletmodel.cpp | 16 + src/qt/walletmodel.h | 6 + 8 files changed, 734 insertions(+), 140 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index 50abd7cd6ef47..6901fbe7e7972 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -6,14 +6,14 @@ 0 0 - 715 + 717 602 N/A - + 0 @@ -37,7 +37,7 @@ QFrame::Raised - + 0 @@ -208,7 +208,7 @@ - + @@ -309,7 +309,7 @@ - + @@ -410,7 +410,7 @@ - + @@ -511,7 +511,7 @@ - + @@ -612,7 +612,7 @@ - + @@ -949,7 +949,7 @@ - + 6 @@ -1057,8 +1057,8 @@ - - + + 6 @@ -1144,8 +1144,8 @@ - - + + 6 @@ -1220,7 +1220,7 @@ - + Qt::Vertical @@ -1302,7 +1302,422 @@ + + + + 6 + + + 90 + + + 12 + + + 90 + + + 12 + + + + + + 0 + 50 + + + + + 16777215 + 50 + + + + Set Masternode Owner Data + + + + + + + Can leave the fields empty, new addresses will be created automatically + + + Qt::AlignCenter + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Enter main MN address (can only use addresses inside the wallet) + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + e.g. Dxxx + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + Enter Payout Address + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + e.g. Dxxx + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 6 + + + 85 + + + 12 + + + 85 + + + 12 + + + + + + 0 + 40 + + + + + 16777215 + 50 + + + + Set Operator Data + + + + + + + + 360 + 30 + + + + Customize the operator key, the operator reward percentage and payout address + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Enter key (can leave it empty and the wallet will create one for you) + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + e.g. bls-pubewlknlkweqkwli1j3 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + Optional: operator rewards percentage and payout address + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + e.g. Dxxx + + + + + + + + 40 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 70 + 16777215 + + + + + + + + + + 2 + + + Qt::AlignCenter + + + 10 % + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + 6 @@ -1371,7 +1786,7 @@ 0 - + 0 @@ -1565,7 +1980,7 @@ - + 8 @@ -1608,7 +2023,7 @@ - + 4 @@ -1646,7 +2061,7 @@ Qt::LeftToRight - + 4 @@ -1683,14 +2098,14 @@ - + 60 0 - + 0 @@ -1707,8 +2122,8 @@ 0 - - + + 4 @@ -1742,8 +2157,8 @@ - - + + 4 diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index dd2dc51fce1fd..3f831244819eb 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -5,6 +5,7 @@ #include "qt/pivx/masternodeswidget.h" #include "qt/pivx/forms/ui_masternodeswidget.h" +#include "qt/pivx/defaultdialog.h" #include "qt/pivx/qtutils.h" #include "qt/pivx/mnrow.h" #include "qt/pivx/mninfodialog.h" @@ -397,17 +398,32 @@ void MasterNodesWidget::onCreateMNClicked() .arg(GUIUtil::formatBalance(mnCollateralAmount, BitcoinUnits::PIV))); return; } - showHideOp(true); - MasterNodeWizardDialog *dialog = new MasterNodeWizardDialog(walletModel, mnModel, window); - if (openDialogWithOpaqueBackgroundY(dialog, window, 5, 7)) { - if (dialog->isOk) { - updateListState(); - // add mn - inform(dialog->returnStr); - } else { - warn(tr("Error creating masternode"), dialog->returnStr); + MasterNodeWizardDialog* dialog = new MasterNodeWizardDialog(walletModel, mnModel, window); + connect(dialog, &MasterNodeWizardDialog::message, this, &PWidget::emitMessage); + do { + showHideOp(true); + dialog->isWaitingForAsk = false; + if (openDialogWithOpaqueBackgroundY(dialog, window, 5, 7)) { + if (dialog->isOk) { + updateListState(); + inform(dialog->returnStr); + } else { + warn(tr("Error creating masternode"), dialog->returnStr); + } + } else if (dialog->isWaitingForAsk) { + auto* askDialog = new DefaultDialog(window); + showHide(true); + askDialog->setText(tr("Advanced Masternode Configurations"), + tr("The wallet can complete the next steps,\ncreating the MN keys and addresses automatically\n\n" + "Do you want to customize the owner, operator\nand voter or create them automatically?\n" + "(recommended only for advanced users)"), + tr("Automatic"), tr("Customize")); + askDialog->adjustSize(); + openDialogWithOpaqueBackground(askDialog, window); + askDialog->isOk ? dialog->completeTask() : dialog->moveToAdvancedConf(); } - } + } while (dialog->isWaitingForAsk); + dialog->deleteLater(); } diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 94fe2887d44d3..90889bd8df51d 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -6,6 +6,7 @@ #include "qt/pivx/forms/ui_masternodewizarddialog.h" #include "key_io.h" +#include "interfaces/tiertwo.h" #include "qt/pivx/mnmodel.h" #include "qt/pivx/qtutils.h" #include "qt/walletmodel.h" @@ -76,7 +77,7 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM setCssProperty({ui->labelLine1, ui->labelLine2, ui->labelLine3, ui->labelLine4}, "line-purple"); setCssProperty({ui->groupBoxName, ui->groupContainer}, "container-border"); - // Frame 1 + // Frame Intro setCssProperty(ui->labelTitle1, "text-title-dialog"); setCssProperty(ui->labelMessage1a, "text-main-grey"); setCssProperty(ui->labelMessage1b, "text-main-purple"); @@ -87,7 +88,7 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM "to the network (however, these coins are still yours and will never leave your possession).").arg(collateralAmountStr)) + formatParagraph(tr("You can deactivate the node and unlock the coins at any time.")))); - // Frame 3 + // Frame Collateral setCssProperty(ui->labelTitle3, "text-title-dialog"); setCssProperty(ui->labelMessage3, "text-main-grey"); @@ -101,7 +102,7 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM QRegularExpression rx("^(?:(?![\\#\\s]).)*"); ui->lineEditName->setValidator(new QRegularExpressionValidator(rx, ui->lineEditName)); - // Frame 3 + // Frame Service setCssProperty(ui->labelTitle4, "text-title-dialog"); setCssProperty({ui->labelSubtitleIp, ui->labelSubtitlePort}, "text-title"); setCssSubtitleScreen(ui->labelSubtitleAddressIp); @@ -118,7 +119,23 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM ui->lineEditPort->setText("51472"); } - // Frame 4 + // Frame Owner + setCssProperty(ui->labelTitle5, "text-title-dialog"); + setCssSubtitleScreen(ui->labelSubtitleOwner); + setCssProperty({ui->labelSubtitleOwnerAddress, ui->labelSubtitlePayoutAddress}, "text-title"); + initCssEditLine(ui->lineEditOwnerAddress); + initCssEditLine(ui->lineEditPayoutAddress); + + // Frame Operator + setCssProperty(ui->labelTitle6, "text-title-dialog"); + setCssSubtitleScreen(ui->labelSubtitleOperator); + setCssProperty({ui->labelSubtitleOperatorKey, ui->labelSubtitleOperatorReward}, "text-title"); + initCssEditLine(ui->lineEditOperatorKey); + initCssEditLine(ui->lineEditOperatorPayoutAddress); + initCssEditLine(ui->lineEditPercentage); + ui->lineEditPercentage->setValidator(new QIntValidator(1, 99)); + + // Frame Summary setCssProperty(ui->labelSummary, "text-title-dialog"); setCssProperty({ui->containerOwner, ui->containerOperator}, "card-governance"); setCssProperty({ui->labelOwnerSection, ui->labelOperatorSection}, "text-section-title"); @@ -165,7 +182,7 @@ void MasterNodeWizardDialog::moveToNextPage(int currentPos, int nextPos) { ui->stackedWidget->setCurrentIndex(nextPos); list_icConfirm[currentPos]->setVisible(true); - list_pushNumber[nextPos]->setChecked(true); + if (list_pushNumber.size() != nextPos) list_pushNumber[nextPos]->setChecked(true); setBtnsChecked(list_pushName, pos, nextPos); } @@ -173,13 +190,13 @@ void MasterNodeWizardDialog::accept() { int nextPos = pos + 1; switch (pos) { - case 0: { + case Pages::INTRO: { moveToNextPage(pos, nextPos); ui->btnBack->setVisible(true); ui->lineEditName->setFocus(); break; } - case 1: { + case Pages::ALIAS: { // No empty names accepted. if (ui->lineEditName->text().isEmpty()) { setCssEditLine(ui->lineEditName, false, true); @@ -190,7 +207,7 @@ void MasterNodeWizardDialog::accept() ui->lineEditIpAddress->setFocus(); break; } - case 2: { + case Pages::SERVICE: { // No empty address accepted if (ui->lineEditIpAddress->text().isEmpty()) { return; @@ -199,34 +216,45 @@ void MasterNodeWizardDialog::accept() isOk = createMN(); QDialog::accept(); } else { - moveToNextPage(pos, nextPos); + // Ask if the user want to customize the owner, operator and voter addresses and keys + // if not, the process will generate all the values for them and present them in the summary page. + isWaitingForAsk = true; + hide(); } break; } - case 3: { - // todo: add owner page - moveToNextPage(pos, nextPos); - break; - } - case 4: { - // todo: add operator page + case Pages::OWNER: { + if (!validateOwner()) return; // invalid state informed internally moveToNextPage(pos, nextPos); break; } - case 5: { - ui->btnBack->setVisible(false); - ui->btnNext->setText("CLOSE"); - ui->stackedWidget->setCurrentIndex(3); - setSummary(); - break; + case Pages::OPERATOR: { + if (!validateOperator()) return; // invalid state informed internally + completeTask(); + return; } - case 6: { + case Pages::SUMMARY: { QDialog::accept(); + break; } } pos++; } +void MasterNodeWizardDialog::completeTask() +{ + for (auto btn : list_icConfirm) { btn->setVisible(true); } + setBtnsChecked(list_pushNumber, 0, list_pushNumber.size()); + setBtnsChecked(list_pushName, 0, list_pushNumber.size()); + ui->btnBack->setVisible(false); + ui->btnNext->setText("CLOSE"); + pos = Pages::SUMMARY; + ui->stackedWidget->setCurrentIndex(pos); + isOk = createMN(); + if (isOk) setSummary(); + else QDialog::accept(); +} + static void setShortText(QLabel* label, const QString& str, int size) { label->setText(str.left(size) + "..." + str.right(size)); @@ -240,7 +268,51 @@ void MasterNodeWizardDialog::setSummary() setShortText(ui->labelCollateralHash, QString::fromStdString(mnSummary->collateralOut.hash.GetHex()), 20); ui->labelCollateralIndex->setText(QString::number(mnSummary->collateralOut.n)); ui->labelOperatorService->setText(QString::fromStdString(mnSummary->service)); - ui->labelOperatorKey->setText(QString::fromStdString(mnSummary->operatorKey)); + setShortText(ui->labelOperatorKey, QString::fromStdString(mnSummary->operatorKey), 20); + if (mnSummary->operatorPayoutAddr) { + setShortText(ui->labelOperatorPayout, QString::fromStdString(*mnSummary->operatorPayoutAddr), 14); + ui->labelOperatorPercentage->setText(QString::number(mnSummary->operatorPercentage)+ " %"); + } else { + ui->labelOperatorPayout->setText(tr("No address")); + } +} + +CallResult> MasterNodeWizardDialog::getOrCreateOwnerAddress(const std::string& alias) +{ + QString ownerAddrStr(ui->lineEditOwnerAddress->text()); + if (ownerAddrStr.isEmpty()) { + // Create owner addr + const auto ownerAddr = walletModel->getNewAddress("dmn_owner_" + alias); + if (!ownerAddr) return {ownerAddr.getError()}; + const CKeyID* ownerKeyId = ownerAddr.getObjResult()->getKeyID(); + return {{ownerAddr.getObjResult()->ToString(), *ownerKeyId}}; + } else { + if (!walletModel->isMine(ownerAddrStr)) { + return {"Invalid owner address, must be owned by this wallet"}; // Shouldn't happen.. + } + std::string addrStr = ownerAddrStr.toStdString(); + auto opKeyId = walletModel->getKeyIDFromAddr(addrStr); + if (!opKeyId) return {"Invalid owner address id"}; + return {{addrStr, *opKeyId}}; + } +} + +// Future: generalize with getOrCreateOwnerAddress. +CallResult> MasterNodeWizardDialog::getOrCreatePayoutAddress(const std::string& alias) +{ + QString payoutAddrStr(ui->lineEditPayoutAddress->text()); + if (payoutAddrStr.isEmpty()) { + // Create payout addr + const auto ownerAddr = walletModel->getNewAddress("dmn_payout_" + alias); + if (!ownerAddr) return {ownerAddr.getError()}; + const CKeyID* ownerKeyId = ownerAddr.getObjResult()->getKeyID(); + return {{ownerAddr.getObjResult()->ToString(), *ownerKeyId}}; + } else { + std::string addrStr = payoutAddrStr.toStdString(); + auto opKeyId = walletModel->getKeyIDFromAddr(addrStr); + if (!opKeyId) return {"Invalid payout address id"}; + return {{addrStr, *opKeyId}}; + } } bool MasterNodeWizardDialog::createMN() @@ -290,45 +362,55 @@ bool MasterNodeWizardDialog::createMN() } if (isDeterministic) { - // Deterministic - - // For now, create every single key inside the wallet - // later this can be customized by the user. + // 1) Get or create the owner addr + auto opOwnerAddrAndKeyId = getOrCreateOwnerAddress(alias); + if (!opOwnerAddrAndKeyId.getRes()) { + return errorOut(tr(opOwnerAddrAndKeyId.getError().c_str())); + } + auto ownerAddrAndKeyId = opOwnerAddrAndKeyId.getObjResult(); + std::string ownerAddrStr = ownerAddrAndKeyId->first; + CKeyID ownerKeyId = ownerAddrAndKeyId->second; + + // 2) Get or create the payout addr + auto opPayoutAddrAndKeyId = getOrCreatePayoutAddress(alias); + if (!opPayoutAddrAndKeyId.getRes()) { + return errorOut(tr(opPayoutAddrAndKeyId.getError().c_str())); + } + auto payoutAddrAndKeyId = opPayoutAddrAndKeyId.getObjResult(); + std::string payoutAddrStr = payoutAddrAndKeyId->first; + CKeyID payoutKeyId = payoutAddrAndKeyId->second; - // Create owner addr - const auto ownerAddr = walletModel->getNewAddress("dmn_owner_" + alias); - if (!ownerAddr) return errorOut(tr(ownerAddr.getError().c_str())); - const CKeyID* ownerKeyId = ownerAddr.getObjResult()->getKeyID(); - - // Create payout addr - const auto payoutAddr = walletModel->getNewAddress("dmn_payout_" + alias); - if (!payoutAddr) return errorOut(tr(payoutAddr.getError().c_str())); - const std::string& payoutStr{payoutAddr.getObjResult()->ToString()}; + // 3) Get operator data + QString operatorKey = ui->lineEditOperatorKey->text(); + Optional operatorPayoutKeyId = walletModel->getKeyIDFromAddr(ui->lineEditOperatorPayoutAddress->text().toStdString()); + int operatorPercentage = ui->lineEditPercentage->text().isEmpty() ? 0 : (int) ui->lineEditPercentage->text().toUInt(); // For now, collateral key is always inside the wallet std::string error_str; - bool res = mnModel->createDMN(alias, + auto res = mnModel->createDMN(alias, collateralOut, ipAddress, port, ownerKeyId, - nullopt, // generate operator key + operatorKey.isEmpty() ? nullopt : Optional(operatorKey.toStdString()), nullopt, // use owner key as voting key - {payoutStr}, // use owner key as payout script + payoutKeyId, // use owner key as payout script error_str, - nullopt, // operator percentage - nullopt); // operator payout script + operatorPercentage, // operator percentage + operatorPayoutKeyId); // operator payout script if (!res) { return errorOut(tr(error_str.c_str())); } - std::string ownerAddrStr = ownerAddr.getObjResult()->ToString(); + // If the operator key was created locally, let's get it for the summary + // future: move "operatorSk" to a constant field + std::string operatorSk = walletModel->getStrFromTxExtraData(*res.getObjResult(), "operatorSk"); mnSummary = std::make_unique(alias, ipAddress+":"+port, collateralOut, ownerAddrStr, - payoutAddr.getObjResult()->ToString(), - "fa3b23b341ccba23ab398befea2321bc46f", // todo: add real operator key.. + payoutAddrStr, + operatorSk.empty() ? operatorKey.toStdString() : operatorSk, ownerAddrStr, // voting key, for now fixed to owner addr 0, // operator percentage nullopt); // operator payout @@ -350,6 +432,44 @@ bool MasterNodeWizardDialog::createMN() return true; } +bool MasterNodeWizardDialog::validateOwner() +{ + QString ownerAddress(ui->lineEditOwnerAddress->text()); + if (!ownerAddress.isEmpty() && !walletModel->isMine(ownerAddress)) { + setCssEditLine(ui->lineEditOwnerAddress, false, true); + inform(tr("Invalid main address, must be an address from this wallet")); + return false; + } + + QString payoutAddress(ui->lineEditPayoutAddress->text()); + if (!payoutAddress.isEmpty() && !walletModel->validateAddress(payoutAddress)) { + setCssEditLine(ui->lineEditPayoutAddress, false, true); + inform(tr("Invalid payout address")); + return false; + } + + return true; +} + +bool MasterNodeWizardDialog::validateOperator() +{ + QString operatorKey(ui->lineEditOperatorKey->text()); + if (!operatorKey.isEmpty() && !interfaces::g_tiertwo->isBlsPubKeyValid(operatorKey.toStdString())) { + setCssEditLine(ui->lineEditOperatorKey, false, true); + inform(tr("Invalid operator public key")); + return false; + } + + QString payoutAddress(ui->lineEditOperatorPayoutAddress->text()); + if (!payoutAddress.isEmpty() && !walletModel->validateAddress(payoutAddress)) { + setCssEditLine(ui->lineEditOperatorPayoutAddress, false, true); + inform(tr("Invalid payout address")); + return false; + } + + return true; +} + void MasterNodeWizardDialog::moveBack(int backPos) { ui->stackedWidget->setCurrentIndex(backPos); diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index 8f940a40c9609..d07e704ef16ef 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -46,6 +46,15 @@ class MasterNodeWizardDialog : public FocusedDialog, public PWidget::Translator { Q_OBJECT +enum Pages { + INTRO = 0, + ALIAS = 1, + SERVICE = 2, + OWNER = 3, + OPERATOR = 4, + SUMMARY = 5 +}; + public: explicit MasterNodeWizardDialog(WalletModel* walletMode, MNModel* mnModel, @@ -54,9 +63,21 @@ class MasterNodeWizardDialog : public FocusedDialog, public PWidget::Translator void showEvent(QShowEvent *event) override; QString translate(const char *msg) override { return tr(msg); } - QString returnStr = ""; - bool isOk = false; - CMasternodeConfig::CMasternodeEntry* mnEntry = nullptr; + void moveToAdvancedConf() + { + pos = Pages::OWNER; + moveToNextPage(Pages::SERVICE, pos); + }; + + void completeTask(); + + QString returnStr{""}; + bool isOk{false}; + bool isWaitingForAsk{false}; + CMasternodeConfig::CMasternodeEntry* mnEntry{nullptr}; + +Q_SIGNALS: + void message(const QString& title, const QString& body, unsigned int style, bool* ret = nullptr); private Q_SLOTS: void accept() override; @@ -80,6 +101,12 @@ private Q_SLOTS: void moveToNextPage(int currentPos, int nextPos); void moveBack(int backPos); + + bool validateOwner(); + bool validateOperator(); + + CallResult> getOrCreateOwnerAddress(const std::string& alias); + CallResult> getOrCreatePayoutAddress(const std::string& alias); }; #endif // MASTERNODEWIZARDDIALOG_H diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 2c64148f4c2ed..35eaf8a2c7e3a 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -292,16 +292,16 @@ int MNModel::getMasternodeCollateralMinConf() return Params().GetConsensus().MasternodeCollateralMinConf(); } -static OperationResult createDMNInternal(const COutPoint& collateral, - const CKey& keyCollateral, - const CService& service, - const CKeyID& ownerAddr, - const CBLSPublicKey& operatorPubKey, - const Optional& votingAddr, - const CKeyID& payoutAddr, - const Optional& operatorSk, - const Optional& operatorPercentage, - const Optional& operatorPayoutAddr) +static CallResult createDMNInternal(const COutPoint& collateral, + const CKey& keyCollateral, + const CService& service, + const CKeyID& ownerAddr, + const CBLSPublicKey& operatorPubKey, + const Optional& votingAddr, + const CKeyID& payoutAddr, + const Optional& operatorSk, + const Optional& operatorPercentage, + const Optional& operatorPayoutAddr) { ProRegPL pl; pl.nVersion = ProRegPL::CURRENT_VERSION; @@ -311,7 +311,7 @@ static OperationResult createDMNInternal(const COutPoint& collateral, pl.keyIDVoting = votingAddr ? *votingAddr : pl.keyIDOwner; pl.collateralOutpoint = collateral; pl.scriptPayout = GetScriptForDestination(payoutAddr); - if (operatorPercentage) { + if (operatorPayoutAddr) { pl.nOperatorReward = *operatorPercentage; pl.scriptOperatorPayout = GetScriptForDestination(*operatorPayoutAddr); } @@ -324,30 +324,31 @@ static OperationResult createDMNInternal(const COutPoint& collateral, auto wallet = vpwallets[0]; // TODO: Move to walletModel auto res = FundSpecialTx(wallet, tx, pl); - if (!res) return res; + if (!res) return {res.getError()}; res = SignSpecialTxPayloadByString(pl, keyCollateral); - if (!res) return res; + if (!res) return {res.getError()}; std::map extraValues; if (operatorSk) { // Only if the operator sk was provided extraValues.emplace("operatorSk", bls::EncodeSecret(Params(), *operatorSk)); } - return SignAndSendSpecialTx(wallet, tx, pl, &extraValues); + res = SignAndSendSpecialTx(wallet, tx, pl, &extraValues); + return res ? CallResult(tx.GetHash()) : CallResult(res.getError()); } -bool MNModel::createDMN(const std::string& alias, - const COutPoint& collateral, - std::string& serviceAddr, - const std::string& servicePort, - const CKeyID* ownerAddr, - const Optional& operatorPubKey, - const Optional& votingAddr, - const Optional& payoutAddr, - std::string& strError, - const Optional& operatorPercentage, - const Optional& operatorPayoutAddr) +CallResult MNModel::createDMN(const std::string& alias, + const COutPoint& collateral, + std::string& serviceAddr, + const std::string& servicePort, + const CKeyID& ownerAddr, + const Optional& operatorPubKey, + const Optional& votingAddr, + const CKeyID& payoutKeyId, + std::string& strError, + const Optional& operatorPercentage, + const Optional& operatorPayoutAddr) { // Parse and validate inputs @@ -365,22 +366,14 @@ bool MNModel::createDMN(const std::string& alias, if (!serviceAddr.empty()) { if (!Lookup(serviceAddr+":"+servicePort, service, chainparams.GetDefaultPort(), false)) { strError = strprintf("invalid network address %s", serviceAddr); - return false; + return {strError}; } } - // Parse payout script - CKeyID payoutKeyId; - if (payoutAddr) { - auto opPayout = ParsePubKeyIDFromAddress(*payoutAddr, strError); - if (!opPayout) return false; - payoutKeyId = *opPayout; - } - CPubKey pubKeyCollateral; CKey keyCollateral; if (!p_wallet->GetMasternodeVinAndKeys(pubKeyCollateral, keyCollateral, collateral, false, strError)) { - return false; + return {strError}; } // parse operator pubkey or create one @@ -390,7 +383,7 @@ bool MNModel::createDMN(const std::string& alias, auto opPk = bls::DecodePublic(Params(), *operatorPubKey); if (!opPk || !opPk->IsValid()) { strError = "invalid operator pubkey"; - return false; + return {strError}; } operatorPk = *opPk; } else { @@ -401,22 +394,22 @@ bool MNModel::createDMN(const std::string& alias, } auto res = createDMNInternal(collateral, - keyCollateral, - service, - *ownerAddr, - operatorPk, - Optional(*ownerAddr), // voting key - payoutKeyId, // payout script - operatorSk, // only if the operator was provided (or locally created) - nullopt, // operator percentage - nullopt); // operator percentage + keyCollateral, + service, + ownerAddr, + operatorPk, + Optional(ownerAddr), // voting key + payoutKeyId, // payout script + operatorSk, // only if the operator was provided (or locally created) + operatorPercentage, // operator percentage + operatorPayoutAddr); // operator payout keyid if (!res) { strError = res.getError(); - return false; + return {strError}; } // All good - return true; + return res; } OperationResult MNModel::killDMN(const uint256& collateralHash, unsigned int outIndex) diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index da5920aaa0cb9..2ffd8eb5bef8a 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -105,17 +105,18 @@ class MNModel : public QAbstractTableModel // Return the specific chain min conf for the collateral tx int getMasternodeCollateralMinConf(); - bool createDMN(const std::string& alias, - const COutPoint& collateral, - std::string& serviceAddr, - const std::string& servicePort, - const CKeyID* ownerAddr, - const Optional& operatorPubKey, - const Optional& votingAddr, - const Optional& payoutAddr, - std::string& strError, - const Optional& operatorPercentage = nullopt, - const Optional& operatorPayoutAddr = nullopt); + // Creates the DMN and return the hash of the proregtx + CallResult createDMN(const std::string& alias, + const COutPoint& collateral, + std::string& serviceAddr, + const std::string& servicePort, + const CKeyID& ownerAddr, + const Optional& operatorPubKey, + const Optional& votingAddr, + const CKeyID& payoutAddr, + std::string& strError, + const Optional& operatorPercentage = nullopt, + const Optional& operatorPayoutAddr = nullopt); // Completely stops the Masternode spending the collateral OperationResult killDMN(const uint256& collateralHash, unsigned int outIndex); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index b179c4c8d4da6..3a72e046b0a9c 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1063,6 +1063,22 @@ bool WalletModel::isSpent(const COutPoint& outpoint) const return wallet->IsSpent(outpoint.hash, outpoint.n); } +Optional WalletModel::getKeyIDFromAddr(const std::string& addr) +{ + if (addr.empty()) return nullopt; + CTxDestination dest = DecodeDestination(addr); + const CTxDestination* regDest = Standard::GetTransparentDestination(dest); + return (regDest) ? Optional{*boost::get(regDest)} : nullopt; +} + +std::string WalletModel::getStrFromTxExtraData(const uint256& txHash, const std::string& key) +{ + auto tx = wallet->GetWalletTx(txHash); + if (!tx) return ""; + auto it = tx->mapValue.find(key); + return it != tx->mapValue.end() ? it->second : ""; +} + void WalletModel::listCoins(std::map>& mapCoins, bool fTransparent) const { if (fTransparent) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index a0a1bcca0bd90..a7bcacdbee772 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -305,6 +305,12 @@ class WalletModel : public QObject int getWalletTxDepth(const uint256& txHash) const; bool isSpent(const COutPoint& outpoint) const; + // Parse the addr and return the keyid if valid + Optional getKeyIDFromAddr(const std::string& addr); + + // Gets the extra data key->value + std::string getStrFromTxExtraData(const uint256& txHash, const std::string& key); + class ListCoinsKey { public: QString address; From 0ad9d4bfb7a86176eedf07301d988472e109fd83 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 7 Feb 2022 12:12:46 -0300 Subject: [PATCH 23/82] GUI: add copy to clipboard action to DMN creation summary page --- src/Makefile.qt.include | 4 +- src/qt/CMakeLists.txt | 6 +++ src/qt/pivx/clickablelabel.h | 27 +++++++++++ src/qt/pivx/forms/masternodewizarddialog.ui | 23 +++++---- src/qt/pivx/masternodewizarddialog.cpp | 52 +++++++++++++++++---- src/qt/pivx/masternodewizarddialog.h | 3 ++ 6 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 src/qt/pivx/clickablelabel.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 248a2ebac8fde..b50016d9be8cb 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -111,6 +111,7 @@ QT_MOC_CPP = \ qt/pivx/moc_furabstractlistitemdelegate.cpp \ qt/pivx/moc_receivedialog.cpp \ qt/pivx/moc_pfborderimage.cpp \ + qt/pivx/moc_clickablelabel.cpp \ qt/pivx/moc_topbar.cpp \ qt/pivx/moc_txrow.cpp \ qt/pivx/moc_dashboardwidget.cpp \ @@ -275,7 +276,8 @@ BITCOIN_QT_H = \ qt/pivx/settings/settingswalletrepairwidget.h \ qt/pivx/settings/settingswidget.h \ qt/pivx/welcomecontentwidget.h \ - qt/pivx/splash.h + qt/pivx/splash.h \ + qt/pivx/clickablelabel.h RES_ICONS = \ qt/res/icons/bitcoin.ico \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index bc64cd3b04429..b409b9a4b8b65 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -163,13 +163,19 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/welcomecontentwidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/splash.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/governancemodel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/clickablelabel.h ) execute_process( COMMAND ${MOC_BIN} -o moc_pfborderimage.cpp pfborderimage.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/pivx ) +execute_process( + COMMAND ${MOC_BIN} -o moc_clickablelabel.cpp clickablelabel.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/pivx +) list(APPEND QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/moc_pfborderimage.cpp) +list(APPEND QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/moc_clickablelabel.cpp) if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") list(APPEND QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/macdockiconhandler.mm) diff --git a/src/qt/pivx/clickablelabel.h b/src/qt/pivx/clickablelabel.h new file mode 100644 index 0000000000000..9276cf335509d --- /dev/null +++ b/src/qt/pivx/clickablelabel.h @@ -0,0 +1,27 @@ +// Copyright (c) 2022 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_CLICKABLELABEL_H +#define PIVX_CLICKABLELABEL_H + +#include +#include + +class ClickableLabel : public QLabel { +Q_OBJECT + +public: + explicit ClickableLabel(QWidget* parent = nullptr, + Qt::WindowFlags f = Qt::WindowFlags()) : QLabel(parent) {}; + ~ClickableLabel() override = default; + +Q_SIGNALS: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent* event) override { Q_EMIT clicked(); } +}; + + +#endif //PIVX_CLICKABLELABEL_H diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index 6901fbe7e7972..e8589dae1b6d1 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -1828,7 +1828,7 @@ - + N/A @@ -1866,7 +1866,7 @@ - + N/A @@ -1928,7 +1928,7 @@ - + N/A @@ -1963,7 +1963,7 @@ - + N/A @@ -2047,7 +2047,7 @@ - + N/A @@ -2085,7 +2085,7 @@ - + N/A @@ -2147,7 +2147,7 @@ - + N/A @@ -2182,7 +2182,7 @@ - + 0.00 @@ -2286,6 +2286,13 @@ + + + ClickableLabel + QLabel +
qt/pivx/clickablelabel.h
+
+
diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 90889bd8df51d..d7b41902a003c 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -7,6 +7,7 @@ #include "key_io.h" #include "interfaces/tiertwo.h" +#include "qt/pivx/clickablelabel.h" #include "qt/pivx/mnmodel.h" #include "qt/pivx/qtutils.h" #include "qt/walletmodel.h" @@ -136,16 +137,7 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM ui->lineEditPercentage->setValidator(new QIntValidator(1, 99)); // Frame Summary - setCssProperty(ui->labelSummary, "text-title-dialog"); - setCssProperty({ui->containerOwner, ui->containerOperator}, "card-governance"); - setCssProperty({ui->labelOwnerSection, ui->labelOperatorSection}, "text-section-title"); - setCssProperty({ui->labelTitleMainAddr, ui->labelTitlePayoutAddr, ui->labelTitleCollateral, ui->labelTitleCollateralIndex, - ui->labelTitleOperatorKey, ui->labelTitleOperatorService, ui->labelTitleOperatorPayout, ui->labelTitleOperatorPercentage, - ui->labelTitleOperatorService}, "text-title-right"); - setCssProperty({ui->labelMainAddr, ui->labelPayoutAddr, ui->labelCollateralIndex, ui->labelCollateralHash, - ui->labelOperatorKey, ui->labelOperatorPayout, ui->labelOperatorPercentage, ui->labelOperatorService}, "text-body2-dialog"); - setCardShadow(ui->containerOwner); - setCardShadow(ui->containerOperator); + initSummaryPage(); // Confirm icons ui->stackedIcon1->addWidget(list_icConfirm[0]); @@ -166,6 +158,46 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM connect(ui->btnBack, &QPushButton::clicked, this, &MasterNodeWizardDialog::onBackClicked); } +void MasterNodeWizardDialog::initSummaryPage() +{ + setCssProperty(ui->labelSummary, "text-title-dialog"); + setCssProperty({ui->containerOwner, ui->containerOperator}, "card-governance"); + setCssProperty({ui->labelOwnerSection, ui->labelOperatorSection}, "text-section-title"); + setCssProperty({ui->labelTitleMainAddr, ui->labelTitlePayoutAddr, ui->labelTitleCollateral, ui->labelTitleCollateralIndex, + ui->labelTitleOperatorKey, ui->labelTitleOperatorService, ui->labelTitleOperatorPayout, ui->labelTitleOperatorPercentage, + ui->labelTitleOperatorService}, "text-title-right"); + setCssProperty({ui->labelMainAddr, ui->labelPayoutAddr, ui->labelCollateralIndex, ui->labelCollateralHash, + ui->labelOperatorKey, ui->labelOperatorPayout, ui->labelOperatorPercentage, ui->labelOperatorService}, "text-body2-dialog"); + setCardShadow(ui->containerOwner); + setCardShadow(ui->containerOperator); + + connect(ui->labelMainAddr, &ClickableLabel::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(mnSummary->ownerAddr)); + inform(tr("Owner address copied to clipboard")); + }); + connect(ui->labelPayoutAddr, &ClickableLabel::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(mnSummary->ownerPayoutAddr)); + inform(tr("Payout address copied to clipboard")); + }); + connect(ui->labelCollateralHash, &ClickableLabel::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(mnSummary->collateralOut.hash.GetHex())); + inform(tr("Collateral tx hash copied to clipboard")); + }); + connect(ui->labelOperatorService, &ClickableLabel::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(mnSummary->service)); + inform(tr("Service copied to clipboard")); + }); + connect(ui->labelOperatorKey, &ClickableLabel::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(mnSummary->operatorKey)); + inform(tr("Operator key copied to clipboard")); + }); + connect(ui->labelOperatorPayout, &ClickableLabel::clicked, [this](){ + if (!mnSummary->operatorPayoutAddr) return; + GUIUtil::setClipboard(QString::fromStdString(*mnSummary->operatorPayoutAddr)); + inform(tr("Operator payout address copied to clipboard")); + }); +} + void MasterNodeWizardDialog::showEvent(QShowEvent *event) { if (ui->btnNext) ui->btnNext->setFocus(); diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index d07e704ef16ef..3da2529b54720 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -94,6 +94,9 @@ private Q_SLOTS: WalletModel* walletModel{nullptr}; MNModel* mnModel{nullptr}; + + void initSummaryPage(); + bool createMN(); void setSummary(); void inform(const QString& text); From 063c103f18593d5471fb591fd61f7a0cb2e442ce Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Feb 2022 09:21:48 -0300 Subject: [PATCH 24/82] GUI: add subtitle to masternode creation wizard summary page --- src/qt/pivx/forms/masternodewizarddialog.ui | 27 ++++++++++++++++++--- src/qt/pivx/masternodewizarddialog.cpp | 1 + 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index e8589dae1b6d1..9c2fded978d3b 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -929,7 +929,7 @@ 20 - 15 + 14 @@ -1718,9 +1718,9 @@
- + - 6 + 0 0 @@ -1739,7 +1739,7 @@ 0 - 50 + 25 @@ -1753,6 +1753,25 @@
+ + + + + 360 + 0 + + + + (Press on any label to copy the content) + + + Qt::AlignCenter + + + true + + + diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index d7b41902a003c..aba0e3ce9d8a8 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -161,6 +161,7 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM void MasterNodeWizardDialog::initSummaryPage() { setCssProperty(ui->labelSummary, "text-title-dialog"); + setCssSubtitleScreen(ui->labelSubtitleSummary); setCssProperty({ui->containerOwner, ui->containerOperator}, "card-governance"); setCssProperty({ui->labelOwnerSection, ui->labelOperatorSection}, "text-section-title"); setCssProperty({ui->labelTitleMainAddr, ui->labelTitlePayoutAddr, ui->labelTitleCollateral, ui->labelTitleCollateralIndex, From 35bbe58914bd175b6cb041d8ebfe34cc4684568f Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Feb 2022 10:19:02 -0300 Subject: [PATCH 25/82] GUI: do not invalidate addresses filter when the new filter type is equal to the previous type --- src/qt/pivx/addressfilterproxymodel.cpp | 3 ++- src/qt/pivx/contactsdropdown.cpp | 15 ++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/qt/pivx/addressfilterproxymodel.cpp b/src/qt/pivx/addressfilterproxymodel.cpp index 314a0004a2850..770a9301a6caa 100644 --- a/src/qt/pivx/addressfilterproxymodel.cpp +++ b/src/qt/pivx/addressfilterproxymodel.cpp @@ -31,7 +31,8 @@ void AddressFilterProxyModel::setType(const QString& type) void AddressFilterProxyModel::setType(const QStringList& types) { - this->m_types = types; + if (m_types == types) return; + m_types = types; invalidateFilter(); } diff --git a/src/qt/pivx/contactsdropdown.cpp b/src/qt/pivx/contactsdropdown.cpp index 639543db82abf..3d4def0b6b1c4 100644 --- a/src/qt/pivx/contactsdropdown.cpp +++ b/src/qt/pivx/contactsdropdown.cpp @@ -6,7 +6,6 @@ #include #include -#include "qt/pivx/addresslabelrow.h" #include "qt/pivx/contactdropdownrow.h" #include "qt/pivx/qtutils.h" #include "qt/pivx/furlistrow.h" @@ -19,8 +18,6 @@ class ContViewHolder : public FurListRow { public: - ContViewHolder(); - explicit ContViewHolder(bool _isLightTheme) : FurListRow(), isLightTheme(_isLightTheme){} ContactDropdownRow* createHolder(int pos) override{ @@ -50,13 +47,13 @@ class ContViewHolder : public FurListRow ContactsDropdown::ContactsDropdown(int minWidth, int minHeight, PIVXGUI* _window, QWidget* _parent) : PWidget(_window, _parent) { - this->setStyleSheet(_window->styleSheet()); + setStyleSheet(_window->styleSheet()); init(minWidth, minHeight); } ContactsDropdown::ContactsDropdown(int minWidth, int minHeight, PWidget* parent) : PWidget(parent) { - this->setStyleSheet(parent->styleSheet()); + setStyleSheet(parent->styleSheet()); init(minWidth, minHeight); } @@ -92,10 +89,10 @@ void ContactsDropdown::init(int minWidth, int minHeight) void ContactsDropdown::setWalletModel(WalletModel* _model, const QStringList& type){ if (!model) { model = _model->getAddressTableModel(); - this->filter = new AddressFilterProxyModel(type, this); - this->filter->setSourceModel(model); - this->filter->sort(AddressTableModel::Label, Qt::AscendingOrder); - list->setModel(this->filter); + filter = new AddressFilterProxyModel(type, this); + filter->setSourceModel(model); + filter->sort(AddressTableModel::Label, Qt::AscendingOrder); + list->setModel(filter); list->setModelColumn(AddressTableModel::Address); } else { setType(type); From 41df6ecc9c8b059c97e696f648942483313d8bc6 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Feb 2022 10:23:50 -0300 Subject: [PATCH 26/82] GUI: masternode creation wizard, connect dropdown list to owner address edit line. --- src/qt/pivx/masternodewizarddialog.cpp | 58 ++++++++++++++++++++++++++ src/qt/pivx/masternodewizarddialog.h | 8 ++++ 2 files changed, 66 insertions(+) diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index aba0e3ce9d8a8..75d8747d2cff8 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -126,6 +126,7 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM setCssProperty({ui->labelSubtitleOwnerAddress, ui->labelSubtitlePayoutAddress}, "text-title"); initCssEditLine(ui->lineEditOwnerAddress); initCssEditLine(ui->lineEditPayoutAddress); + setDropdownList(ui->lineEditOwnerAddress, actOwnerAddrList, {AddressTableModel::Receive}); // Frame Operator setCssProperty(ui->labelTitle6, "text-title-dialog"); @@ -529,6 +530,63 @@ void MasterNodeWizardDialog::onBackClicked() } } +void MasterNodeWizardDialog::setDropdownList(QLineEdit* edit, QAction* action, const QStringList& types) +{ + action = edit->addAction(QIcon("://ic-contact-arrow-down"), QLineEdit::TrailingPosition); + connect(action, &QAction::triggered, [this, types, edit](){ onAddrListClicked(types, edit); }); +} + +// TODO: Connect it to every address editable box +void MasterNodeWizardDialog::onAddrListClicked(const QStringList& types, QLineEdit* edit) +{ + const auto& addrModel = walletModel->getAddressTableModel(); + int addrSize = 0; + for (const auto& type : types) { + if (type == AddressTableModel::Send) { + addrSize += addrModel->sizeSend(); + } else if (type == AddressTableModel::Receive) { + addrSize += addrModel->sizeRecv(); + } + } + if (addrSize == 0) { + inform(tr("No addresses available")); + return; + } + + int height = 70 * 2 + 1; // 2 rows (70 each row). + int width = edit->width(); + if (!dropdownMenu) { + // TODO: add different row icon for contacts and own addresses. + // TODO: add filter/search option. + dropdownMenu = new ContactsDropdown( + width, + height, + dynamic_cast(parent()), + this + ); + // TODO: Update connection every time that a new 'edit' is provided + connect(dropdownMenu, &ContactsDropdown::contactSelected, [edit](const QString& address, const QString& label) { + edit->setText(address); + }); + + } + if (dropdownMenu->isVisible()) { + dropdownMenu->hide(); + return; + } + + dropdownMenu->setWalletModel(walletModel, types); + dropdownMenu->resizeList(width, height); + dropdownMenu->setStyleSheet(styleSheet()); + dropdownMenu->adjustSize(); + + QPoint position = edit->parentWidget()->mapToGlobal(edit->rect().bottomLeft()); + position.setY(position.y() - 8); // Minus spacing + position.setX(position.x() - width / 2 - 8); + dropdownMenu->move(position); + dropdownMenu->show(); +} + void MasterNodeWizardDialog::inform(const QString& text) { if (!snackBar) diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index 3da2529b54720..164e787940a5b 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -10,7 +10,9 @@ #include "masternodeconfig.h" #include "qt/pivx/pwidget.h" +class ContactsDropdown; class MNModel; +class QLineEdit; class WalletModel; namespace Ui { @@ -84,6 +86,9 @@ private Q_SLOTS: void onBackClicked(); private: Ui::MasterNodeWizardDialog *ui; + ContactsDropdown* dropdownMenu{nullptr}; + QAction* actOwnerAddrList{nullptr}; + QList list_icConfirm{}; QList list_pushNumber{}; QList list_pushName{}; @@ -110,6 +115,9 @@ private Q_SLOTS: CallResult> getOrCreateOwnerAddress(const std::string& alias); CallResult> getOrCreatePayoutAddress(const std::string& alias); + + void setDropdownList(QLineEdit* edit, QAction* action, const QStringList& types); + void onAddrListClicked(const QStringList& types, QLineEdit* edit); }; #endif // MASTERNODEWIZARDDIALOG_H From f77dc7bf71ff9ce80a6074d52d72384d04bf31d6 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Feb 2022 15:13:38 -0300 Subject: [PATCH 27/82] interfaces::tiertwo, implement function to get single DMN data. --- src/interfaces/tiertwo.cpp | 20 ++++++++++++++++++++ src/interfaces/tiertwo.h | 14 ++++++++++++++ src/qt/walletmodel.cpp | 5 +---- src/wallet/wallet.cpp | 8 ++++++++ src/wallet/wallet.h | 3 +++ 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/interfaces/tiertwo.cpp b/src/interfaces/tiertwo.cpp index 9727f6fc197fb..858d7bb7c80b3 100644 --- a/src/interfaces/tiertwo.cpp +++ b/src/interfaces/tiertwo.cpp @@ -20,6 +20,26 @@ bool TierTwo::isBlsPubKeyValid(const std::string& blsKey) return opKey && opKey->IsValid(); } +Optional TierTwo::getDMNData(const uint256& pro_tx_hash, const CBlockIndex* tip) +{ + if (!tip) return nullopt; + const auto& params = Params(); + CDeterministicMNCPtr ptr_mn = deterministicMNManager->GetListForBlock(tip).GetMN(pro_tx_hash); + if (!ptr_mn) return nullopt; + DMNData data; + data.ownerMainAddr = EncodeDestination(ptr_mn->pdmnState->keyIDOwner); + data.ownerPayoutAddr = EncodeDestination(ptr_mn->pdmnState->scriptPayout); + data.operatorPk = bls::EncodePublic(params, ptr_mn->pdmnState->pubKeyOperator.Get()); + data.operatorPayoutAddr = EncodeDestination(ptr_mn->pdmnState->scriptOperatorPayout); + data.operatorPayoutPercentage = ptr_mn->nOperatorReward; + data.votingAddr = EncodeDestination(ptr_mn->pdmnState->keyIDVoting); + if (!vpwallets.empty()) { + CWallet* p_wallet = vpwallets[0]; + data.operatorSk = p_wallet->GetStrFromTxExtraData(pro_tx_hash, "operatorSk"); + } + return {data}; +} + std::shared_ptr createDMNViewIfMine(CWallet* pwallet, const CDeterministicMNCPtr& dmn) { bool hasOwnerKey; diff --git a/src/interfaces/tiertwo.h b/src/interfaces/tiertwo.h index 26aac573dad89..934e5dbc0c8ab 100644 --- a/src/interfaces/tiertwo.h +++ b/src/interfaces/tiertwo.h @@ -29,6 +29,17 @@ class DMNView { COutPoint collateralOut; }; +struct DMNData +{ + std::string ownerMainAddr; + std::string ownerPayoutAddr; + std::string operatorPk; + std::string operatorSk; + Optional operatorPayoutAddr; + int operatorPayoutPercentage{0}; + std::string votingAddr; +}; + namespace interfaces { class TierTwo : public CValidationInterface { @@ -52,6 +63,9 @@ class TierTwo : public CValidationInterface { // future: add filter to return by owner, operator, voter or a combination of them. std::vector> getKnownDMNs() { return WITH_LOCK(cs_cache, return m_cached_dmns;); }; + // Retrieve the DMNData if the DMN exists + Optional getDMNData(const uint256& proTxHash, const CBlockIndex* tip); + void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) override; }; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 3a72e046b0a9c..e09a11c5395a8 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1073,10 +1073,7 @@ Optional WalletModel::getKeyIDFromAddr(const std::string& addr) std::string WalletModel::getStrFromTxExtraData(const uint256& txHash, const std::string& key) { - auto tx = wallet->GetWalletTx(txHash); - if (!tx) return ""; - auto it = tx->mapValue.find(key); - return it != tx->mapValue.end() ? it->second : ""; + return wallet->GetStrFromTxExtraData(txHash, key); } void WalletModel::listCoins(std::map>& mapCoins, bool fTransparent) const diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 19fc21b4e25f9..98f4b654adfd5 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -701,6 +701,14 @@ bool CWallet::HasSaplingSPKM() const return GetSaplingScriptPubKeyMan()->IsEnabled(); } +std::string CWallet::GetStrFromTxExtraData(const uint256& txHash, const std::string& key) +{ + auto tx = GetWalletTx(txHash); + if (!tx) return ""; + auto it = tx->mapValue.find(key); + return it != tx->mapValue.end() ? it->second : ""; +} + /** * Outpoint is spent if any non-conflicted transaction * spends it: diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index ba415fa573a4a..f8b4f13076986 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -712,6 +712,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool HasSaplingSPKM() const; + // Returns the extra value associated with the key. + std::string GetStrFromTxExtraData(const uint256& txHash, const std::string& key); + /* * Main wallet lock. * This lock protects all the fields added by CWallet. From 19cc68fe237a16698bb2321bd296c99cd96fcde8 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Feb 2022 15:14:24 -0300 Subject: [PATCH 28/82] GUI: Implement and connect MN information dialog for Deterministic Masternodes. --- src/qt/clientmodel.h | 1 + src/qt/pivx/forms/mninfodialog.ui | 300 ++++++++++++++++++++++++- src/qt/pivx/masternodeswidget.cpp | 10 +- src/qt/pivx/masternodewizarddialog.cpp | 5 - src/qt/pivx/mninfodialog.cpp | 88 ++++++-- src/qt/pivx/mninfodialog.h | 15 +- src/qt/pivx/mnmodel.cpp | 3 + src/qt/pivx/mnmodel.h | 1 + src/qt/pivx/qtutils.cpp | 14 ++ src/qt/pivx/qtutils.h | 3 + 10 files changed, 405 insertions(+), 35 deletions(-) diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index fd531ea190dfc..dc9c4cc34f010 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -69,6 +69,7 @@ class ClientModel : public QObject uint256 getLastBlockProcessed() const; int getLastBlockProcessedHeight() const; int64_t getLastBlockProcessedTime() const; + const CBlockIndex* getLastBlockIndexProcessed() const { return cacheTip; } double getVerificationProgress() const; bool isTipCached() const; diff --git a/src/qt/pivx/forms/mninfodialog.ui b/src/qt/pivx/forms/mninfodialog.ui index 27e9744faca1f..d6bb684162a6d 100644 --- a/src/qt/pivx/forms/mninfodialog.ui +++ b/src/qt/pivx/forms/mninfodialog.ui @@ -6,13 +6,13 @@ 0 0 - 574 + 700 700 - 574 + 650 530 @@ -206,8 +206,8 @@ background:transparent; 0 0 - 570 - 596 + 682 + 599 @@ -306,6 +306,152 @@ background:transparent; + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + 0 + + + + + + 16777215 + 16777215 + + + + Owner Payout Address: + + + + + + + N/A + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Qt::NoFocus + + + + + + + 24 + 24 + + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + 0 + + + + + + 16777215 + 16777215 + + + + Voter Address: + + + + + + + N/A + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Qt::NoFocus + + + + + + + 24 + 24 + + + + + + + @@ -322,6 +468,152 @@ background:transparent; + + + + + 0 + + + + + + 16777215 + 16777215 + + + + Operator Public Key: + + + + + + + N/A + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Qt::NoFocus + + + + + + + 24 + 24 + + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + 0 + + + + + + 16777215 + 16777215 + + + + Operator Secret Key: + + + + + + + N/A + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Qt::NoFocus + + + + + + + 24 + 24 + + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 3f831244819eb..ddd65792c02cd 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -13,6 +13,7 @@ #include "clientmodel.h" #include "guiutil.h" +#include "interfaces/tiertwo.h" #include "qt/pivx/mnmodel.h" #include "qt/pivx/optionbutton.h" #include "qt/walletmodel.h" @@ -317,7 +318,14 @@ void MasterNodesWidget::onInfoMNClicked() QString txId = index.sibling(index.row(), MNModel::COLLATERAL_ID).data(Qt::DisplayRole).toString(); QString outIndex = index.sibling(index.row(), MNModel::COLLATERAL_OUT_INDEX).data(Qt::DisplayRole).toString(); QString pubKey = index.sibling(index.row(), MNModel::PUB_KEY).data(Qt::DisplayRole).toString(); - dialog->setData(pubKey, label, address, txId, outIndex, status); + bool isLegacy = ((uint16_t) index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt()) == MNViewType::LEGACY; + Optional opDMN = nullopt; + if (!isLegacy) { + QString proTxHash = index.sibling(index.row(), MNModel::PRO_TX_HASH).data(Qt::DisplayRole).toString(); + opDMN = interfaces::g_tiertwo->getDMNData(uint256S(proTxHash.toStdString()), + clientModel->getLastBlockIndexProcessed()); + } + dialog->setData(pubKey, label, address, txId, outIndex, status, opDMN); dialog->adjustSize(); showDialog(dialog, 3, 17); if (dialog->exportMN) { diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 75d8747d2cff8..230828d3db508 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -289,11 +289,6 @@ void MasterNodeWizardDialog::completeTask() else QDialog::accept(); } -static void setShortText(QLabel* label, const QString& str, int size) -{ - label->setText(str.left(size) + "..." + str.right(size)); -} - void MasterNodeWizardDialog::setSummary() { assert(mnSummary); diff --git a/src/qt/pivx/mninfodialog.cpp b/src/qt/pivx/mninfodialog.cpp index 20003aea3e09d..845ff131bdd05 100644 --- a/src/qt/pivx/mninfodialog.cpp +++ b/src/qt/pivx/mninfodialog.cpp @@ -16,38 +16,80 @@ MnInfoDialog::MnInfoDialog(QWidget *parent) : this->setStyleSheet(parent->styleSheet()); setCssProperty(ui->frame, "container-dialog"); setCssProperty(ui->labelTitle, "text-title-dialog"); - setCssTextBodyDialog({ui->labelAmount, ui->labelSend, ui->labelInputs, ui->labelFee, ui->labelId}); - setCssProperty({ui->labelDivider1, ui->labelDivider4, ui->labelDivider6, ui->labelDivider7, ui->labelDivider8, ui->labelDivider9}, "container-divider"); - setCssTextBodyDialog({ui->textAmount, ui->textAddress, ui->textInputs, ui->textStatus, ui->textId, ui->textExport}); - setCssProperty({ui->pushCopy, ui->pushCopyId, ui->pushExport}, "ic-copy-big"); + setCssTextBodyDialog({ui->labelAmount, ui->labelSend, ui->labelInputs, ui->labelFee, ui->labelId, + ui->labelOwnerPayoutAddr, ui->labelOperatorPubKey, ui->labelOperatorSk, ui->labelVoterAddr}); + setCssProperty({ui->labelDivider1, ui->labelDivider4, ui->labelDivider6, ui->labelDivider7, + ui->labelDivider8, ui->labelDivider9, ui->labelDivider10, ui->labelDivider11, + ui->labelDivider12, ui->labelDivider13}, "container-divider"); + setCssTextBodyDialog({ui->textAmount, ui->textAddress, ui->textInputs, ui->textStatus, + ui->textId, ui->textExport, ui->textOwnerPayoutAddr, ui->textOperatorPubKey, ui->textOperatorSk, + ui->textVoterAddr}); + setCssProperty({ui->pushCopy, ui->pushCopyId, ui->pushExport, ui->pushCopyOwnerPayoutAddr, + ui->pushCopyOperatorPubKey, ui->pushCopyOperatorSk, ui->pushCopyVoterAddr}, "ic-copy-big"); setCssProperty(ui->btnEsc, "ic-close"); connect(ui->btnEsc, &QPushButton::clicked, this, &MnInfoDialog::close); - connect(ui->pushCopy, &QPushButton::clicked, [this](){ copyInform(pubKey, tr("Masternode public key copied")); }); + connect(ui->pushCopy, &QPushButton::clicked, [this](){ + if (dmnData) copyInform(QString::fromStdString(dmnData->ownerMainAddr), tr("Masternode owner address copied")); + else copyInform(pubKey, tr("Masternode public key copied")); + }); connect(ui->pushCopyId, &QPushButton::clicked, [this](){ copyInform(txId, tr("Collateral tx id copied")); }); connect(ui->pushExport, &QPushButton::clicked, [this](){ exportMN = true; accept(); }); + connect(ui->pushCopyOperatorPubKey, &QPushButton::clicked, [this](){ + if (dmnData) copyInform(QString::fromStdString(dmnData->operatorPk), tr("Operator public key copied")); + }); + connect(ui->pushCopyOperatorSk, &QPushButton::clicked, [this](){ + if (dmnData) copyInform(QString::fromStdString(dmnData->operatorSk), tr("Operator secret key copied")); + }); + connect(ui->pushCopyOwnerPayoutAddr, &QPushButton::clicked, [this](){ + if (dmnData) copyInform(QString::fromStdString(dmnData->ownerPayoutAddr), tr("Owner payout script copied")); + }); + connect(ui->pushCopyVoterAddr, &QPushButton::clicked, [this](){ + if (dmnData) copyInform(QString::fromStdString(dmnData->votingAddr), tr("Voter address copied")); + }); } -void MnInfoDialog::setData(const QString& _pubKey, const QString& name, const QString& address, const QString& _txId, const QString& outputIndex, const QString& status) +void MnInfoDialog::setDMNDataVisible(bool show) { - this->pubKey = _pubKey; - this->txId = _txId; - QString shortPubKey = _pubKey; - QString shortTxId = _txId; - QString shortAddress = address; - if (shortPubKey.length() > 20) { - shortPubKey = shortPubKey.left(13) + "..." + shortPubKey.right(13); - } - if (shortTxId.length() > 20) { - shortTxId = shortTxId.left(12) + "..." + shortTxId.right(12); - } - if (shortAddress.length() >= 40) { - shortAddress = shortAddress.left(11) + "..." + shortAddress.right(20); - } - ui->textId->setText(shortPubKey); - ui->textAddress->setText(shortAddress); - ui->textAmount->setText(shortTxId); + ui->contentOwnerPayoutAddr->setVisible(show); + ui->contentOperatorPubKey->setVisible(show); + ui->contentOperatorSk->setVisible(show); + ui->contentVoterAddr->setVisible(show); + ui->labelDivider9->setVisible(show); + ui->labelDivider11->setVisible(show); + ui->labelDivider12->setVisible(show); + ui->labelDivider13->setVisible(show); +} + +void MnInfoDialog::setData(const QString& _pubKey, + const QString& name, + const QString& address, + const QString& _txId, + const QString& outputIndex, + const QString& status, + const Optional& _dmnData) +{ + pubKey = _pubKey; + txId = _txId; + setShortTextIfExceedSize(ui->textId, _pubKey, 13, 20); + setShortTextIfExceedSize(ui->textAmount, _txId, 12, 20); + setShortTextIfExceedSize(ui->textAddress, address, 12, 40); ui->textInputs->setText(outputIndex); ui->textStatus->setText(status); + + dmnData = _dmnData; + if (dmnData) { + setDMNDataVisible(true); + ui->labelId->setText(tr("Owner Address")); + setShortText(ui->textId, QString::fromStdString(dmnData->ownerMainAddr), 15); + setShortText(ui->textOwnerPayoutAddr, QString::fromStdString(dmnData->ownerPayoutAddr), 15); + setShortText(ui->textVoterAddr, QString::fromStdString(dmnData->votingAddr), 15); + setShortTextIfExceedSize(ui->textOperatorPubKey, QString::fromStdString(dmnData->operatorPk), 12, 20); + if (!dmnData->operatorSk.empty()) + setShortTextIfExceedSize(ui->textOperatorSk, QString::fromStdString(dmnData->operatorSk), 12, 20); + } else { + ui->labelId->setText(tr("Public Key")); + setDMNDataVisible(false); + } } void MnInfoDialog::copyInform(const QString& copyStr, const QString& message) diff --git a/src/qt/pivx/mninfodialog.h b/src/qt/pivx/mninfodialog.h index 1e8a56642fe5a..a72c5c3c5eac1 100644 --- a/src/qt/pivx/mninfodialog.h +++ b/src/qt/pivx/mninfodialog.h @@ -8,6 +8,9 @@ #include "qt/pivx/focuseddialog.h" #include "qt/pivx/snackbar.h" +#include "interfaces/tiertwo.h" +#include "optional.h" + class WalletModel; namespace Ui { @@ -20,11 +23,17 @@ class MnInfoDialog : public FocusedDialog public: explicit MnInfoDialog(QWidget *parent = nullptr); - ~MnInfoDialog(); + ~MnInfoDialog() override; bool exportMN = false; - void setData(const QString& _pubKey, const QString& name, const QString& address, const QString& _txId, const QString& outputIndex, const QString& status); + void setData(const QString& _pubKey, + const QString& name, + const QString& address, + const QString& _txId, + const QString& outputIndex, + const QString& status, + const Optional& dmnData); public Q_SLOTS: void reject() override; @@ -36,8 +45,10 @@ public Q_SLOTS: WalletModel *model = nullptr; QString txId; QString pubKey; + Optional dmnData{nullopt}; void copyInform(const QString& copyStr, const QString& message); + void setDMNDataVisible(bool show); }; #endif // MNINFODIALOG_H diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 35eaf8a2c7e3a..75f7bec055660 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -193,6 +193,9 @@ QVariant MNModel::data(const QModelIndex &index, int role) const case IS_POSE_ENABLED:{ return mnWrapper.dmnView && !mnWrapper.dmnView->isPoSeBanned; } + case PRO_TX_HASH:{ + if (mnWrapper.dmnView) return QString::fromStdString(mnWrapper.dmnView->proTxHash.GetHex()); + } } } case Qt::ToolTipRole: diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index 2ffd8eb5bef8a..354759114d551 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -77,6 +77,7 @@ class MNModel : public QAbstractTableModel WAS_COLLATERAL_ACCEPTED = 9, TYPE = 10, /**< Whether is from a Legacy or Deterministic MN */ IS_POSE_ENABLED = 11, /**< Whether the DMN is enabled or not*/ + PRO_TX_HASH = 12, /**< The DMN pro reg hash */ COLUMN_COUNT }; diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index 379c9a9b69291..f6bf865d8e2ad 100644 --- a/src/qt/pivx/qtutils.cpp +++ b/src/qt/pivx/qtutils.cpp @@ -362,3 +362,17 @@ void forceUpdateStyle(std::initializer_list args) forceUpdateStyle(w, true); } } + +void setShortTextIfExceedSize(QLabel* label, const QString& str, int cut, int size) +{ + if (str.length() > size) { + setShortText(label, str, cut); + } else { + label->setText(str); + } +} + +void setShortText(QLabel* label, const QString& str, int size) +{ + label->setText(str.left(size) + "..." + str.right(size)); +} diff --git a/src/qt/pivx/qtutils.h b/src/qt/pivx/qtutils.h index 64ad240e1af5a..8b69724b674d4 100644 --- a/src/qt/pivx/qtutils.h +++ b/src/qt/pivx/qtutils.h @@ -77,4 +77,7 @@ void setCssProperty(QWidget* wid, const QString& value, bool forceUpdate = false void forceUpdateStyle(QWidget* widget, bool forceUpdate); void forceUpdateStyle(std::initializer_list args); +void setShortTextIfExceedSize(QLabel* label, const QString& str, int cut, int size); +void setShortText(QLabel* label, const QString& str, int size); + #endif // QTUTILS_H From 19154cfda1e64fc001fa1061719397d57b6f0217 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 9 Feb 2022 10:43:14 -0300 Subject: [PATCH 29/82] GUI: add export DMN functionality --- src/qt/pivx/masternodeswidget.cpp | 18 ++++++++++++------ src/qt/pivx/masternodewizarddialog.cpp | 11 +++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index ddd65792c02cd..7dcc3c58cfe72 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -329,16 +329,22 @@ void MasterNodesWidget::onInfoMNClicked() dialog->adjustSize(); showDialog(dialog, 3, 17); if (dialog->exportMN) { + QString legacyText = isLegacy ? tr(" Then start the Masternode using\nthis controller wallet (select the Masternode in the list and press \"start\").") : ""; if (ask(tr("Remote Masternode Data"), tr("You are just about to export the required data to run a Masternode\non a remote server to your clipboard.\n\n\n" - "You will only have to paste the data in the pivx.conf file\nof your remote server and start it, " - "then start the Masternode using\nthis controller wallet (select the Masternode in the list and press \"start\").\n" - ))) { + "You will only have to paste the data in the pivx.conf file\nof your remote server and start it." + "%1\n").arg(legacyText))) { // export data QString exportedMN = "masternode=1\n" - "externalip=" + address.left(address.lastIndexOf(":")) + "\n" + - "masternodeaddr=" + address + + "\n" + - "masternodeprivkey=" + index.sibling(index.row(), MNModel::PRIV_KEY).data(Qt::DisplayRole).toString() + "\n"; + "externalip=" + address.left(address.lastIndexOf(":")) + "\n" + "listen=1\n"; + if (isLegacy) { + exportedMN = "masternodeaddr=" + address + +"\n" + + "masternodeprivkey=" + + index.sibling(index.row(), MNModel::PRIV_KEY).data(Qt::DisplayRole).toString() + "\n"; + } else { + exportedMN += "mnoperatorprivatekey=" + QString::fromStdString(opDMN->operatorSk.empty() ? "" : opDMN->operatorSk) + "\n"; + } GUIUtil::setClipboard(exportedMN); inform(tr("Masternode data copied to the clipboard.")); } diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 230828d3db508..897232c7cc3ae 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -111,13 +111,16 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM initCssEditLine(ui->lineEditIpAddress); initCssEditLine(ui->lineEditPort); ui->stackedWidget->setCurrentIndex(pos); - ui->lineEditPort->setEnabled(false); // use default port number if (walletModel->isRegTestNetwork()) { + ui->lineEditPort->setEnabled(true); ui->lineEditPort->setText("51476"); - } else if (walletModel->isTestNetwork()) { - ui->lineEditPort->setText("51474"); } else { - ui->lineEditPort->setText("51472"); + ui->lineEditPort->setEnabled(false); // fixed to default port number + if (walletModel->isTestNetwork()) { + ui->lineEditPort->setText("51474"); + } else { + ui->lineEditPort->setText("51472"); + } } // Frame Owner From 9a9271e4715332606bf475fcb4d5ac116c5442b3 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Feb 2022 17:30:09 -0300 Subject: [PATCH 30/82] GUI: Masternode creation wizard; add voter customization page. --- src/qt/pivx/forms/masternodewizarddialog.ui | 308 +++++++++++++++++++- src/qt/pivx/masternodewizarddialog.cpp | 114 +++++--- src/qt/pivx/masternodewizarddialog.h | 10 +- 3 files changed, 387 insertions(+), 45 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index 9c2fded978d3b..74b4cbf723c4c 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -100,6 +100,12 @@ 24 + + + 16777215 + 40 + + 0 @@ -615,6 +621,107 @@ + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + + + + + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 0 + + + + + 22 + 22 + + + + + 22 + 22 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 6 + + + true + + + false + + + true + + + + + + + + @@ -896,6 +1003,50 @@
+ + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 80 + 0 + + + + + 80 + 16777215 + + + + Qt::NoFocus + + + Voter + + + true + + + false + + + @@ -929,7 +1080,7 @@ 20 - 14 + 12 @@ -945,7 +1096,7 @@ 16777215 - 350 + 370 @@ -1058,9 +1209,9 @@
- + - 6 + 0 140 @@ -1079,13 +1230,13 @@ 0 - 50 + 46 16777215 - 50 + 46 @@ -1093,15 +1244,37 @@ + + + + + 16777215 + 28 + + + + Used for the collateral address label in the addressbook + + + Qt::AlignCenter + + + false + + + Qt::Vertical + + QSizePolicy::Fixed + 20 - 40 + 20 @@ -1127,7 +1300,7 @@ 20 - 40 + 50 @@ -1718,6 +1891,125 @@ + + + 6 + + + 85 + + + 12 + + + 85 + + + 12 + + + + + + 0 + 40 + + + + + 16777215 + 50 + + + + Set Voter Data + + + + + + + + 360 + 30 + + + + Customize who will be able to submit proposal votes + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Enter key (can leave it empty and the wallet will create one for you) + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + e.g. Dxx + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + 0 diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 897232c7cc3ae..1d5181c5bc426 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -72,38 +72,72 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM isDeterministic = walletModel->isV6Enforced(); - for (int i = 0; i < 5; i++) list_icConfirm.push_back(new QPushButton(this)); - list_pushNumber = {ui->pushNumber1, ui->pushNumber2, ui->pushNumber3, ui->pushNumber4, ui->pushNumber5}; - list_pushName = {ui->pushName1, ui->pushName2, ui->pushName3, ui->pushName4, ui->pushName5}; - setCssProperty({ui->labelLine1, ui->labelLine2, ui->labelLine3, ui->labelLine4}, "line-purple"); + for (int i = 0; i < 6; i++) list_icConfirm.push_back(new QPushButton(this)); + list_pushNumber = {ui->pushNumber1, ui->pushNumber2, ui->pushNumber3, ui->pushNumber4, ui->pushNumber5, ui->pushNumber6}; + list_pushName = {ui->pushName1, ui->pushName2, ui->pushName3, ui->pushName4, ui->pushName5, ui->pushName6}; + setCssProperty({ui->labelLine1, ui->labelLine2, ui->labelLine3, ui->labelLine4, ui->labelLine5}, "line-purple"); setCssProperty({ui->groupBoxName, ui->groupContainer}, "container-border"); - // Frame Intro + QString collateralAmountStr(GUIUtil::formatBalance(mnModel->getMNCollateralRequiredAmount())); + initIntroPage(collateralAmountStr); + initCollateralPage(collateralAmountStr); + initServicePage(); + initOwnerPage(); + initOperatorPage(); + initVoterPage(); + initSummaryPage(); + + // Confirm icons + ui->stackedIcon1->addWidget(list_icConfirm[0]); + ui->stackedIcon2->addWidget(list_icConfirm[1]); + ui->stackedIcon3->addWidget(list_icConfirm[2]); + ui->stackedIcon4->addWidget(list_icConfirm[3]); + ui->stackedIcon5->addWidget(list_icConfirm[4]); + ui->stackedIcon6->addWidget(list_icConfirm[5]); + initTopBtns(list_icConfirm, list_pushNumber, list_pushName); + + // Connect btns + setCssBtnPrimary(ui->btnNext); + setCssProperty(ui->btnBack , "btn-dialog-cancel"); + ui->btnBack->setVisible(false); + setCssProperty(ui->pushButtonSkip, "ic-close"); + + connect(ui->pushButtonSkip, &QPushButton::clicked, this, &MasterNodeWizardDialog::close); + connect(ui->btnNext, &QPushButton::clicked, this, &MasterNodeWizardDialog::accept); + connect(ui->btnBack, &QPushButton::clicked, this, &MasterNodeWizardDialog::onBackClicked); +} + +void MasterNodeWizardDialog::initIntroPage(const QString& collateralAmountStr) +{ setCssProperty(ui->labelTitle1, "text-title-dialog"); setCssProperty(ui->labelMessage1a, "text-main-grey"); setCssProperty(ui->labelMessage1b, "text-main-purple"); - QString collateralAmountStr = GUIUtil::formatBalance(mnModel->getMNCollateralRequiredAmount()); ui->labelMessage1a->setText(formatHtmlContent( - formatParagraph(tr("To create a PIVX Masternode you must dedicate %1 (the unit of PIVX) " - "to the network (however, these coins are still yours and will never leave your possession).").arg(collateralAmountStr)) + - formatParagraph(tr("You can deactivate the node and unlock the coins at any time.")))); + formatParagraph(tr("To create a PIVX Masternode you must dedicate %1 (the unit of PIVX) " + "to the network (however, these coins are still yours and will never leave your possession).").arg(collateralAmountStr)) + + formatParagraph(tr("You can deactivate the node and unlock the coins at any time.")))); +} - // Frame Collateral +void MasterNodeWizardDialog::initCollateralPage(const QString& collateralAmountStr) +{ setCssProperty(ui->labelTitle3, "text-title-dialog"); setCssProperty(ui->labelMessage3, "text-main-grey"); + setCssSubtitleScreen(ui->labelSubtitleName); ui->labelMessage3->setText(formatHtmlContent( - formatParagraph(tr("A transaction of %1 will be made").arg(collateralAmountStr)) + - formatParagraph(tr("to a new empty address in your wallet.")) + - formatParagraph(tr("The Address is labeled under the master node's name.")))); + formatParagraph(tr("A transaction of %1 will be made").arg(collateralAmountStr)) + + formatParagraph(tr("to a new empty address in your wallet.")) + + formatParagraph(tr("The Address is labeled under the master node's name.")))); initCssEditLine(ui->lineEditName); // MN alias must not contain spaces or "#" character QRegularExpression rx("^(?:(?![\\#\\s]).)*"); ui->lineEditName->setValidator(new QRegularExpressionValidator(rx, ui->lineEditName)); +} - // Frame Service +void MasterNodeWizardDialog::initServicePage() +{ setCssProperty(ui->labelTitle4, "text-title-dialog"); setCssProperty({ui->labelSubtitleIp, ui->labelSubtitlePort}, "text-title"); setCssSubtitleScreen(ui->labelSubtitleAddressIp); @@ -122,16 +156,20 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM ui->lineEditPort->setText("51472"); } } +} - // Frame Owner +void MasterNodeWizardDialog::initOwnerPage() +{ setCssProperty(ui->labelTitle5, "text-title-dialog"); setCssSubtitleScreen(ui->labelSubtitleOwner); setCssProperty({ui->labelSubtitleOwnerAddress, ui->labelSubtitlePayoutAddress}, "text-title"); initCssEditLine(ui->lineEditOwnerAddress); initCssEditLine(ui->lineEditPayoutAddress); setDropdownList(ui->lineEditOwnerAddress, actOwnerAddrList, {AddressTableModel::Receive}); +} - // Frame Operator +void MasterNodeWizardDialog::initOperatorPage() +{ setCssProperty(ui->labelTitle6, "text-title-dialog"); setCssSubtitleScreen(ui->labelSubtitleOperator); setCssProperty({ui->labelSubtitleOperatorKey, ui->labelSubtitleOperatorReward}, "text-title"); @@ -139,27 +177,14 @@ MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnM initCssEditLine(ui->lineEditOperatorPayoutAddress); initCssEditLine(ui->lineEditPercentage); ui->lineEditPercentage->setValidator(new QIntValidator(1, 99)); +} - // Frame Summary - initSummaryPage(); - - // Confirm icons - ui->stackedIcon1->addWidget(list_icConfirm[0]); - ui->stackedIcon2->addWidget(list_icConfirm[1]); - ui->stackedIcon3->addWidget(list_icConfirm[2]); - ui->stackedIcon4->addWidget(list_icConfirm[3]); - ui->stackedIcon5->addWidget(list_icConfirm[4]); - initTopBtns(list_icConfirm, list_pushNumber, list_pushName); - - // Connect btns - setCssBtnPrimary(ui->btnNext); - setCssProperty(ui->btnBack , "btn-dialog-cancel"); - ui->btnBack->setVisible(false); - setCssProperty(ui->pushButtonSkip, "ic-close"); - - connect(ui->pushButtonSkip, &QPushButton::clicked, this, &MasterNodeWizardDialog::close); - connect(ui->btnNext, &QPushButton::clicked, this, &MasterNodeWizardDialog::accept); - connect(ui->btnBack, &QPushButton::clicked, this, &MasterNodeWizardDialog::onBackClicked); +void MasterNodeWizardDialog::initVoterPage() +{ + setCssProperty(ui->labelTitleVoter, "text-title-dialog"); + setCssSubtitleScreen(ui->labelSubtitleVoter); + setCssProperty({ui->labelSubtitleVoterKey}, "text-title"); + initCssEditLine(ui->lineEditVoterKey); } void MasterNodeWizardDialog::initSummaryPage() @@ -267,6 +292,11 @@ void MasterNodeWizardDialog::accept() } case Pages::OPERATOR: { if (!validateOperator()) return; // invalid state informed internally + moveToNextPage(pos, nextPos); + break; + } + case Pages::VOTER: { + if (!validateVoter()) return; // invalid state informed internally completeTask(); return; } @@ -483,6 +513,18 @@ bool MasterNodeWizardDialog::validateOwner() return true; } +bool MasterNodeWizardDialog::validateVoter() +{ + QString voterAddress(ui->lineEditVoterKey->text()); + if (!voterAddress.isEmpty() && !walletModel->validateAddress(voterAddress)) { + setCssEditLine(ui->lineEditVoterKey, false, true); + inform(tr("Invalid voting address")); + return false; + } + + return true; +} + bool MasterNodeWizardDialog::validateOperator() { QString operatorKey(ui->lineEditOperatorKey->text()); diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index 164e787940a5b..2205e713e0032 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -54,7 +54,8 @@ enum Pages { SERVICE = 2, OWNER = 3, OPERATOR = 4, - SUMMARY = 5 + VOTER = 5, + SUMMARY = 6 }; public: @@ -100,6 +101,12 @@ private Q_SLOTS: WalletModel* walletModel{nullptr}; MNModel* mnModel{nullptr}; + void initIntroPage(const QString& collateralAmountStr); + void initCollateralPage(const QString& collateralAmountStr); + void initServicePage(); + void initOwnerPage(); + void initOperatorPage(); + void initVoterPage(); void initSummaryPage(); bool createMN(); @@ -110,6 +117,7 @@ private Q_SLOTS: void moveToNextPage(int currentPos, int nextPos); void moveBack(int backPos); + bool validateVoter(); bool validateOwner(); bool validateOperator(); From e082151610fb24096e8cc332f9583fe01088f67d Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Feb 2022 03:36:26 -0300 Subject: [PATCH 31/82] GUI: Masternode creation wizard; connect custom voting key to the backend. --- src/qt/pivx/masternodewizarddialog.cpp | 71 +++++++++++++++----------- src/qt/pivx/masternodewizarddialog.h | 1 + src/qt/pivx/mnmodel.cpp | 4 +- src/qt/pivx/mnmodel.h | 2 +- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 1d5181c5bc426..27ced17e33521 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -339,42 +339,41 @@ void MasterNodeWizardDialog::setSummary() } } -CallResult> MasterNodeWizardDialog::getOrCreateOwnerAddress(const std::string& alias) +CallResult> getOrCreateAddress(const QString& input, + const std::string& alias, + const std::string& label_prefix, + WalletModel* walletModel, + bool checkIsMine) { - QString ownerAddrStr(ui->lineEditOwnerAddress->text()); - if (ownerAddrStr.isEmpty()) { - // Create owner addr - const auto ownerAddr = walletModel->getNewAddress("dmn_owner_" + alias); - if (!ownerAddr) return {ownerAddr.getError()}; - const CKeyID* ownerKeyId = ownerAddr.getObjResult()->getKeyID(); - return {{ownerAddr.getObjResult()->ToString(), *ownerKeyId}}; + if (input.isEmpty()) { + const auto addr = walletModel->getNewAddress("dmn_"+label_prefix+"_"+alias); + if (!addr) return {addr.getError()}; + const CKeyID* ownerKeyId = addr.getObjResult()->getKeyID(); + return {{addr.getObjResult()->ToString(), *ownerKeyId}}; } else { - if (!walletModel->isMine(ownerAddrStr)) { - return {"Invalid owner address, must be owned by this wallet"}; // Shouldn't happen.. - } - std::string addrStr = ownerAddrStr.toStdString(); + std::string addrStr = input.toStdString(); auto opKeyId = walletModel->getKeyIDFromAddr(addrStr); - if (!opKeyId) return {"Invalid owner address id"}; + if (!opKeyId) return {"Invalid "+label_prefix+" address id"}; + if (checkIsMine && !walletModel->isMine(*opKeyId)) { + return {"Invalid "+label_prefix+" address, must be owned by this wallet"}; + } return {{addrStr, *opKeyId}}; } } -// Future: generalize with getOrCreateOwnerAddress. +CallResult> MasterNodeWizardDialog::getOrCreateOwnerAddress(const std::string& alias) +{ + return getOrCreateAddress(ui->lineEditOwnerAddress->text(), alias, "owner", walletModel, true); +} + CallResult> MasterNodeWizardDialog::getOrCreatePayoutAddress(const std::string& alias) { - QString payoutAddrStr(ui->lineEditPayoutAddress->text()); - if (payoutAddrStr.isEmpty()) { - // Create payout addr - const auto ownerAddr = walletModel->getNewAddress("dmn_payout_" + alias); - if (!ownerAddr) return {ownerAddr.getError()}; - const CKeyID* ownerKeyId = ownerAddr.getObjResult()->getKeyID(); - return {{ownerAddr.getObjResult()->ToString(), *ownerKeyId}}; - } else { - std::string addrStr = payoutAddrStr.toStdString(); - auto opKeyId = walletModel->getKeyIDFromAddr(addrStr); - if (!opKeyId) return {"Invalid payout address id"}; - return {{addrStr, *opKeyId}}; - } + return getOrCreateAddress(ui->lineEditPayoutAddress->text(), alias, "payout", walletModel, false); +} + +CallResult> MasterNodeWizardDialog::getOrCreateVotingAddress(const std::string& alias) +{ + return getOrCreateAddress(ui->lineEditVoterKey->text(), alias, "voting", walletModel, false); } bool MasterNodeWizardDialog::createMN() @@ -447,6 +446,20 @@ bool MasterNodeWizardDialog::createMN() Optional operatorPayoutKeyId = walletModel->getKeyIDFromAddr(ui->lineEditOperatorPayoutAddress->text().toStdString()); int operatorPercentage = ui->lineEditPercentage->text().isEmpty() ? 0 : (int) ui->lineEditPercentage->text().toUInt(); + // 4) Get voter data + Optional votingAddr; + if (!ui->lineEditVoterKey->text().isEmpty()) { + auto opVotingAddrAndKeyId = getOrCreateVotingAddress(alias); + if (!opVotingAddrAndKeyId.getRes()) { + return errorOut(tr(opVotingAddrAndKeyId.getError().c_str())); + } + auto votingAddrAndKeyId = opVotingAddrAndKeyId.getObjResult(); + votingAddr = votingAddrAndKeyId->second; + } else { + // Empty voting address means "use the owner key for voting" + votingAddr = ownerKeyId; + } + // For now, collateral key is always inside the wallet std::string error_str; auto res = mnModel->createDMN(alias, @@ -455,8 +468,8 @@ bool MasterNodeWizardDialog::createMN() port, ownerKeyId, operatorKey.isEmpty() ? nullopt : Optional(operatorKey.toStdString()), - nullopt, // use owner key as voting key - payoutKeyId, // use owner key as payout script + votingAddr, + payoutKeyId, error_str, operatorPercentage, // operator percentage operatorPayoutKeyId); // operator payout script diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index 2205e713e0032..c83767b5f7fdd 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -123,6 +123,7 @@ private Q_SLOTS: CallResult> getOrCreateOwnerAddress(const std::string& alias); CallResult> getOrCreatePayoutAddress(const std::string& alias); + CallResult> getOrCreateVotingAddress(const std::string& alias); void setDropdownList(QLineEdit* edit, QAction* action, const QStringList& types); void onAddrListClicked(const QStringList& types, QLineEdit* edit); diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 75f7bec055660..cc20632c562b1 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -347,7 +347,7 @@ CallResult MNModel::createDMN(const std::string& alias, const std::string& servicePort, const CKeyID& ownerAddr, const Optional& operatorPubKey, - const Optional& votingAddr, + const Optional& votingAddr, const CKeyID& payoutKeyId, std::string& strError, const Optional& operatorPercentage, @@ -401,7 +401,7 @@ CallResult MNModel::createDMN(const std::string& alias, service, ownerAddr, operatorPk, - Optional(ownerAddr), // voting key + votingAddr, // voting key payoutKeyId, // payout script operatorSk, // only if the operator was provided (or locally created) operatorPercentage, // operator percentage diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index 354759114d551..a7a1cc7449f14 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -113,7 +113,7 @@ class MNModel : public QAbstractTableModel const std::string& servicePort, const CKeyID& ownerAddr, const Optional& operatorPubKey, - const Optional& votingAddr, + const Optional& votingAddr, const CKeyID& payoutAddr, std::string& strError, const Optional& operatorPercentage = nullopt, From d69289d33c947d5a38be73f076f81eabe1916167 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Feb 2022 20:24:44 -0300 Subject: [PATCH 32/82] GUI: connect custom voting address to summary screen --- src/qt/pivx/forms/masternodewizarddialog.ui | 991 +++++++++++--------- src/qt/pivx/masternodewizarddialog.cpp | 17 +- 2 files changed, 568 insertions(+), 440 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index 74b4cbf723c4c..65396a57e8814 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -1935,7 +1935,7 @@ - Customize who will be able to submit proposal votes + Customize who will be able to vote for proposal Qt::AlignCenter @@ -1964,7 +1964,7 @@ - Enter key (can leave it empty and the wallet will create one for you) + Enter address (if empty, the wallet will create one for you) Qt::AlignCenter @@ -2065,450 +2065,569 @@ - - - - - - - 8 - - - 9 - - - 9 - - - 12 - - - - - Owner Section - - - - - - - - 0 - 0 - - - - - 0 - - - 3 - - - 0 - - - 0 + + + Qt::NoFocus + + + false + + + #scrollAreaSummary{ + border: 0; + } + + + 0 + + + true + + + + + 0 + 0 + 681 + 352 + + + + Qt::NoFocus + + + #containerSummary{ + border: 0; + } + + + + + + + 8 + + + 9 + + + 9 + + + 12 + + + + + Owner Section - - 0 + + + + + + + 0 + 0 + - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Main Address - - - - - - - N/A - - - - - - - - - - Qt::LeftToRight - - - - 4 + + + 0 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Main Address + + + + + + + N/A + + + + + + + + + + Qt::LeftToRight - - 0 - - - 0 - - - 0 - - - 0 - - - - - Payout Address - - - - - - - N/A - - - - - - - - - - - - - - 60 - 0 - - - - - 0 - - - 0 + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Payout Address + + + + + + + N/A + + + + + + + + + + + + + + 60 + 0 + - - 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 4 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Collateral ID + + + + + + + N/A + + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Collateral Index + + + + + + + N/A + + + + + + + + + + + + + + + + + 8 + + + 9 + + + 12 + + + + + Operator Section - - 0 + + + + + + + 0 + 0 + - - 0 - - - - - - 4 - - - 3 - - - 0 - - - 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 4 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Operator Key + + + + + + + N/A + + + + + + + + + + Qt::LeftToRight - - 0 - - - - - Collateral ID - - - - - - - N/A - - - - - - - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Collateral Index - - - - - - - N/A - - - - - - - - - - - - - - - - - 8 - - - 9 - - - 12 - - - - - Operator Section - - - - - - - - 0 - 0 - - - - - 0 - - - 0 + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Service + + + + + + + N/A + + + + + + + + + + + + + + 60 + 0 + - - 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 4 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Payout Address + + + + + + + N/A + + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Payout Percentage + + + + + + + 0.00 + + + + + + + + + + + + + + + + + 8 + + + 9 + + + 12 + + + + + Voting Section - - 0 + + + + + + + 0 + 0 + - - 0 - - - - - - 4 - - - 3 - - - 0 - - - 0 - - - 0 - - - - - Operator Key - - - - - - - N/A - - - - - - - - - - Qt::LeftToRight - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Service - - - - - - - N/A - - - - - - - - - - - - - - 60 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 4 - - - 3 - - - 0 - - - 0 - - - 0 - - - - - Payout Address - - - - - - - N/A - - - - - - - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Payout Percentage - - - - - - - 0.00 - - - - - - - - - - - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 4 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Voting Address + + + + + + + N/A + + + + + + + + + + + + + + diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 27ced17e33521..e9c4bfa386aeb 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -189,17 +189,20 @@ void MasterNodeWizardDialog::initVoterPage() void MasterNodeWizardDialog::initSummaryPage() { + setCssProperty({ui->scrollAreaSummary, ui->containerSummary}, "container"); setCssProperty(ui->labelSummary, "text-title-dialog"); setCssSubtitleScreen(ui->labelSubtitleSummary); - setCssProperty({ui->containerOwner, ui->containerOperator}, "card-governance"); - setCssProperty({ui->labelOwnerSection, ui->labelOperatorSection}, "text-section-title"); + setCssProperty({ui->containerOwner, ui->containerOperator, ui->containerVoter}, "card-governance"); + setCssProperty({ui->labelOwnerSection, ui->labelOperatorSection, ui->labelVoterSection}, "text-section-title"); setCssProperty({ui->labelTitleMainAddr, ui->labelTitlePayoutAddr, ui->labelTitleCollateral, ui->labelTitleCollateralIndex, ui->labelTitleOperatorKey, ui->labelTitleOperatorService, ui->labelTitleOperatorPayout, ui->labelTitleOperatorPercentage, - ui->labelTitleOperatorService}, "text-title-right"); + ui->labelTitleOperatorService, ui->labelTitleVoterAddress}, "text-title-right"); setCssProperty({ui->labelMainAddr, ui->labelPayoutAddr, ui->labelCollateralIndex, ui->labelCollateralHash, - ui->labelOperatorKey, ui->labelOperatorPayout, ui->labelOperatorPercentage, ui->labelOperatorService}, "text-body2-dialog"); + ui->labelOperatorKey, ui->labelOperatorPayout, ui->labelOperatorPercentage, ui->labelOperatorService, + ui->labelVoterAddress}, "text-body2-dialog"); setCardShadow(ui->containerOwner); setCardShadow(ui->containerOperator); + setCardShadow(ui->containerVoter); connect(ui->labelMainAddr, &ClickableLabel::clicked, [this](){ GUIUtil::setClipboard(QString::fromStdString(mnSummary->ownerAddr)); @@ -226,6 +229,11 @@ void MasterNodeWizardDialog::initSummaryPage() GUIUtil::setClipboard(QString::fromStdString(*mnSummary->operatorPayoutAddr)); inform(tr("Operator payout address copied to clipboard")); }); + + connect(ui->labelVoterAddress, &ClickableLabel::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(mnSummary->votingKey)); + inform(tr("Voting address copied to clipboard")); + }); } void MasterNodeWizardDialog::showEvent(QShowEvent *event) @@ -337,6 +345,7 @@ void MasterNodeWizardDialog::setSummary() } else { ui->labelOperatorPayout->setText(tr("No address")); } + setShortText(ui->labelVoterAddress, QString::fromStdString(mnSummary->votingKey), 14); } CallResult> getOrCreateAddress(const QString& input, From 1af1a8434efd1011f966b87c5ecb186f1bc236f0 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 19 Feb 2022 09:47:27 -0300 Subject: [PATCH 33/82] GUI: DMN creation, convert rejection error into an understandable string for the user --- src/evo/specialtx_utils.cpp | 4 +++- src/qt/pivx/mnmodel.cpp | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/evo/specialtx_utils.cpp b/src/evo/specialtx_utils.cpp index 0f678fe40e2ad..ef3d2aa0256b5 100644 --- a/src/evo/specialtx_utils.cpp +++ b/src/evo/specialtx_utils.cpp @@ -51,7 +51,9 @@ OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx, CWallet::CommitResult res = pwallet->CommitTransaction(MakeTransactionRef(tx), nullptr, g_connman.get(), extras); CValidationState& state = res.state; if (state.IsInvalid()) { - return {false, strprintf("%s: %s", state.GetRejectReason(), state.GetDebugMessage())}; + std::string debugMsg = state.GetDebugMessage(); + return {false, debugMsg.empty() ? state.GetRejectReason() : + strprintf("%s: %s", state.GetRejectReason(), debugMsg)}; } return {true}; diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index cc20632c562b1..ab11540449a4c 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -295,6 +295,17 @@ int MNModel::getMasternodeCollateralMinConf() return Params().GetConsensus().MasternodeCollateralMinConf(); } +// Add here only the errors that the user could face +std::string translateRejectionError(const std::string& rejection) +{ + if (rejection == "bad-protx-ipaddr-port") { + return _("Invalid service IP address"); + } else if (rejection == "bad-protx-dup-IP-address") { + return _("The provided service IP address is already in use by another registered Masternode"); + } + return rejection; +} + static CallResult createDMNInternal(const COutPoint& collateral, const CKey& keyCollateral, const CService& service, @@ -338,7 +349,8 @@ static CallResult createDMNInternal(const COutPoint& collateral, extraValues.emplace("operatorSk", bls::EncodeSecret(Params(), *operatorSk)); } res = SignAndSendSpecialTx(wallet, tx, pl, &extraValues); - return res ? CallResult(tx.GetHash()) : CallResult(res.getError()); + return res ? CallResult(tx.GetHash()) : + CallResult(translateRejectionError(res.getError())); } CallResult MNModel::createDMN(const std::string& alias, From 8b02ee719ca886e054a41bcc1669ca7915a5a573 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Feb 2022 13:14:38 -0300 Subject: [PATCH 34/82] test: failing txs are now properly considered RPC_VERIFY_REJECTED and not general std::exception. --- src/rpc/rpcevo.cpp | 16 ++++++++-------- test/functional/tiertwo_deterministicmns.py | 11 ++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index a59c4ccf9e2c2..9c86e46653055 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -146,8 +146,8 @@ std::string GetHelpString(int nParamNum, ProRegParam p) return strprintf(it->second, nParamNum); } -void CheckOpResult(const OperationResult& res) { - if (!res) throw JSONRPCError(RPC_INTERNAL_ERROR, res.getError()); +void CheckOpResult(const OperationResult& res, RPCErrorCode errorCode = RPC_INTERNAL_ERROR) { + if (!res) throw JSONRPCError(errorCode, res.getError()); } #ifdef ENABLE_WALLET @@ -409,7 +409,7 @@ static UniValue ProTxRegister(const JSONRPCRequest& request, bool fSignAndSend) if (fSignAndSend) { CheckOpResult(SignSpecialTxPayloadByString(pl, keyCollateral)); // prove we own the collateral // check the payload, add the tx inputs sigs, and send the tx. - CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); return tx.GetHash().GetHex(); } // external signing with collateral key @@ -479,7 +479,7 @@ UniValue protx_register_submit(const JSONRPCRequest& request) pl.vchSig = DecodeBase64(request.params[1].get_str().c_str()); // check the payload, add the tx inputs sigs, and send the tx. - CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); return tx.GetHash().GetHex(); } @@ -542,7 +542,7 @@ UniValue protx_register_fund(const JSONRPCRequest& request) // update payload on tx (with final collateral outpoint) pl.vchSig.clear(); // check the payload, add the tx inputs sigs, and send the tx. - CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); return tx.GetHash().GetHex(); } @@ -765,7 +765,7 @@ UniValue protx_update_service(const JSONRPCRequest& request) CheckOpResult(FundSpecialTx(pwallet, tx, pl)); CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); - CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); return tx.GetHash().GetHex(); } @@ -840,7 +840,7 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) CheckOpResult(FundSpecialTx(pwallet, tx, pl)); CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, ownerKey)); - CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); return tx.GetHash().GetHex(); } @@ -907,7 +907,7 @@ UniValue protx_revoke(const JSONRPCRequest& request) CheckOpResult(FundSpecialTx(pwallet, tx, pl)); CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); - CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); return tx.GetHash().GetHex(); } #endif diff --git a/test/functional/tiertwo_deterministicmns.py b/test/functional/tiertwo_deterministicmns.py index a9f84212829ad..678aa6bc66bca 100755 --- a/test/functional/tiertwo_deterministicmns.py +++ b/test/functional/tiertwo_deterministicmns.py @@ -23,6 +23,7 @@ spend_mn_collateral, ) +RPC_VERIFY_REJECTED = -26 #! Transaction was rejected by network rules class DIP3Test(PivxTestFramework): @@ -224,21 +225,21 @@ def run_test(self): # Now try to register dmn2 again with an already-used IP self.log.info("Trying duplicate IP...") rand_idx = mns[randrange(len(mns))].idx - assert_raises_rpc_error(-1, "bad-protx-dup-IP-address", + assert_raises_rpc_error(RPC_VERIFY_REJECTED, "bad-protx-dup-IP-address", self.register_new_dmn, rand_idx, self.minerPos, self.controllerPos, "fund", op_blskeys=dmn2_keys) # Now try with duplicate operator key self.log.info("Trying duplicate operator key...") dmn2b = create_new_dmn(dmn2.idx, controller, dummy_add, dmn_keys) - assert_raises_rpc_error(-1, "bad-protx-dup-operator-key", + assert_raises_rpc_error(RPC_VERIFY_REJECTED, "bad-protx-dup-operator-key", self.protx_register_fund, miner, controller, dmn2b, dummy_add) # Now try with duplicate owner key self.log.info("Trying duplicate owner key...") dmn2c = create_new_dmn(dmn2.idx, controller, dummy_add, dmn2_keys) dmn2c.owner = mns[randrange(len(mns))].owner - assert_raises_rpc_error(-1, "bad-protx-dup-owner-key", + assert_raises_rpc_error(RPC_VERIFY_REJECTED, "bad-protx-dup-owner-key", self.protx_register_fund, miner, controller, dmn2c, dummy_add) # Finally, register it properly. This time setting 10% of the reward for the operator @@ -295,7 +296,7 @@ def run_test(self): assert_raises_rpc_error(-8, "not found", miner.protx_update_service, "%064x" % getrandbits(256), "127.0.0.1:1000") self.log.info("Trying to update an IP address to an already used one...") - assert_raises_rpc_error(-1, "bad-protx-dup-addr", miner.protx_update_service, + assert_raises_rpc_error(RPC_VERIFY_REJECTED, "bad-protx-dup-addr", miner.protx_update_service, mns[0].proTx, mns[1].ipport, "", mns[0].operator_sk) self.log.info("Trying to update the payout address when the reward is 0...") assert_raises_rpc_error(-8, "Operator reward is 0. Cannot set operator payout address", @@ -333,7 +334,7 @@ def run_test(self): assert_raises_rpc_error(-8, "not found", miner.protx_update_registrar, "%064x" % getrandbits(256), "", "", "") self.log.info("Trying to update an operator address to an already used one...") - assert_raises_rpc_error(-1, "bad-protx-dup-key", controller.protx_update_registrar, + assert_raises_rpc_error(RPC_VERIFY_REJECTED, "bad-protx-dup-key", controller.protx_update_registrar, mns[0].proTx, mns[1].operator_pk, "", "") self.log.info("Trying to update the payee to an invalid address...") assert_raises_rpc_error(-5, "invalid PIVX address InvalidPayee", controller.protx_update_registrar, From bc31cf37d374bd40542b6ffb03ce2e79a750f05d Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Feb 2022 14:02:56 -0300 Subject: [PATCH 35/82] Add and connect function to validate the operator service --- src/evo/specialtx_validation.cpp | 2 +- src/evo/specialtx_validation.h | 3 +++ src/interfaces/tiertwo.cpp | 19 +++++++++++++++++++ src/interfaces/tiertwo.h | 4 ++++ src/qt/pivx/masternodewizarddialog.cpp | 7 +++++++ src/qt/pivx/masternodewizarddialog.h | 1 + 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/evo/specialtx_validation.cpp b/src/evo/specialtx_validation.cpp index fa4cb6a2103be..76730feaf87c4 100644 --- a/src/evo/specialtx_validation.cpp +++ b/src/evo/specialtx_validation.cpp @@ -21,7 +21,7 @@ /* -- Helper static functions -- */ -static bool CheckService(const CService& addr, CValidationState& state) +bool CheckService(const CService& addr, CValidationState& state) { if (!addr.IsValid()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr"); diff --git a/src/evo/specialtx_validation.h b/src/evo/specialtx_validation.h index eb49d2b0676e8..3a3c89aa8de64 100644 --- a/src/evo/specialtx_validation.h +++ b/src/evo/specialtx_validation.h @@ -20,6 +20,9 @@ class uint256; /** The maximum allowed size of the extraPayload (for any TxType) */ static const unsigned int MAX_SPECIALTX_EXTRAPAYLOAD = 10000; +/** Operator service validity checks */ +bool CheckService(const CService& addr, CValidationState& state); + /** Payload validity checks (including duplicate unique properties against list at pindexPrev)*/ // Note: for +v2, if the tx is not a special tx, this method returns true. // Note2: This function only performs extra payload related checks, it does NOT checks regular inputs and outputs. diff --git a/src/interfaces/tiertwo.cpp b/src/interfaces/tiertwo.cpp index 858d7bb7c80b3..e2b9563345c91 100644 --- a/src/interfaces/tiertwo.cpp +++ b/src/interfaces/tiertwo.cpp @@ -7,6 +7,8 @@ #include "bls/key_io.h" #include "evo/deterministicmns.h" #include "optional.h" +#include "netbase.h" +#include "evo/specialtx_validation.h" // For CheckService #include "validation.h" #include "wallet/wallet.h" @@ -20,6 +22,23 @@ bool TierTwo::isBlsPubKeyValid(const std::string& blsKey) return opKey && opKey->IsValid(); } +OperationResult TierTwo::isServiceValid(const std::string& serviceStr) +{ + if (serviceStr.empty()) return false; + const auto& params = Params(); + CService service; + if (!Lookup(serviceStr, service, params.GetDefaultPort(), false)) { + return {false, strprintf("invalid network address %s", serviceStr)}; + } + + CValidationState state; + if (!CheckService(service, state)) { + return {false, state.GetRejectReason()}; + } + // All good + return {true}; +} + Optional TierTwo::getDMNData(const uint256& pro_tx_hash, const CBlockIndex* tip) { if (!tip) return nullopt; diff --git a/src/interfaces/tiertwo.h b/src/interfaces/tiertwo.h index 934e5dbc0c8ab..0b0a7f81f77c9 100644 --- a/src/interfaces/tiertwo.h +++ b/src/interfaces/tiertwo.h @@ -5,6 +5,7 @@ #ifndef PIVX_INTERFACES_TIERTWO_H #define PIVX_INTERFACES_TIERTWO_H +#include "operationresult.h" #include "sync.h" #include "uint256.h" #include "validationinterface.h" @@ -59,6 +60,9 @@ class TierTwo : public CValidationInterface { // Return true if the bls key is valid bool isBlsPubKeyValid(const std::string& blsKey); + // Verifies the operator service address validity + OperationResult isServiceValid(const std::string& serviceStr); + // Return the DMNs that this wallet "owns". // future: add filter to return by owner, operator, voter or a combination of them. std::vector> getKnownDMNs() { return WITH_LOCK(cs_cache, return m_cached_dmns;); }; diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index e9c4bfa386aeb..21cdf56d180a0 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -286,6 +286,7 @@ void MasterNodeWizardDialog::accept() isOk = createMN(); QDialog::accept(); } else { + if (!validateService()) return; // invalid state informed internally // Ask if the user want to customize the owner, operator and voter addresses and keys // if not, the process will generate all the values for them and present them in the summary page. isWaitingForAsk = true; @@ -516,6 +517,12 @@ bool MasterNodeWizardDialog::createMN() return true; } +bool MasterNodeWizardDialog::validateService() +{ + auto opRes = interfaces::g_tiertwo->isServiceValid(ui->lineEditIpAddress->text().toStdString()); + return opRes || errorOut(tr(opRes.getError().c_str())); +} + bool MasterNodeWizardDialog::validateOwner() { QString ownerAddress(ui->lineEditOwnerAddress->text()); diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index c83767b5f7fdd..1314c9a7a3191 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -117,6 +117,7 @@ private Q_SLOTS: void moveToNextPage(int currentPos, int nextPos); void moveBack(int backPos); + bool validateService(); bool validateVoter(); bool validateOwner(); bool validateOperator(); From c99f8d03e1bc69b3b3eb2ac8aa7c9f9dfbbd4ee6 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Feb 2022 15:15:45 -0300 Subject: [PATCH 36/82] GUI: enable custom operator payout --- src/qt/pivx/masternodewizarddialog.cpp | 8 ++++---- src/qt/pivx/mnmodel.cpp | 4 ++-- src/qt/pivx/mnmodel.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 21cdf56d180a0..d799952f55045 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -12,7 +12,7 @@ #include "qt/pivx/qtutils.h" #include "qt/walletmodel.h" -#include +#include #include #include @@ -176,7 +176,7 @@ void MasterNodeWizardDialog::initOperatorPage() initCssEditLine(ui->lineEditOperatorKey); initCssEditLine(ui->lineEditOperatorPayoutAddress); initCssEditLine(ui->lineEditPercentage); - ui->lineEditPercentage->setValidator(new QIntValidator(1, 99)); + ui->lineEditPercentage->setValidator(new QDoubleValidator(0.00, 100.00, 2, ui->lineEditPercentage)); } void MasterNodeWizardDialog::initVoterPage() @@ -454,7 +454,7 @@ bool MasterNodeWizardDialog::createMN() // 3) Get operator data QString operatorKey = ui->lineEditOperatorKey->text(); Optional operatorPayoutKeyId = walletModel->getKeyIDFromAddr(ui->lineEditOperatorPayoutAddress->text().toStdString()); - int operatorPercentage = ui->lineEditPercentage->text().isEmpty() ? 0 : (int) ui->lineEditPercentage->text().toUInt(); + double operatorPercentage = ui->lineEditPercentage->text().isEmpty() ? 0 : (double) ui->lineEditPercentage->text().toDouble(); // 4) Get voter data Optional votingAddr; @@ -481,7 +481,7 @@ bool MasterNodeWizardDialog::createMN() votingAddr, payoutKeyId, error_str, - operatorPercentage, // operator percentage + (uint16_t) operatorPercentage * 100, // operator percentage operatorPayoutKeyId); // operator payout script if (!res) { return errorOut(tr(error_str.c_str())); diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index ab11540449a4c..8c20d5bd335f6 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -314,7 +314,7 @@ static CallResult createDMNInternal(const COutPoint& collateral, const Optional& votingAddr, const CKeyID& payoutAddr, const Optional& operatorSk, - const Optional& operatorPercentage, + const Optional& operatorPercentage, const Optional& operatorPayoutAddr) { ProRegPL pl; @@ -362,7 +362,7 @@ CallResult MNModel::createDMN(const std::string& alias, const Optional& votingAddr, const CKeyID& payoutKeyId, std::string& strError, - const Optional& operatorPercentage, + const Optional& operatorPercentage, const Optional& operatorPayoutAddr) { // Parse and validate inputs diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index a7a1cc7449f14..e1348d03f8abe 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -116,7 +116,7 @@ class MNModel : public QAbstractTableModel const Optional& votingAddr, const CKeyID& payoutAddr, std::string& strError, - const Optional& operatorPercentage = nullopt, + const Optional& operatorPercentage = nullopt, const Optional& operatorPayoutAddr = nullopt); // Completely stops the Masternode spending the collateral From 525af94782a2651c9ee22c3b4c54a822cce858a4 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Feb 2022 15:38:36 -0300 Subject: [PATCH 37/82] GUI, Voting dialog: filter out legacy MNs and DMNs that don't have the voting key --- src/interfaces/tiertwo.cpp | 5 +++++ src/interfaces/tiertwo.h | 3 +++ src/qt/pivx/masternodeswidget.cpp | 8 ++++---- src/qt/pivx/mnmodel.cpp | 5 +++++ src/qt/pivx/mnmodel.h | 6 ++++-- src/qt/pivx/mnrow.cpp | 2 +- src/qt/pivx/mnrow.h | 2 +- src/qt/pivx/mnselectiondialog.cpp | 17 +++++++++++------ 8 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/interfaces/tiertwo.cpp b/src/interfaces/tiertwo.cpp index e2b9563345c91..0c319bbb2f402 100644 --- a/src/interfaces/tiertwo.cpp +++ b/src/interfaces/tiertwo.cpp @@ -16,6 +16,11 @@ namespace interfaces { std::unique_ptr g_tiertwo; +bool TierTwo::isLegacySystemObsolete() +{ + return deterministicMNManager->LegacyMNObsolete(); +} + bool TierTwo::isBlsPubKeyValid(const std::string& blsKey) { auto opKey = bls::DecodePublic(Params(), blsKey); diff --git a/src/interfaces/tiertwo.h b/src/interfaces/tiertwo.h index 0b0a7f81f77c9..be8b0cb5ee190 100644 --- a/src/interfaces/tiertwo.h +++ b/src/interfaces/tiertwo.h @@ -57,6 +57,9 @@ class TierTwo : public CValidationInterface { // Initialize cache void init(); + // Return true if spork21 is enabled + bool isLegacySystemObsolete(); + // Return true if the bls key is valid bool isBlsPubKeyValid(const std::string& blsKey); diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 7dcc3c58cfe72..d1b98e148d14e 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -41,7 +41,7 @@ class MNHolder : public FurListRow QString address = index.sibling(index.row(), MNModel::ADDRESS).data(Qt::DisplayRole).toString(); QString status = index.sibling(index.row(), MNModel::STATUS).data(Qt::DisplayRole).toString(); bool wasCollateralAccepted = index.sibling(index.row(), MNModel::WAS_COLLATERAL_ACCEPTED).data(Qt::DisplayRole).toBool(); - uint16_t type = index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt(); + uint8_t type = index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt(); row->updateView("Address: " + address, label, status, wasCollateralAccepted, type); } @@ -194,7 +194,7 @@ void MasterNodesWidget::onEditMNClicked() { if (walletModel) { if (!walletModel->isRegTestNetwork() && !checkMNsNetwork()) return; - uint16_t mnType = index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt(); + uint8_t mnType = index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt(); if (mnType == MNViewType::LEGACY) { if (index.sibling(index.row(), MNModel::WAS_COLLATERAL_ACCEPTED).data(Qt::DisplayRole).toBool()) { // Start MN @@ -318,7 +318,7 @@ void MasterNodesWidget::onInfoMNClicked() QString txId = index.sibling(index.row(), MNModel::COLLATERAL_ID).data(Qt::DisplayRole).toString(); QString outIndex = index.sibling(index.row(), MNModel::COLLATERAL_OUT_INDEX).data(Qt::DisplayRole).toString(); QString pubKey = index.sibling(index.row(), MNModel::PUB_KEY).data(Qt::DisplayRole).toString(); - bool isLegacy = ((uint16_t) index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt()) == MNViewType::LEGACY; + bool isLegacy = ((uint8_t) index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt()) == MNViewType::LEGACY; Optional opDMN = nullopt; if (!isLegacy) { QString proTxHash = index.sibling(index.row(), MNModel::PRO_TX_HASH).data(Qt::DisplayRole).toString(); @@ -358,7 +358,7 @@ void MasterNodesWidget::onDeleteMNClicked() QString txId = index.sibling(index.row(), MNModel::COLLATERAL_ID).data(Qt::DisplayRole).toString(); QString outIndex = index.sibling(index.row(), MNModel::COLLATERAL_OUT_INDEX).data(Qt::DisplayRole).toString(); QString qAliasString = index.data(Qt::DisplayRole).toString(); - bool isLegacy = ((uint16_t) index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt()) == MNViewType::LEGACY; + bool isLegacy = ((uint8_t) index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt()) == MNViewType::LEGACY; bool convertOK = false; unsigned int indexOut = outIndex.toUInt(&convertOK); diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index 8c20d5bd335f6..fddafbf541b21 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -275,6 +275,11 @@ bool MNModel::isMNCollateralMature(const QString& mnAlias) return mn->collateralId && collateralTxAccepted.value(mn->collateralId->hash.GetHex()); } +bool MNModel::isLegacySystemObsolete() +{ + return interfaces::g_tiertwo->isLegacySystemObsolete(); +} + bool MNModel::isMNsNetworkSynced() { return g_tiertwo_sync_state.IsSynced(); diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index e1348d03f8abe..f69e43ce26a2e 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -15,7 +15,7 @@ class CMasternode; class DMNView; class WalletModel; -enum MNViewType : uint16_t +enum MNViewType : uint8_t { LEGACY = 0, DMN_OWNER = (1 << 0), @@ -88,7 +88,9 @@ class MNModel : public QAbstractTableModel bool addMn(CMasternodeConfig::CMasternodeEntry* entry); void updateMNList(); - + // Whether the MN legacy system is active or not + bool isLegacySystemObsolete(); + // Whether the tier two synchronization completed or not bool isMNsNetworkSynced(); // Returns the MN activeState field. int getMNState(const QString& mnAlias); diff --git a/src/qt/pivx/mnrow.cpp b/src/qt/pivx/mnrow.cpp index 83861a7211e3d..74dc42c0c6eba 100644 --- a/src/qt/pivx/mnrow.cpp +++ b/src/qt/pivx/mnrow.cpp @@ -22,7 +22,7 @@ void MNRow::updateView(QString address, const QString& label, QString status, bool wasCollateralAccepted, - uint16_t type) + uint8_t type) { ui->labelName->setText(label); address = address.size() < 40 ? address : address.left(20) + "..." + address.right(20); diff --git a/src/qt/pivx/mnrow.h b/src/qt/pivx/mnrow.h index 611599c20ffa8..37feba9f1b74b 100644 --- a/src/qt/pivx/mnrow.h +++ b/src/qt/pivx/mnrow.h @@ -23,7 +23,7 @@ class MNRow : public QWidget const QString& label, QString status, bool wasCollateralAccepted, - uint16_t type); + uint8_t type); Q_SIGNALS: void onMenuClicked(); diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 526bd4ed85613..b845e563f68f1 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -111,13 +111,18 @@ void MnSelectionDialog::updateView() QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + bool isLegacyObselete = mnModel->isLegacySystemObsolete(); for (int i = 0; i < mnModel->rowCount(); ++i) { - QString alias = mnModel->index(i, MNModel::ALIAS, QModelIndex()).data().toString(); - QString status = mnModel->index(i, MNModel::STATUS, QModelIndex()).data().toString(); - VoteInfo* ptrVoteInfo{nullptr}; - auto it = votes.find(alias.toStdString()); - if (it != votes.end()) { ptrVoteInfo = &it->second; } - appendItem(flgCheckbox, flgTristate, alias, status, ptrVoteInfo); + uint8_t mnType = mnModel->index(i, MNModel::TYPE, QModelIndex()).data().toUInt(); + bool acceptLegacy = mnType == MNViewType::LEGACY && !isLegacyObselete; + if (acceptLegacy || mnType & MNViewType::DMN_VOTER) { + QString alias = mnModel->index(i, MNModel::ALIAS, QModelIndex()).data().toString(); + QString status = mnModel->index(i, MNModel::STATUS, QModelIndex()).data().toString(); + VoteInfo* ptrVoteInfo{nullptr}; + auto it = votes.find(alias.toStdString()); + if (it != votes.end()) { ptrVoteInfo = &it->second; } + appendItem(flgCheckbox, flgTristate, alias, status, ptrVoteInfo); + } } // save COLUMN_CHECKBOX width for tree-mode From 50af2fb11a2ccbeba58115cb16575c9ad9275d1f Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 28 Feb 2022 13:22:17 -0300 Subject: [PATCH 38/82] GUI: MN wizard dialog text cut off correction. Seems to only happen on linux. --- src/qt/pivx/forms/masternodewizarddialog.ui | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index 65396a57e8814..9742075c67152 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -796,7 +796,7 @@ - 70 + 65 20 @@ -812,7 +812,7 @@ - 80 + 83 16777215 @@ -856,7 +856,7 @@ - 80 + 83 16777215 @@ -897,7 +897,7 @@ - 80 + 83 16777215 @@ -941,7 +941,7 @@ - 80 + 83 16777215 @@ -985,7 +985,7 @@ - 80 + 83 16777215 @@ -1029,7 +1029,7 @@ - 80 + 83 16777215 @@ -1057,7 +1057,7 @@ - 70 + 65 20 @@ -1481,13 +1481,13 @@ 6 - 90 + 78 12 - 90 + 78 12 @@ -1711,7 +1711,7 @@ - Enter key (can leave it empty and the wallet will create one for you) + Enter address (if empty, the wallet will create one for you) Qt::AlignCenter @@ -2088,7 +2088,7 @@ 0 0 - 681 + 214 352 @@ -2175,7 +2175,7 @@ - N/A + N/A @@ -2213,7 +2213,7 @@ - N/A + N/A @@ -2275,7 +2275,7 @@ - N/A + N/A @@ -2310,7 +2310,7 @@ - N/A + N/A @@ -2394,7 +2394,7 @@ - N/A + N/A @@ -2613,7 +2613,7 @@ - N/A + N/A From 2be9d54dd91f88e270978eb781d1934f71dc718d Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 1 Mar 2022 17:31:39 -0300 Subject: [PATCH 39/82] GUI: MN creation wizard, get default port number from the client model. --- src/qt/clientmodel.cpp | 5 +++++ src/qt/clientmodel.h | 2 ++ src/qt/pivx/masternodeswidget.cpp | 2 +- src/qt/pivx/masternodewizarddialog.cpp | 19 ++++++------------- src/qt/pivx/masternodewizarddialog.h | 3 +++ 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 6f1cead258cdc..4894349ea3f3a 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -113,6 +113,11 @@ int ClientModel::getNumBlocksAtStartup() return numBlocksAtStartup; } +unsigned int ClientModel::getNetworkPort() +{ + return Params().GetDefaultPort(); +} + quint64 ClientModel::getTotalBytesRecv() const { if(!g_connman) diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index dc9c4cc34f010..ad41358ac50ae 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -62,6 +62,8 @@ class ClientModel : public QObject int getNumConnections(unsigned int flags = CONNECTIONS_ALL) const; int getNumBlocksAtStartup(); + unsigned int getNetworkPort(); + // from cached block index int getNumBlocks(); QDateTime getLastBlockDate() const; diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index d1b98e148d14e..36128456dff2a 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -412,7 +412,7 @@ void MasterNodesWidget::onCreateMNClicked() .arg(GUIUtil::formatBalance(mnCollateralAmount, BitcoinUnits::PIV))); return; } - MasterNodeWizardDialog* dialog = new MasterNodeWizardDialog(walletModel, mnModel, window); + MasterNodeWizardDialog* dialog = new MasterNodeWizardDialog(walletModel, mnModel, clientModel, window); connect(dialog, &MasterNodeWizardDialog::message, this, &PWidget::emitMessage); do { showHideOp(true); diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index d799952f55045..8d8488caa986d 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -59,11 +59,12 @@ static void setCardShadow(QWidget* edit) edit->setGraphicsEffect(shadowEffect); } -MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnModel, QWidget *parent) : +MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnModel, ClientModel* _clientModel, QWidget *parent) : FocusedDialog(parent), ui(new Ui::MasterNodeWizardDialog), walletModel(model), - mnModel(_mnModel) + mnModel(_mnModel), + clientModel(_clientModel) { ui->setupUi(this); setStyleSheet(parent->styleSheet()); @@ -145,17 +146,9 @@ void MasterNodeWizardDialog::initServicePage() initCssEditLine(ui->lineEditIpAddress); initCssEditLine(ui->lineEditPort); ui->stackedWidget->setCurrentIndex(pos); - if (walletModel->isRegTestNetwork()) { - ui->lineEditPort->setEnabled(true); - ui->lineEditPort->setText("51476"); - } else { - ui->lineEditPort->setEnabled(false); // fixed to default port number - if (walletModel->isTestNetwork()) { - ui->lineEditPort->setText("51474"); - } else { - ui->lineEditPort->setText("51472"); - } - } + // Fixed to default port number for mainnet and testnet. + ui->lineEditPort->setEnabled(walletModel->isRegTestNetwork()); + ui->lineEditPort->setText(QString::number(clientModel->getNetworkPort())); } void MasterNodeWizardDialog::initOwnerPage() diff --git a/src/qt/pivx/masternodewizarddialog.h b/src/qt/pivx/masternodewizarddialog.h index 1314c9a7a3191..f80240f197e39 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -10,6 +10,7 @@ #include "masternodeconfig.h" #include "qt/pivx/pwidget.h" +class ClientModel; class ContactsDropdown; class MNModel; class QLineEdit; @@ -61,6 +62,7 @@ enum Pages { public: explicit MasterNodeWizardDialog(WalletModel* walletMode, MNModel* mnModel, + ClientModel* clientModel, QWidget *parent = nullptr); ~MasterNodeWizardDialog() override; void showEvent(QShowEvent *event) override; @@ -100,6 +102,7 @@ private Q_SLOTS: WalletModel* walletModel{nullptr}; MNModel* mnModel{nullptr}; + ClientModel* clientModel{nullptr}; void initIntroPage(const QString& collateralAmountStr); void initCollateralPage(const QString& collateralAmountStr); From 326607a54fe5149e17a1f50d82dc106e3e7935c7 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 27 Feb 2022 19:53:53 -0300 Subject: [PATCH 40/82] test: extending base tier two MN network. --- .../test_framework/test_framework.py | 38 +++++++++++++++---- .../tiertwo_governance_sync_basic.py | 2 +- .../tiertwo_masternode_activation.py | 2 +- test/functional/tiertwo_mn_compatibility.py | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 877eb03340512..bf946c4417dcc 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1618,9 +1618,9 @@ def mine_quorum(self, invalidate_func=None, invalidated_idx=None, skip_bad_membe # !TODO: remove after obsoleting legacy system class PivxTier2TestFramework(PivxTestFramework): - def set_test_params(self): + def set_test_params(self, v6_enforcement_height = 250): self.setup_clean_chain = True - self.num_nodes = 6 + self.num_nodes = 8 self.enable_mocktime() self.ownerOnePos = 0 @@ -1629,17 +1629,20 @@ def set_test_params(self): self.remoteTwoPos = 3 self.minerPos = 4 self.remoteDMN1Pos = 5 - - self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:250", "-whitelist=127.0.0.1"]] * self.num_nodes + self.ownerThreePos = 6 + self.remoteThreePos = 7 + self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes for i in [self.remoteOnePos, self.remoteTwoPos, self.remoteDMN1Pos]: self.extra_args[i] += ["-listen", "-externalip=127.0.0.1"] self.extra_args[self.minerPos].append("-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi") self.masternodeOneAlias = "mnOne" self.masternodeTwoAlias = "mntwo" + self.masternodeThreeAlias = "mnThree" self.mnOnePrivkey = "9247iC59poZmqBYt9iDh9wDam6v9S1rW5XekjLGyPnDhrDkP4AK" self.mnTwoPrivkey = "92Hkebp3RHdDidGZ7ARgS4orxJAGyFUPDXNqtsYsiwho1HGVRbF" + self.mnThreePrivkey = "91qP855JNR3aWv6Z71BcFjqhkeizchSDjKSi7BdqMSSirEVDTEk" # Updated in setup_3_masternodes_network() to be called at the start of run_test self.ownerOne = None # self.nodes[self.ownerOnePos] @@ -1647,14 +1650,17 @@ def set_test_params(self): self.ownerTwo = None # self.nodes[self.ownerTwoPos] self.remoteTwo = None # self.nodes[self.remoteTwoPos] self.miner = None # self.nodes[self.minerPos] - self.remoteDMN1 = None # self.nodes[self.remoteDMN1Pos] + self.remoteDMN1 = None # self.nodes[self.remoteDMN1Pos] + self.ownerThree = None # self.nodes[self.ownerThreePos] + self.remoteThree = None # self.nodes[self.remoteThreePos] self.mnOneCollateral = COutPoint() self.mnTwoCollateral = COutPoint() + self.mnThreeCollateral = COutPoint() self.proRegTx1 = None # hash of provider-register-tx def send_3_pings(self): - mns = [self.remoteOne, self.remoteTwo] + mns = [self.remoteOne, self.remoteTwo, self.remoteThree] self.advance_mocktime(30) self.send_pings(mns) self.stake(1, mns) @@ -1668,12 +1674,15 @@ def stake(self, num_blocks, with_ping_mns=[]): def controller_start_all_masternodes(self): self.controller_start_masternode(self.ownerOne, self.masternodeOneAlias) self.controller_start_masternode(self.ownerTwo, self.masternodeTwoAlias) + self.controller_start_masternode(self.ownerThree, self.masternodeThreeAlias) self.wait_until_mn_preenabled(self.mnOneCollateral.hash, 40) self.wait_until_mn_preenabled(self.mnTwoCollateral.hash, 40) + self.wait_until_mn_preenabled(self.mnThreeCollateral.hash, 40) self.log.info("masternodes started, waiting until both get enabled..") self.send_3_pings() - self.wait_until_mn_enabled(self.mnOneCollateral.hash, 120, [self.remoteOne, self.remoteTwo]) - self.wait_until_mn_enabled(self.mnTwoCollateral.hash, 120, [self.remoteOne, self.remoteTwo]) + self.wait_until_mn_enabled(self.mnOneCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.wait_until_mn_enabled(self.mnTwoCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.wait_until_mn_enabled(self.mnThreeCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) self.log.info("masternodes enabled and running properly!") def advance_mocktime_and_stake(self, secs_to_add): @@ -1686,10 +1695,13 @@ def setup_3_masternodes_network(self): self.remoteOne = self.nodes[self.remoteOnePos] self.ownerTwo = self.nodes[self.ownerTwoPos] self.remoteTwo = self.nodes[self.remoteTwoPos] + self.ownerThree = self.nodes[self.ownerThreePos] + self.remoteThree = self.nodes[self.remoteThreePos] self.miner = self.nodes[self.minerPos] self.remoteDMN1 = self.nodes[self.remoteDMN1Pos] ownerOneDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerOnePos) ownerTwoDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerTwoPos) + ownerThreeDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerThreePos) self.log.info("generating 256 blocks..") # First mine 250 PoW blocks @@ -1716,6 +1728,14 @@ def setup_3_masternodes_network(self): os.path.join(ownerTwoDir, "regtest"), self.remoteTwoPos, self.mnTwoPrivkey) + # setup third masternode node, corresponding to nodeTwo + self.mnThreeCollateral = self.setupMasternode( + self.ownerThree, + self.miner, + self.masternodeThreeAlias, + os.path.join(ownerThreeDir, "regtest"), + self.remoteThreePos, + self.mnThreePrivkey) # setup deterministic masternode self.proRegTx1, self.dmn1Privkey = self.setupDMN( self.ownerOne, @@ -1732,8 +1752,10 @@ def setup_3_masternodes_network(self): self.advance_mocktime(10) remoteOnePort = p2p_port(self.remoteOnePos) remoteTwoPort = p2p_port(self.remoteTwoPos) + remoteThreePort = p2p_port(self.remoteThreePos) self.remoteOne.initmasternode(self.mnOnePrivkey, "127.0.0.1:"+str(remoteOnePort)) self.remoteTwo.initmasternode(self.mnTwoPrivkey, "127.0.0.1:"+str(remoteTwoPort)) + self.remoteThree.initmasternode(self.mnThreePrivkey, "127.0.0.1:"+str(remoteThreePort)) self.remoteDMN1.initmasternode(self.dmn1Privkey) # wait until mnsync complete on all nodes diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index f61e7ea761e3f..0b3f15147a452 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -178,7 +178,7 @@ def submit_proposals(self, props): def run_test(self): self.enable_mocktime() - self.setup_3_masternodes_network() + self.setup_masternodes_network() txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) # check mn list from miner self.check_mn_list(self.miner, txHashSet) diff --git a/test/functional/tiertwo_masternode_activation.py b/test/functional/tiertwo_masternode_activation.py index 2fdc631512eb6..e5c2b3cd9d6b6 100755 --- a/test/functional/tiertwo_masternode_activation.py +++ b/test/functional/tiertwo_masternode_activation.py @@ -68,7 +68,7 @@ def wait_until_mn_expired(self, _timeout, removed=False): def run_test(self): self.enable_mocktime() - self.setup_3_masternodes_network() + self.setup_masternodes_network() # check masternode expiration self.log.info("testing expiration now.") diff --git a/test/functional/tiertwo_mn_compatibility.py b/test/functional/tiertwo_mn_compatibility.py index 670696c420709..6dcf91f5b6aef 100755 --- a/test/functional/tiertwo_mn_compatibility.py +++ b/test/functional/tiertwo_mn_compatibility.py @@ -96,7 +96,7 @@ def check_mn_list(self, node, txHashSet): def run_test(self): self.mn_addresses = {} self.enable_mocktime() - self.setup_3_masternodes_network() + self.setup_masternodes_network() # start with 3 masternodes (2 legacy + 1 DMN) self.check_mn_enabled_count(3, 3) From 2b60344c91415b6f4a6aa4856a0e1dfeae603784 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 27 Feb 2022 19:55:14 -0300 Subject: [PATCH 41/82] test: decouple budget functions from governance_sync test to a budget util file --- test/functional/test_framework/budget_util.py | 130 ++++++++++++ .../tiertwo_governance_sync_basic.py | 186 ++++-------------- 2 files changed, 170 insertions(+), 146 deletions(-) create mode 100644 test/functional/test_framework/budget_util.py diff --git a/test/functional/test_framework/budget_util.py b/test/functional/test_framework/budget_util.py new file mode 100644 index 0000000000000..c3a63c4e883cd --- /dev/null +++ b/test/functional/test_framework/budget_util.py @@ -0,0 +1,130 @@ +from .util import ( + assert_equal, + assert_true, + satoshi_round, +) + +class Proposal: + def __init__(self, name, link, cycles, payment_addr, amount_per_cycle): + self.name = name + self.link = link + self.cycles = cycles + self.paymentAddr = payment_addr + self.amountPerCycle = amount_per_cycle + self.feeTxId = "" + self.proposalHash = "" + +def get_proposal_obj(Name, URL, Hash, FeeHash, BlockStart, BlockEnd, + TotalPaymentCount, RemainingPaymentCount, PaymentAddress, + Ratio, Yeas, Nays, Abstains, TotalPayment, MonthlyPayment, + IsEstablished, IsValid, Allotted, TotalBudgetAllotted, IsInvalidReason = ""): + obj = {} + obj["Name"] = Name + obj["URL"] = URL + obj["Hash"] = Hash + obj["FeeHash"] = FeeHash + obj["BlockStart"] = BlockStart + obj["BlockEnd"] = BlockEnd + obj["TotalPaymentCount"] = TotalPaymentCount + obj["RemainingPaymentCount"] = RemainingPaymentCount + obj["PaymentAddress"] = PaymentAddress + obj["Ratio"] = Ratio + obj["Yeas"] = Yeas + obj["Nays"] = Nays + obj["Abstains"] = Abstains + obj["TotalPayment"] = TotalPayment + obj["MonthlyPayment"] = MonthlyPayment + obj["IsEstablished"] = IsEstablished + obj["IsValid"] = IsValid + if IsInvalidReason != "": + obj["IsInvalidReason"] = IsInvalidReason + obj["Allotted"] = Allotted + obj["TotalBudgetAllotted"] = TotalBudgetAllotted + return obj + +def get_proposal(prop, block_start, alloted, total_budget_alloted, positive_votes): + blockEnd = block_start + prop.cycles * 145 + total_payment = prop.amountPerCycle * prop.cycles + return get_proposal_obj(prop.name, prop.link, prop.proposalHash, prop.feeTxId, block_start, + blockEnd, prop.cycles, prop.cycles, prop.paymentAddr, 1, + positive_votes, 0, 0, satoshi_round(total_payment), satoshi_round(prop.amountPerCycle), + True, True, satoshi_round(alloted), satoshi_round(total_budget_alloted)) + +def check_mns_status_legacy(node, txhash): + status = node.getmasternodestatus() + assert_equal(status["txhash"], txhash) + assert_equal(status["message"], "Masternode successfully started") + +def check_mns_status(node, txhash): + status = node.getmasternodestatus() + assert_equal(status["proTxHash"], txhash) + assert_equal(status["dmnstate"]["PoSePenalty"], 0) + assert_equal(status["status"], "Ready") + +def check_mn_list(node, txHashSet): + # check masternode list from node + mnlist = node.listmasternodes() + assert_equal(len(mnlist), 3) + foundHashes = set([mn["txhash"] for mn in mnlist if mn["txhash"] in txHashSet]) + assert_equal(len(foundHashes), len(txHashSet)) + +def check_budget_finalization_sync(nodes, votesCount, status): + for i in range(0, len(nodes)): + node = nodes[i] + budFin = node.mnfinalbudget("show") + assert_true(len(budFin) == 1, "MN budget finalization not synced in node" + str(i)) + budget = budFin[next(iter(budFin))] + assert_equal(budget["VoteCount"], votesCount) + assert_equal(budget["Status"], status) + +def check_proposal_existence(nodes, proposalName, proposalHash): + for node in nodes: + proposals = node.getbudgetinfo(proposalName) + assert(len(proposals) > 0) + assert_equal(proposals[0]["Hash"], proposalHash) + +def check_vote_existence(nodes, proposalName, mnCollateralHash, voteType, voteValid): + for i in range(0, len(nodes)): + node = nodes[i] + node.syncwithvalidationinterfacequeue() + votesInfo = node.getbudgetvotes(proposalName) + assert(len(votesInfo) > 0) + found = False + for voteInfo in votesInfo: + if (voteInfo["mnId"].split("-")[0] == mnCollateralHash) : + assert_equal(voteInfo["Vote"], voteType) + assert_equal(voteInfo["fValid"], voteValid) + found = True + assert_true(found, "Error checking vote existence in node " + str(i)) + +def check_budgetprojection(nodes, expected, log): + for i in range(len(nodes)): + assert_equal(nodes[i].getbudgetprojection(), expected) + log.info("Budget projection valid for node %d" % i) + +def create_proposals_tx(miner, props): + nextSuperBlockHeight = miner.getnextsuperblock() + for entry in props: + proposalFeeTxId = miner.preparebudget( + entry.name, + entry.link, + entry.cycles, + nextSuperBlockHeight, + entry.paymentAddr, + entry.amountPerCycle) + entry.feeTxId = proposalFeeTxId + return props + +def propagate_proposals(miner, props): + nextSuperBlockHeight = miner.getnextsuperblock() + for entry in props: + proposalHash = miner.submitbudget( + entry.name, + entry.link, + entry.cycles, + nextSuperBlockHeight, + entry.paymentAddr, + entry.amountPerCycle, + entry.feeTxId) + entry.proposalHash = proposalHash + return props \ No newline at end of file diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 0b3f15147a452..1bcb9ef3adaf4 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -15,9 +15,21 @@ from test_framework.messages import COutPoint from test_framework.test_framework import PivxTier2TestFramework +from test_framework.budget_util import ( + check_budget_finalization_sync, + create_proposals_tx, + check_budgetprojection, + check_proposal_existence, + check_mn_list, + check_mns_status_legacy, + check_mns_status, + check_vote_existence, + get_proposal_obj, + Proposal, + propagate_proposals +) from test_framework.util import ( assert_equal, - assert_true, connect_nodes, get_datadir_path, satoshi_round @@ -25,45 +37,8 @@ import shutil import os -class Proposal: - def __init__(self, name, link, cycles, payment_addr, amount_per_cycle): - self.name = name - self.link = link - self.cycles = cycles - self.paymentAddr = payment_addr - self.amountPerCycle = amount_per_cycle - self.feeTxId = "" - self.proposalHash = "" - class MasternodeGovernanceBasicTest(PivxTier2TestFramework): - def check_mns_status_legacy(self, node, txhash): - status = node.getmasternodestatus() - assert_equal(status["txhash"], txhash) - assert_equal(status["message"], "Masternode successfully started") - - def check_mns_status(self, node, txhash): - status = node.getmasternodestatus() - assert_equal(status["proTxHash"], txhash) - assert_equal(status["dmnstate"]["PoSePenalty"], 0) - assert_equal(status["status"], "Ready") - - def check_mn_list(self, node, txHashSet): - # check masternode list from node - mnlist = node.listmasternodes() - assert_equal(len(mnlist), 3) - foundHashes = set([mn["txhash"] for mn in mnlist if mn["txhash"] in txHashSet]) - assert_equal(len(foundHashes), len(txHashSet)) - - def check_budget_finalization_sync(self, votesCount, status): - for i in range(0, len(self.nodes)): - node = self.nodes[i] - budFin = node.mnfinalbudget("show") - assert_true(len(budFin) == 1, "MN budget finalization not synced in node" + str(i)) - budget = budFin[next(iter(budFin))] - assert_equal(budget["VoteCount"], votesCount) - assert_equal(budget["Status"], status) - def broadcastbudgetfinalization(self, node, with_ping_mns=[]): self.log.info("suggesting the budget finalization..") assert (node.mnfinalbudgetsuggest() is not None) @@ -75,92 +50,12 @@ def broadcastbudgetfinalization(self, node, with_ping_mns=[]): self.log.info("broadcasting the budget finalization..") return node.mnfinalbudgetsuggest() - def check_proposal_existence(self, proposalName, proposalHash): - for node in self.nodes: - proposals = node.getbudgetinfo(proposalName) - assert(len(proposals) > 0) - assert_equal(proposals[0]["Hash"], proposalHash) - - def check_vote_existence(self, proposalName, mnCollateralHash, voteType, voteValid): - for i in range(0, len(self.nodes)): - node = self.nodes[i] - node.syncwithvalidationinterfacequeue() - votesInfo = node.getbudgetvotes(proposalName) - assert(len(votesInfo) > 0) - found = False - for voteInfo in votesInfo: - if (voteInfo["mnId"].split("-")[0] == mnCollateralHash) : - assert_equal(voteInfo["Vote"], voteType) - assert_equal(voteInfo["fValid"], voteValid) - found = True - assert_true(found, "Error checking vote existence in node " + str(i)) - - def get_proposal_obj(self, Name, URL, Hash, FeeHash, BlockStart, BlockEnd, - TotalPaymentCount, RemainingPaymentCount, PaymentAddress, - Ratio, Yeas, Nays, Abstains, TotalPayment, MonthlyPayment, - IsEstablished, IsValid, Allotted, TotalBudgetAllotted, IsInvalidReason = ""): - obj = {} - obj["Name"] = Name - obj["URL"] = URL - obj["Hash"] = Hash - obj["FeeHash"] = FeeHash - obj["BlockStart"] = BlockStart - obj["BlockEnd"] = BlockEnd - obj["TotalPaymentCount"] = TotalPaymentCount - obj["RemainingPaymentCount"] = RemainingPaymentCount - obj["PaymentAddress"] = PaymentAddress - obj["Ratio"] = Ratio - obj["Yeas"] = Yeas - obj["Nays"] = Nays - obj["Abstains"] = Abstains - obj["TotalPayment"] = TotalPayment - obj["MonthlyPayment"] = MonthlyPayment - obj["IsEstablished"] = IsEstablished - obj["IsValid"] = IsValid - if IsInvalidReason != "": - obj["IsInvalidReason"] = IsInvalidReason - obj["Allotted"] = Allotted - obj["TotalBudgetAllotted"] = TotalBudgetAllotted - return obj - - def check_budgetprojection(self, expected): - for i in range(self.num_nodes): - assert_equal(self.nodes[i].getbudgetprojection(), expected) - self.log.info("Budget projection valid for node %d" % i) - def connect_nodes_bi(self, nodes, a, b): connect_nodes(nodes[a], b) connect_nodes(nodes[b], a) - def create_proposals_tx(self, props): - nextSuperBlockHeight = self.miner.getnextsuperblock() - for entry in props: - proposalFeeTxId = self.miner.preparebudget( - entry.name, - entry.link, - entry.cycles, - nextSuperBlockHeight, - entry.paymentAddr, - entry.amountPerCycle) - entry.feeTxId = proposalFeeTxId - return props - - def propagate_proposals(self, props): - nextSuperBlockHeight = self.miner.getnextsuperblock() - for entry in props: - proposalHash = self.miner.submitbudget( - entry.name, - entry.link, - entry.cycles, - nextSuperBlockHeight, - entry.paymentAddr, - entry.amountPerCycle, - entry.feeTxId) - entry.proposalHash = proposalHash - return props - def submit_proposals(self, props): - props = self.create_proposals_tx(props) + props = create_proposals_tx(self.miner, props) # generate 3 blocks to confirm the tx (and update the mnping) self.stake(3, [self.remoteOne, self.remoteTwo]) # check fee tx existence @@ -168,11 +63,11 @@ def submit_proposals(self, props): txinfo = self.miner.gettransaction(entry.feeTxId) assert_equal(txinfo['amount'], -50.00) # propagate proposals - props = self.propagate_proposals(props) + props = propagate_proposals(self.miner, props) # let's wait a little bit and see if all nodes are sync time.sleep(1) for entry in props: - self.check_proposal_existence(entry.name, entry.proposalHash) + check_proposal_existence(self.nodes, entry.name, entry.proposalHash) self.log.info("proposal %s broadcast successful!" % entry.name) return props @@ -181,14 +76,14 @@ def run_test(self): self.setup_masternodes_network() txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) # check mn list from miner - self.check_mn_list(self.miner, txHashSet) + check_mn_list(self.miner, txHashSet) # check status of masternodes - self.check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) + check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) self.log.info("MN1 active") - self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) + check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) self.log.info("MN2 active") - self.check_mns_status(self.remoteDMN1, self.proRegTx1) + check_mns_status(self.remoteDMN1, self.proRegTx1) self.log.info("DMN1 active") # activate sporks @@ -232,7 +127,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposal.name, self.mnOneCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) self.log.info("all good, MN1 vote accepted everywhere!") # before broadcast the second vote, let's drop the budget data of ownerOne. @@ -250,13 +145,13 @@ def run_test(self): # check orphan vote proposal re-sync self.log.info("checking orphan vote based proposal re-sync...") time.sleep(5) # wait a bit before check it - self.check_proposal_existence(firstProposal.name, firstProposal.proposalHash) - self.check_vote_existence(firstProposal.name, self.mnOneCollateral.hash, "YES", True) + check_proposal_existence(self.nodes, firstProposal.name, firstProposal.proposalHash) + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) self.log.info("all good, orphan vote based proposal re-sync succeeded") # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposal.name, self.mnTwoCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.mnTwoCollateral.hash, "YES", True) self.log.info("all good, MN2 vote accepted everywhere!") # now let's vote for the proposal with the first DMN @@ -266,7 +161,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposal.name, self.proRegTx1, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", True) self.log.info("all good, DMN1 vote accepted everywhere!") # Now check the budget @@ -275,13 +170,12 @@ def run_test(self): TotalPayment = firstProposal.amountPerCycle * firstProposal.cycles Allotted = firstProposal.amountPerCycle RemainingPaymentCount = firstProposal.cycles - expected_budget = [ - self.get_proposal_obj(firstProposal.name, firstProposal.link, firstProposal.proposalHash, firstProposal.feeTxId, blockStart, + expected_budget = [get_proposal_obj(firstProposal.name, firstProposal.link, firstProposal.proposalHash, firstProposal.feeTxId, blockStart, blockEnd, firstProposal.cycles, RemainingPaymentCount, firstProposal.paymentAddr, 1, 3, 0, 0, satoshi_round(TotalPayment), satoshi_round(firstProposal.amountPerCycle), True, True, satoshi_round(Allotted), satoshi_round(Allotted)) ] - self.check_budgetprojection(expected_budget) + check_budgetprojection(self.nodes, expected_budget, self.log) # Quick block count check. assert_equal(self.ownerOne.getblockcount(), 279) @@ -299,7 +193,7 @@ def run_test(self): time.sleep(2) self.log.info("checking budget finalization sync..") - self.check_budget_finalization_sync(0, "OK") + check_budget_finalization_sync(self.nodes, 0, "OK") self.log.info("budget finalization synced!, now voting for the budget finalization..") # Connecting owner to all the other nodes. @@ -309,7 +203,7 @@ def run_test(self): assert_equal(voteResult["detail"][0]["result"], "success") time.sleep(2) # wait a bit self.stake(2, [self.remoteOne, self.remoteTwo]) - self.check_budget_finalization_sync(1, "OK") + check_budget_finalization_sync(self.nodes, 1, "OK") self.log.info("Remote One voted successfully.") # before broadcast the second finalization vote, let's drop the budget data of remoteOne. @@ -330,7 +224,7 @@ def run_test(self): self.stake(2, [self.remoteOne, self.remoteTwo]) self.log.info("checking finalization votes..") - self.check_budget_finalization_sync(3, "OK") + check_budget_finalization_sync(self.nodes, 3, "OK") self.log.info("orphan vote based finalization re-sync succeeded") self.stake(6, [self.remoteOne, self.remoteTwo]) @@ -341,7 +235,7 @@ def run_test(self): # Check that the proposal info returns updated payment count expected_budget[0]["RemainingPaymentCount"] -= 1 - self.check_budgetprojection(expected_budget) + check_budgetprojection(self.nodes, expected_budget, self.log) self.stake(1, [self.remoteOne, self.remoteTwo]) @@ -354,7 +248,7 @@ def run_test(self): self.log.info("budget cleaned, starting resync") self.wait_until_mnsync_finished() - self.check_budgetprojection(expected_budget) + check_budgetprojection(self.nodes, expected_budget, self.log) for i in range(self.num_nodes): assert_equal(len(self.nodes[i].getbudgetinfo()), 16) @@ -393,10 +287,10 @@ def run_test(self): self.log.info("Checking budget sync..") for i in range(self.num_nodes): assert_equal(len(self.nodes[i].getbudgetinfo()), 16) - self.check_vote_existence(firstProposal.name, self.mnOneCollateral.hash, "YES", True) - self.check_vote_existence(firstProposal.name, self.mnTwoCollateral.hash, "YES", True) - self.check_vote_existence(firstProposal.name, self.proRegTx1, "YES", True) - self.check_budget_finalization_sync(3, "OK") + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.mnTwoCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", True) + check_budget_finalization_sync(self.nodes, 3, "OK") self.log.info("Remote incremental sync succeeded") # now let's verify that votes expire properly. @@ -406,8 +300,8 @@ def run_test(self): self.wait_until_mn_vinspent(self.mnOneCollateral.hash, 30, [self.remoteTwo]) self.stake(15, [self.remoteTwo]) # create blocks to remove staled votes time.sleep(2) # wait a little bit - self.check_vote_existence(firstProposal.name, self.mnOneCollateral.hash, "YES", False) - self.check_budget_finalization_sync(2, "OK") # budget finalization vote removal + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", False) + check_budget_finalization_sync(self.nodes, 2, "OK") # budget finalization vote removal self.log.info("MN1 vote expired after collateral spend, all good") self.log.info("expiring DMN1..") @@ -416,8 +310,8 @@ def run_test(self): self.wait_until_mn_vinspent(self.proRegTx1, 30, [self.remoteTwo]) self.stake(15, [self.remoteTwo]) # create blocks to remove staled votes time.sleep(2) # wait a little bit - self.check_vote_existence(firstProposal.name, self.proRegTx1, "YES", False) - self.check_budget_finalization_sync(1, "OK") # budget finalization vote removal + check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", False) + check_budget_finalization_sync(self.nodes, 1, "OK") # budget finalization vote removal self.log.info("DMN vote expired after collateral spend, all good") # Check that the budget is removed 200 blocks after the last payment From 8b758b1651e31b2c882a561884e5a549b36ea59d Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 27 Feb 2022 19:55:39 -0300 Subject: [PATCH 42/82] test: implement single block payments budget test --- test/functional/test_framework/budget_util.py | 3 +- .../test_framework/test_framework.py | 30 +- test/functional/tiertwo_budget.py | 290 ++++++++++++++++++ 3 files changed, 310 insertions(+), 13 deletions(-) create mode 100755 test/functional/tiertwo_budget.py diff --git a/test/functional/test_framework/budget_util.py b/test/functional/test_framework/budget_util.py index c3a63c4e883cd..edc5f8a5caf97 100644 --- a/test/functional/test_framework/budget_util.py +++ b/test/functional/test_framework/budget_util.py @@ -1,5 +1,6 @@ from .util import ( assert_equal, + assert_greater_than_or_equal, assert_true, satoshi_round, ) @@ -72,7 +73,7 @@ def check_budget_finalization_sync(nodes, votesCount, status): for i in range(0, len(nodes)): node = nodes[i] budFin = node.mnfinalbudget("show") - assert_true(len(budFin) == 1, "MN budget finalization not synced in node" + str(i)) + assert_greater_than_or_equal(len(budFin), 1) budget = budFin[next(iter(budFin))] assert_equal(budget["VoteCount"], votesCount) assert_equal(budget["Status"], status) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index bf946c4417dcc..fc8eee88c56c4 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1618,7 +1618,11 @@ def mine_quorum(self, invalidate_func=None, invalidated_idx=None, skip_bad_membe # !TODO: remove after obsoleting legacy system class PivxTier2TestFramework(PivxTestFramework): - def set_test_params(self, v6_enforcement_height = 250): + def __init__(self): + super().__init__() + self.v6_enforcement_height = 250 + + def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 8 self.enable_mocktime() @@ -1631,7 +1635,7 @@ def set_test_params(self, v6_enforcement_height = 250): self.remoteDMN1Pos = 5 self.ownerThreePos = 6 self.remoteThreePos = 7 - self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes + self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(self.v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes for i in [self.remoteOnePos, self.remoteTwoPos, self.remoteDMN1Pos]: self.extra_args[i] += ["-listen", "-externalip=127.0.0.1"] self.extra_args[self.minerPos].append("-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi") @@ -1644,7 +1648,7 @@ def set_test_params(self, v6_enforcement_height = 250): self.mnTwoPrivkey = "92Hkebp3RHdDidGZ7ARgS4orxJAGyFUPDXNqtsYsiwho1HGVRbF" self.mnThreePrivkey = "91qP855JNR3aWv6Z71BcFjqhkeizchSDjKSi7BdqMSSirEVDTEk" - # Updated in setup_3_masternodes_network() to be called at the start of run_test + # Updated in setup_masternodes_network() to be called at the start of run_test self.ownerOne = None # self.nodes[self.ownerOnePos] self.remoteOne = None # self.nodes[self.remoteOnePos] self.ownerTwo = None # self.nodes[self.ownerTwoPos] @@ -1690,7 +1694,7 @@ def advance_mocktime_and_stake(self, secs_to_add): self.mocktime = self.generate_pos(self.minerPos, self.mocktime) time.sleep(2) - def setup_3_masternodes_network(self): + def setup_masternodes_network(self, setup_dmn=True): self.ownerOne = self.nodes[self.ownerOnePos] self.remoteOne = self.nodes[self.remoteOnePos] self.ownerTwo = self.nodes[self.ownerTwoPos] @@ -1736,13 +1740,14 @@ def setup_3_masternodes_network(self): os.path.join(ownerThreeDir, "regtest"), self.remoteThreePos, self.mnThreePrivkey) - # setup deterministic masternode - self.proRegTx1, self.dmn1Privkey = self.setupDMN( - self.ownerOne, - self.miner, - self.remoteDMN1Pos, - "fund" - ) + if setup_dmn: + # setup deterministic masternode + self.proRegTx1, self.dmn1Privkey = self.setupDMN( + self.ownerOne, + self.miner, + self.remoteDMN1Pos, + "fund" + ) self.log.info("masternodes setup completed, initializing them..") @@ -1756,7 +1761,8 @@ def setup_3_masternodes_network(self): self.remoteOne.initmasternode(self.mnOnePrivkey, "127.0.0.1:"+str(remoteOnePort)) self.remoteTwo.initmasternode(self.mnTwoPrivkey, "127.0.0.1:"+str(remoteTwoPort)) self.remoteThree.initmasternode(self.mnThreePrivkey, "127.0.0.1:"+str(remoteThreePort)) - self.remoteDMN1.initmasternode(self.dmn1Privkey) + if setup_dmn: + self.remoteDMN1.initmasternode(self.dmn1Privkey) # wait until mnsync complete on all nodes self.stake(1) diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py new file mode 100755 index 0000000000000..133e6919f7e50 --- /dev/null +++ b/test/functional/tiertwo_budget.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The PIVX developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. +""" +Test checking: + 1) pre-v6 multi-block payments + 2) different payment ordering validation (proposals paid in different order) + 3) duplicated payments validation --> wrong path + 4) post-v6 single block payments + 5) single block payments ordering validation (proposals paid in different order) + 6) duplicated payments validation --> wrong path +""" + +import time + +from test_framework.test_framework import PivxTier2TestFramework +from test_framework.util import ( + assert_equal +) +from test_framework.budget_util import ( + check_budget_finalization_sync, + create_proposals_tx, + check_budgetprojection, + check_proposal_existence, + check_mn_list, + check_mns_status_legacy, + check_vote_existence, + get_proposal, + Proposal, + propagate_proposals +) + +class BudgetTest(PivxTier2TestFramework): + + def set_test_params(self): + self.v6_enforcement_height = 300 + super().set_test_params() + + def broadcastbudgetfinalization(self, node, with_ping_mns=[]): + self.log.info("suggesting the budget finalization..") + assert (node.mnfinalbudgetsuggest() is not None) + + self.log.info("confirming the budget finalization..") + time.sleep(1) + self.stake(4, with_ping_mns) + + self.log.info("broadcasting the budget finalization..") + return node.mnfinalbudgetsuggest() + + def submit_proposals(self, props): + props = create_proposals_tx(self.miner, props) + # generate 3 blocks to confirm the tx (and update the mnping) + self.stake(3, [self.remoteOne, self.remoteTwo, self.remoteThree]) + # check fee tx existence + for entry in props: + txinfo = self.miner.gettransaction(entry.feeTxId) + assert_equal(txinfo['amount'], -50.00) + # propagate proposals + props = propagate_proposals(self.miner, props) + # let's wait a little bit and see if all nodes are sync + time.sleep(1) + for entry in props: + check_proposal_existence(self.nodes, entry.name, entry.proposalHash) + self.log.info("proposal %s broadcast successful!" % entry.name) + return props + + def vote_legacy(self, node_voter, proposal, vote_direction, mn_voter_alias): + self.log.info("Voting with " + mn_voter_alias + ", for: " + proposal.name) + voteResult = node_voter.mnbudgetvote("alias", proposal.proposalHash, vote_direction, mn_voter_alias, True) + assert_equal(voteResult["detail"][0]["result"], "success") + time.sleep(1) + + def vote(self, node_voter, proposal, vote_direction, pro_reg_tx): + self.log.info("Voting with DMN " + pro_reg_tx + ", for: " + proposal.name) + voteResult = node_voter.mnbudgetvote("alias", proposal.proposalHash, vote_direction, pro_reg_tx) + assert_equal(voteResult["detail"][0]["result"], "success") + time.sleep(1) + + def vote_finalization(self, voting_node, budget_fin_hash, legacy): + voteResult = voting_node.mnfinalbudget("vote-many" if legacy else "vote", budget_fin_hash, legacy) + assert_equal(voteResult["detail"][0]["result"], "success") + + def check_address_balance(self, addr, expected_balance, has_balance=True): + addrInfo = self.miner.listreceivedbyaddress(0, False, False, addr) + if has_balance: + assert_equal(addrInfo[0]["amount"], expected_balance) + else: + assert_equal(len(addrInfo), 0) + + def check_block_proposal_payment(self, block_hash, expected_to_address, expected_to_value, expected_out_index, is_v6_active): + block = self.miner.getblock(block_hash) + if is_v6_active: + # Get the coinbase second output that is the proposal payment + coinbase_tx = self.miner.getrawtransaction(block["tx"][0], True) + proposal_out = coinbase_tx["vout"][expected_out_index] + assert_equal(proposal_out["value"], expected_to_value) + assert_equal(proposal_out["scriptPubKey"]["addresses"][0], expected_to_address) + else: + # Get the coinstake third output + coinstake_tx = self.miner.getrawtransaction(block["tx"][1], True) + proposal_out = coinstake_tx["vout"][expected_out_index] + assert_equal(proposal_out["value"], expected_to_value) + assert_equal(proposal_out["scriptPubKey"]["addresses"][0], expected_to_address) + + def finalize_and_vote_budget(self): + # suggest the budget finalization and confirm the tx (+4 blocks). + budgetFinHash = self.broadcastbudgetfinalization(self.miner, + with_ping_mns=[self.remoteOne, self.remoteTwo, self.remoteThree]) + assert (budgetFinHash != "") + time.sleep(2) + print(budgetFinHash) + self.log.info("voting budget finalization..") + for node in [self.ownerOne, self.ownerTwo, self.ownerThree]: + self.vote_finalization(node, budgetFinHash, True) + time.sleep(2) # wait a bit + check_budget_finalization_sync(self.nodes, 3, "OK") + + def run_test(self): + self.enable_mocktime() + self.setup_masternodes_network(setup_dmn=False) + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.mnThreeCollateral.hash]) + # check mn list from miner + check_mn_list(self.miner, txHashSet) + + # check status of masternodes + check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) + check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) + check_mns_status_legacy(self.remoteThree, self.mnThreeCollateral.hash) + + # activate sporks + self.activate_spork(self.minerPos, "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT") + self.activate_spork(self.minerPos, "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT") + self.activate_spork(self.minerPos, "SPORK_13_ENABLE_SUPERBLOCKS") + nextSuperBlockHeight = self.miner.getnextsuperblock() + + # Submit first proposal + self.log.info("preparing budget proposal..") + # Create 15 more proposals to have a higher tier two net gossip movement + props = [] + for i in range(16): + props.append(Proposal("prop_"+str(i), + "https://link_"+str(i)+".com", + 3, + self.miner.getnewaddress(), + 11 * (i + 1))) + self.submit_proposals(props) + + # Proposals are established after 5 minutes. Mine 7 blocks + # Proposal needs to be on the chain > 5 min. + self.stake(7, [self.remoteOne, self.remoteTwo, self.remoteThree]) + # Check proposals existence + for i in range(self.num_nodes): + assert_equal(len(self.nodes[i].getbudgetinfo()), 16) + + # now let's vote for the two first proposals + expected_budget = [] + blockStart = nextSuperBlockHeight + alloted = 0 + for i in range(2): + prop = props[i] + self.vote_legacy(self.ownerOne, prop, "yes", self.masternodeOneAlias) + check_vote_existence(self.nodes, prop.name, self.mnOneCollateral.hash, "YES", True) + self.vote_legacy(self.ownerTwo, prop, "yes", self.masternodeTwoAlias) + check_vote_existence(self.nodes, prop.name, self.mnTwoCollateral.hash, "YES", True) + if i < 1: + self.vote_legacy(self.ownerThree, prop, "yes", self.masternodeThreeAlias) + check_vote_existence(self.nodes, prop.name, self.mnThreeCollateral.hash, "YES", True) + alloted += prop.amountPerCycle + expected_budget.append(get_proposal(prop, blockStart, prop.amountPerCycle, alloted, 3 - i)) + + # Now check the budget + check_budgetprojection(self.nodes, expected_budget, self.log) + + # Quick block count check. + assert_equal(self.ownerOne.getblockcount(), 272) + self.stake(10, [self.remoteOne, self.remoteTwo, self.remoteThree]) + # Finalize budget + self.finalize_and_vote_budget() + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + # Check first proposal payment + prop1 = props[0] + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop1.paymentAddr, prop1.amountPerCycle, 2, False) + self.check_address_balance(prop1.paymentAddr, prop1.amountPerCycle) + + # Check second proposal payment + prop2 = props[1] + assert prop2.paymentAddr is not prop1.paymentAddr + self.check_address_balance(prop2.paymentAddr, 0, has_balance=False) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop2.paymentAddr, prop2.amountPerCycle, 2, False) + self.check_address_balance(prop2.paymentAddr, prop2.amountPerCycle) + + # Check that the proposal info returns updated payment count + expected_budget[0]["RemainingPaymentCount"] -= 1 + expected_budget[1]["RemainingPaymentCount"] -= 1 + check_budgetprojection(self.nodes, expected_budget, self.log) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + self.log.info("pre-v6 budget proposal paid, all good. Testing enforcement now..") + + ################################################################## + # Now test post enforcement, active from block 300 + for _ in range(4): + self.miner.generate(30) + self.stake_and_ping(self.minerPos, 1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + next_super_block = self.miner.getnextsuperblock() + block_count = self.miner.getblockcount() + self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) + assert_equal(self.ownerOne.getblockcount(), 426) + # Finalize budget + self.finalize_and_vote_budget() + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.log.info("checking single block payments..") + assert_equal(self.ownerOne.getblockcount(), 432) + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop1.paymentAddr, prop1.amountPerCycle, 1, True) + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop2.paymentAddr, prop2.amountPerCycle, 2, True) + + # Check that the proposal info returns updated payment count + expected_budget[0]["RemainingPaymentCount"] -= 1 + expected_budget[1]["RemainingPaymentCount"] -= 1 + check_budgetprojection(self.nodes, expected_budget, self.log) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.log.info("post-v6 budget proposal paid, all good.") + + ################################################################## + self.log.info("Now test proposal with duplicate script and value") + + self.proRegTx1, self.dmn1Privkey = self.setupDMN( + self.ownerOne, + self.miner, + self.remoteDMN1Pos, + "fund" + ) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + time.sleep(3) + self.advance_mocktime(10) + self.remoteDMN1.initmasternode(self.dmn1Privkey) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + # Now test creating a new proposal paying to the same script and value as prop1 + blockStart = self.miner.getnextsuperblock() + prop17 = Proposal("prop_17", "https://link_"+str(17)+".com", 2, prop1.paymentAddr, 11) + self.submit_proposals([prop17]) + self.stake(5, [self.remoteOne, self.remoteTwo, self.remoteThree]) + for i in range(self.num_nodes): + assert_equal(len(self.nodes[i].getbudgetinfo()), 17) + # vote prop17 + self.vote_legacy(self.ownerOne, prop17, "yes", self.masternodeOneAlias) + check_vote_existence(self.nodes, prop17.name, self.mnOneCollateral.hash, "YES", True) + self.vote_legacy(self.ownerTwo, prop17, "yes", self.masternodeTwoAlias) + check_vote_existence(self.nodes, prop17.name, self.mnTwoCollateral.hash, "YES", True) + self.vote_legacy(self.ownerThree, prop17, "yes", self.masternodeThreeAlias) + check_vote_existence(self.nodes, prop17.name, self.mnThreeCollateral.hash, "YES", True) + self.vote(self.ownerOne, prop17, "yes", self.proRegTx1) + check_vote_existence(self.nodes, prop17.name, self.proRegTx1, "YES", True) + + alloted += prop17.amountPerCycle + expected_budget.insert(0, get_proposal(prop17, blockStart, prop17.amountPerCycle, prop17.amountPerCycle, 4)) + expected_budget[1]["TotalBudgetAllotted"] = Decimal('22.00000000') + expected_budget[2]["TotalBudgetAllotted"] = Decimal('44.00000000') + # Now check the budget + check_budgetprojection(self.nodes, expected_budget, self.log) + + for _ in range(4): + self.miner.generate(30) + self.stake_and_ping(self.minerPos, 1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + next_super_block = self.miner.getnextsuperblock() + block_count = self.miner.getblockcount() + self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) + assert_equal(self.ownerOne.getblockcount(), 570) + + # Finalize budget + self.finalize_and_vote_budget() + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.check_address_balance(prop1.paymentAddr, prop1.amountPerCycle * 4) # prop1.amountPerCycle * 3 + prop17.amountPerCycle + self.check_address_balance(prop2.paymentAddr, prop2.amountPerCycle * 3) + + # Check that the proposal info returns updated payment count + expected_budget[0]["RemainingPaymentCount"] -= 1 + expected_budget[1]["RemainingPaymentCount"] -= 1 + expected_budget[2]["RemainingPaymentCount"] -= 1 + check_budgetprojection(self.nodes, expected_budget, self.log) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.log.info("post-v6 duplicate proposals payouts paid.") + + +if __name__ == '__main__': + BudgetTest().main() From 81b6d96f0ecae706377b8923ad956f05c4d56b8d Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 3 Mar 2022 10:35:27 -0300 Subject: [PATCH 43/82] test: post-v6 enforcement, check coinstake output value --- test/functional/tiertwo_budget.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py index 133e6919f7e50..28e196e5e5df4 100755 --- a/test/functional/tiertwo_budget.py +++ b/test/functional/tiertwo_budget.py @@ -14,6 +14,7 @@ import time +from decimal import Decimal from test_framework.test_framework import PivxTier2TestFramework from test_framework.util import ( assert_equal @@ -30,6 +31,9 @@ Proposal, propagate_proposals ) +from test_framework.messages import ( + COIN +) class BudgetTest(PivxTier2TestFramework): @@ -201,10 +205,28 @@ def run_test(self): self.log.info("pre-v6 budget proposal paid, all good. Testing enforcement now..") ################################################################## + self.log.info("checking post enforcement coinstake value..") # Now test post enforcement, active from block 300 for _ in range(4): self.miner.generate(30) self.stake_and_ping(self.minerPos, 1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + # Post-v6 enforcement + # Get the coinstake and check that the input value is equal to + # the output value + block reward - MN payment. + BLOCK_REWARD = Decimal(250 * COIN) + MN_BLOCK_REWARD = Decimal(3 * COIN) + tx_coinstake_id = self.miner.getblock(self.miner.getbestblockhash(), True)["tx"][1] + tx_coinstake = self.miner.getrawtransaction(tx_coinstake_id, True) + tx_coinstake_out_value = Decimal(tx_coinstake["vout"][1]["value"]) * COIN + tx_coinstake_vin = tx_coinstake["vin"][0] + tx_coinstake_input = self.miner.getrawtransaction(tx_coinstake_vin["txid"], True) + tx_coinstake_input_value = Decimal(tx_coinstake_input["vout"][int(tx_coinstake_vin["vout"])]["value"]) * COIN + assert(tx_coinstake_out_value == tx_coinstake_input_value + BLOCK_REWARD - MN_BLOCK_REWARD) + + ############################################################## + # Check single block payments + self.log.info("mining until next superblock..") next_super_block = self.miner.getnextsuperblock() block_count = self.miner.getblockcount() self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) From c06de4a207d76f2fce28ea48f41edd2cfbed55f8 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 4 Feb 2022 14:50:20 +0100 Subject: [PATCH 44/82] Budget-Util: implement IsSuperBlock function --- src/budget/budgetproposal.cpp | 5 +++-- src/budget/budgetutil.cpp | 8 +++++++- src/budget/budgetutil.h | 3 +++ src/budget/finalizedbudget.cpp | 3 ++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/budget/budgetproposal.cpp b/src/budget/budgetproposal.cpp index 08fa76a00774f..12e0533f5d823 100644 --- a/src/budget/budgetproposal.cpp +++ b/src/budget/budgetproposal.cpp @@ -4,6 +4,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "budget/budgetproposal.h" + +#include "budget/budgetutil.h" #include "chainparams.h" #include "script/standard.h" @@ -85,8 +87,7 @@ bool CBudgetProposal::IsHeavilyDownvoted(int mnCount) bool CBudgetProposal::CheckStartEnd() { // block start must be a superblock - if (nBlockStart < 0 || - nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) { + if (nBlockStart < 0 || !IsSuperBlock(nBlockStart)) { strInvalid = "Invalid nBlockStart"; return false; } diff --git a/src/budget/budgetutil.cpp b/src/budget/budgetutil.cpp index ec9ca8aa78b88..6deadb125d6ea 100644 --- a/src/budget/budgetutil.cpp +++ b/src/budget/budgetutil.cpp @@ -4,6 +4,7 @@ #include "budget/budgetutil.h" +#include "chainparams.h" #include "budget/budgetmanager.h" #include "masternodeman.h" #include "masternodeconfig.h" @@ -269,4 +270,9 @@ UniValue mnLocalBudgetVoteInner(bool fLegacyMN, const uint256& budgetHash, bool return (fFinal ? voteFinalBudget(budgetHash, mnKeys, resultsObj, 0) : voteProposal(budgetHash, nVote, mnKeys, resultsObj, 0)); -} \ No newline at end of file +} + +bool IsSuperBlock(int height) +{ + return height % Params().GetConsensus().nBudgetCycleBlocks == 0; +} diff --git a/src/budget/budgetutil.h b/src/budget/budgetutil.h index a2b350bfe6b97..49627f64702e2 100644 --- a/src/budget/budgetutil.h +++ b/src/budget/budgetutil.h @@ -24,4 +24,7 @@ UniValue mnLocalBudgetVoteInner(bool fLegacyMN, const uint256& budgetHash, bool UniValue mnBudgetVoteInner(CWallet* const pwallet, bool fLegacyMN, const uint256& budgetHash, bool fFinal, const CBudgetVote::VoteDirection& nVote, const Optional& mnAliasFilter); +// One every Consensus::Params:nBudgetCycleBlocks blocks +bool IsSuperBlock(int height); + #endif //PIVX_BUDGETUTIL_H diff --git a/src/budget/finalizedbudget.cpp b/src/budget/finalizedbudget.cpp index 52091a4fce817..3379c6fbd93d6 100644 --- a/src/budget/finalizedbudget.cpp +++ b/src/budget/finalizedbudget.cpp @@ -5,6 +5,7 @@ #include "budget/finalizedbudget.h" +#include "budget/budgetutil.h" #include "masternodeman.h" #include "validation.h" @@ -187,7 +188,7 @@ bool CFinalizedBudget::CheckStartEnd() } // Must be the correct block for payment to happen (once a month) - if (nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) { + if (!IsSuperBlock(nBlockStart)) { strInvalid = "Invalid BlockStart"; return false; } From b941d11aed07694699443063c0693df08ada1c5a Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 10:18:33 +0100 Subject: [PATCH 45/82] Refactor: decouple IsBlockPayeeValid/IsBlockValueValid from their legacy implementation --- src/budget/budgetmanager.cpp | 17 +++++++++++ src/budget/budgetmanager.h | 5 ++-- src/masternode-payments.cpp | 58 +++++++++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index aaac39c7c7725..6e84f12d627aa 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -5,6 +5,7 @@ #include "budget/budgetmanager.h" +#include "budget/budgetutil.h" #include "consensus/validation.h" #include "evo/deterministicmns.h" #include "masternodeman.h" @@ -510,6 +511,22 @@ bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet); } +CAmount CBudgetManager::GetFinalizedBudgetTotalPayout(int chainHeight) const +{ + if (!IsSuperBlock(chainHeight)) { + return 0; + } + int nFivePercent = mnodeman.CountEnabled() / 20; + + const auto highest = GetBudgetWithHighestVoteCount(chainHeight); + const CFinalizedBudget* pfb = highest.m_budget_fin; + if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + // No finalization or not enough votes. + return 0; + } + return pfb->GetTotalPayout(); +} + bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const { if (nHeight <= 0) return false; diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index 654ac0e8dfecd..e9da3bfcdb329 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -136,8 +136,9 @@ class CBudgetManager : public CValidationInterface std::vector GetAllProposalsOrdered(); std::vector GetFinalizedBudgets(); bool GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet) const; - bool IsBudgetPaymentBlock(int nBlockHeight) const; - bool IsBudgetPaymentBlock(int nBlockHeight, int& nCountThreshold) const; + CAmount GetFinalizedBudgetTotalPayout(int chainHeight) const; + bool IsBudgetPaymentBlock(int nBlockHeight) const; // legacy (multiple SB) + bool IsBudgetPaymentBlock(int nBlockHeight, int& nCountThreshold) const; // legacy (multiple SB) bool AddProposal(CBudgetProposal& budgetProposal); bool AddFinalizedBudget(CFinalizedBudget& finalizedBudget, CNode* pfrom = nullptr); void ForceAddFinalizedBudget(const uint256& nHash, const uint256& feeTxId, const CFinalizedBudget& finalizedBudget); diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index e99b22e4e4316..1b82857f3a7b1 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -9,6 +9,7 @@ #include "evo/deterministicmns.h" #include "fs.h" #include "budget/budgetmanager.h" +#include "budget/budgetutil.h" #include "masternodeman.h" #include "netmessagemaker.h" #include "tiertwo/netfulfilledman.h" @@ -194,7 +195,7 @@ void DumpMasternodePayments() LogPrint(BCLog::MASTERNODE,"Budget dump finished %dms\n", GetTimeMillis() - nStart); } -bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt) +static bool IsBlockValueValid_legacy(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt) { const Consensus::Params& consensus = Params().GetConsensus(); if (!g_tiertwo_sync_state.IsSynced()) { @@ -224,9 +225,30 @@ bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CA return nMinted <= nExpectedValue; } -bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) +bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt) +{ + if (!Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0)) { + return IsBlockValueValid_legacy(nHeight, nExpectedValue, nMinted, nBudgetAmt); + } + + if (IsSuperBlock(nHeight)) { + if (!g_tiertwo_sync_state.IsSynced()) { + // there is no budget data to use to check anything + nExpectedValue += g_budgetman.GetTotalBudget(nHeight); + } else if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { + // we're synced and the superblock spork is enabled + nBudgetAmt = g_budgetman.GetFinalizedBudgetTotalPayout(nHeight); + nExpectedValue += nBudgetAmt; + } + } + + return nMinted >= 0 && nMinted <= nExpectedValue; +} + +static bool IsBlockPayeeValid_legacy(const CBlock& block, const CBlockIndex* pindexPrev) { int nBlockHeight = pindexPrev->nHeight + 1; + assert(!Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_V6_0)); TrxValidationStatus transactionStatus = TrxValidationStatus::InValid; if (!g_tiertwo_sync_state.IsSynced()) { //there is no budget data to use to check anything -- find the longest chain @@ -234,8 +256,7 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) return true; } - const bool fPayCoinstake = Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_POS) && - !Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_V6_0); + const bool fPayCoinstake = Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_POS); const CTransaction& txNew = *(fPayCoinstake ? block.vtx[1] : block.vtx[0]); //check if it's a budget block @@ -272,6 +293,35 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) return true; } +bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) +{ + int nBlockHeight = pindexPrev->nHeight + 1; + if (!Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_V6_0)) { + return IsBlockPayeeValid_legacy(block, pindexPrev); + } + + if (!g_tiertwo_sync_state.IsSynced()) { //there is no budget data to use to check anything -- find the longest chain + // !TODO: after transition to v6, restrict this to budget-checks only + LogPrint(BCLog::MASTERNODE, "Client not synced, skipping block payee checks\n"); + return true; + } + + const CTransaction& coinbase_tx = *block.vtx[0]; + + // Check masternode payment + if (sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) && + !masternodePayments.IsTransactionValid(coinbase_tx, pindexPrev)) { + LogPrint(BCLog::MASTERNODE, "Missing required masternode payment\n"); + return false; + } + + // Check budget payments during superblocks + if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && IsSuperBlock(nBlockHeight)) { + // !TODO... + } + + return true; +} void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) { From 98a5424ca5d175c279a6bc3788b7a56cb5513884 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 11:45:47 +0100 Subject: [PATCH 46/82] Budget: Add function to check if ALL budgets are paid in a tx --- src/budget/budgetmanager.cpp | 15 +++++++++++++++ src/budget/budgetmanager.h | 4 +++- src/budget/finalizedbudget.cpp | 28 ++++++++++++++++++++++++++++ src/budget/finalizedbudget.h | 3 +++ src/masternode-payments.cpp | 2 +- 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 6e84f12d627aa..a0c953dfdebbf 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -759,6 +759,21 @@ TrxValidationStatus CBudgetManager::IsTransactionValid(const CTransaction& txNew return fThreshold ? TrxValidationStatus::InValid : TrxValidationStatus::VoteThreshold; } +bool CBudgetManager::IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const +{ + assert(IsSuperBlock(nBlockHeight)); + + int nFivePercent = mnodeman.CountEnabled() / 20; + + const auto highest = GetBudgetWithHighestVoteCount(nBlockHeight); + const CFinalizedBudget* pfb = highest.m_budget_fin; + if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + // No finalization or not enough votes. Nothing to check. + return true; + } + return pfb->AllBudgetsPaid(txNew); +} + std::vector CBudgetManager::GetAllProposalsOrdered() { LOCK(cs_proposals); diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index e9da3bfcdb329..e1c1ed9872f81 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -146,7 +146,9 @@ class CBudgetManager : public CValidationInterface bool UpdateProposal(const CBudgetVote& vote, CNode* pfrom, std::string& strError); bool UpdateFinalizedBudget(const CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError); - TrxValidationStatus IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const; + TrxValidationStatus IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const; // legacy (multiple SB) + bool IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const; // v6.0: single SB + std::string GetRequiredPaymentsString(int nBlockHeight); bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; diff --git a/src/budget/finalizedbudget.cpp b/src/budget/finalizedbudget.cpp index 3379c6fbd93d6..7943277f42481 100644 --- a/src/budget/finalizedbudget.cpp +++ b/src/budget/finalizedbudget.cpp @@ -7,6 +7,7 @@ #include "budget/budgetutil.h" #include "masternodeman.h" +#include "utilmoneystr.h" #include "validation.h" CFinalizedBudget::CFinalizedBudget() : @@ -390,6 +391,33 @@ bool CFinalizedBudget::GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, C return true; } +bool CFinalizedBudget::AllBudgetsPaid(const CTransaction& tx) const +{ + // make a map for faster lookup and deal with duplicate payees + auto cmp = [](CTxOut a, CTxOut b) { + return a.scriptPubKey < b.scriptPubKey || + (a.scriptPubKey == b.scriptPubKey && a.nValue < b.nValue); + }; + std::set outs(tx.vout.begin(), tx.vout.end(), cmp); + + for (const CTxBudgetPayment& payment : vecBudgetPayments) { + const auto it = outs.find(CTxOut(payment.nAmount, payment.payee)); + if (it == outs.end()) { + // Payment not found + CTxDestination addr; + const std::string& payee = ExtractDestination(payment.payee, addr) ? EncodeDestination(addr) + : HexStr(payment.payee); + LogPrint(BCLog::MNBUDGET, "Missing payment of %s for %s (proposal hash: %s)\n", + FormatMoney(payment.nAmount), payee, payment.nProposalHash.ToString()); + return false; + } + outs.erase(it); + } + + // all budgets are paid by tx + return true; +} + // return broadcast serialization CDataStream CFinalizedBudget::GetBroadcast() const { diff --git a/src/budget/finalizedbudget.h b/src/budget/finalizedbudget.h index d936dabb92af0..0aea8601580d7 100644 --- a/src/budget/finalizedbudget.h +++ b/src/budget/finalizedbudget.h @@ -93,6 +93,9 @@ class CFinalizedBudget bool GetBudgetPaymentByBlock(int64_t nBlockHeight, CTxBudgetPayment& payment) const; bool GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, CAmount& nAmount) const; + // Check if ALL the budgets are paid by transaction tx + bool AllBudgetsPaid(const CTransaction& tx) const; + // Check finalized budget proposals. Masternodes only (when voting on finalized budgets) bool CheckProposals(const std::map& mapWinningProposals) const; // Total amount paid out by this budget diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 1b82857f3a7b1..52e5a65d886d5 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -317,7 +317,7 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) // Check budget payments during superblocks if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && IsSuperBlock(nBlockHeight)) { - // !TODO... + return g_budgetman.IsValidSuperBlockTx(coinbase_tx, nBlockHeight); } return true; From 09d191e285470c6b50518f92dfb6154a77b5da8e Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 13:14:39 +0100 Subject: [PATCH 47/82] BlockAssembler: pay all budgets (and masternode) in single SuperBlock --- src/budget/budgetmanager.cpp | 16 ++++++++++++++++ src/budget/budgetmanager.h | 3 ++- src/budget/finalizedbudget.cpp | 7 +++++++ src/budget/finalizedbudget.h | 3 +++ src/masternode-payments.cpp | 19 ++++++++++++++++++- 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index a0c953dfdebbf..0be062c41f378 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -570,6 +570,22 @@ bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTra return true; } +void CBudgetManager::FillBlockPayees(CMutableTransaction& tx, int height) const +{ + if (!IsSuperBlock(height)) { + return; + } + int nFivePercent = mnodeman.CountEnabled() / 20; + + const auto highest = GetBudgetWithHighestVoteCount(height); + const CFinalizedBudget* pfb = highest.m_budget_fin; + if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + // No finalization or not enough votes. + return; + } + pfb->PayAllBudgets(tx); +} + void CBudgetManager::VoteOnFinalizedBudgets() { // function called only from initialized masternodes diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index e1c1ed9872f81..19fcf9d50a3cb 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -150,7 +150,8 @@ class CBudgetManager : public CValidationInterface bool IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const; // v6.0: single SB std::string GetRequiredPaymentsString(int nBlockHeight); - bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; + bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; // legacy (multiple SB) + void FillBlockPayees(CMutableTransaction& tx, int height) const; // v6.0: single SB // Only initialized masternodes: sign and submit votes on valid finalized budgets void VoteOnFinalizedBudgets(); diff --git a/src/budget/finalizedbudget.cpp b/src/budget/finalizedbudget.cpp index 7943277f42481..7a0f9c3257ed1 100644 --- a/src/budget/finalizedbudget.cpp +++ b/src/budget/finalizedbudget.cpp @@ -418,6 +418,13 @@ bool CFinalizedBudget::AllBudgetsPaid(const CTransaction& tx) const return true; } +void CFinalizedBudget::PayAllBudgets(CMutableTransaction& tx) const +{ + for (const CTxBudgetPayment& payment : vecBudgetPayments) { + tx.vout.emplace_back(payment.nAmount, payment.payee); + } +} + // return broadcast serialization CDataStream CFinalizedBudget::GetBroadcast() const { diff --git a/src/budget/finalizedbudget.h b/src/budget/finalizedbudget.h index 0aea8601580d7..79dcb5075fd11 100644 --- a/src/budget/finalizedbudget.h +++ b/src/budget/finalizedbudget.h @@ -96,6 +96,9 @@ class CFinalizedBudget // Check if ALL the budgets are paid by transaction tx bool AllBudgetsPaid(const CTransaction& tx) const; + // Add payments for ALL budgets to tx outs + void PayAllBudgets(CMutableTransaction& tx) const; + // Check finalized budget proposals. Masternodes only (when voting on finalized budgets) bool CheckProposals(const std::map& mapWinningProposals) const; // Total amount paid out by this budget diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 52e5a65d886d5..05d92e0f7828a 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -323,7 +323,7 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) return true; } -void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) +static void FillBlockPayee_legacy(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) { if (!sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) || // if superblocks are not enabled // ... or this is not a superblock @@ -333,6 +333,23 @@ void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoin } } +void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) +{ + int height = pindexPrev->nHeight + 1; + if (!Params().GetConsensus().NetworkUpgradeActive(height, Consensus::UPGRADE_V6_0)) { + // legacy - !TODO: remove after transition + return FillBlockPayee_legacy(txCoinbase, txCoinstake, pindexPrev, fProofOfStake); + } + + // Add masternode payment + masternodePayments.FillBlockPayee(txCoinbase, txCoinstake, pindexPrev, fProofOfStake); + + // Add budget payments (if superblock, and SPORK_13 is active) + if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { + g_budgetman.FillBlockPayees(txCoinbase, height); + } +} + std::string GetRequiredPaymentsString(int nBlockHeight) { if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && g_budgetman.IsBudgetPaymentBlock(nBlockHeight)) { From 61dda2b9833ab9c21fda448d258ab2f7b419d4f7 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 13:27:40 +0100 Subject: [PATCH 48/82] Refactor: de-duplicate code with CBudgetManager::GetBestFinalizedBudget --- src/budget/budgetmanager.cpp | 37 +++++++++++++++++------------------- src/budget/budgetmanager.h | 1 + 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 0be062c41f378..ce7dada445847 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -511,17 +511,25 @@ bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet); } -CAmount CBudgetManager::GetFinalizedBudgetTotalPayout(int chainHeight) const +const CFinalizedBudget* CBudgetManager::GetBestFinalizedBudget(int chainHeight) const { if (!IsSuperBlock(chainHeight)) { - return 0; + return nullptr; } int nFivePercent = mnodeman.CountEnabled() / 20; const auto highest = GetBudgetWithHighestVoteCount(chainHeight); - const CFinalizedBudget* pfb = highest.m_budget_fin; - if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + if (highest.m_budget_fin == nullptr || highest.m_vote_count <= nFivePercent) { // No finalization or not enough votes. + return nullptr; + } + return highest.m_budget_fin; +} + +CAmount CBudgetManager::GetFinalizedBudgetTotalPayout(int chainHeight) const +{ + const CFinalizedBudget* pfb = GetBestFinalizedBudget(chainHeight); + if (pfb == nullptr) { return 0; } return pfb->GetTotalPayout(); @@ -572,18 +580,10 @@ bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTra void CBudgetManager::FillBlockPayees(CMutableTransaction& tx, int height) const { - if (!IsSuperBlock(height)) { - return; + const CFinalizedBudget* pfb = GetBestFinalizedBudget(height); + if (pfb != nullptr) { + pfb->PayAllBudgets(tx); } - int nFivePercent = mnodeman.CountEnabled() / 20; - - const auto highest = GetBudgetWithHighestVoteCount(height); - const CFinalizedBudget* pfb = highest.m_budget_fin; - if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { - // No finalization or not enough votes. - return; - } - pfb->PayAllBudgets(tx); } void CBudgetManager::VoteOnFinalizedBudgets() @@ -779,11 +779,8 @@ bool CBudgetManager::IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHe { assert(IsSuperBlock(nBlockHeight)); - int nFivePercent = mnodeman.CountEnabled() / 20; - - const auto highest = GetBudgetWithHighestVoteCount(nBlockHeight); - const CFinalizedBudget* pfb = highest.m_budget_fin; - if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + const CFinalizedBudget* pfb = GetBestFinalizedBudget(nBlockHeight); + if (pfb == nullptr) { // No finalization or not enough votes. Nothing to check. return true; } diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index 19fcf9d50a3cb..735e47e858827 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -150,6 +150,7 @@ class CBudgetManager : public CValidationInterface bool IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const; // v6.0: single SB std::string GetRequiredPaymentsString(int nBlockHeight); + const CFinalizedBudget* GetBestFinalizedBudget(int chainHeight) const; bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; // legacy (multiple SB) void FillBlockPayees(CMutableTransaction& tx, int height) const; // v6.0: single SB From 4a0e754de066c9463a4c917445b2041fc12463d7 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 18:11:23 +0100 Subject: [PATCH 49/82] Validation: consider mn payment for superblocks in IsCoinbaseValueValid --- src/masternode-payments.cpp | 37 ++++++++++++++----------------------- src/test/budget_tests.cpp | 32 ++++++++++++++++++++++++-------- src/validation.cpp | 2 +- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 05d92e0f7828a..7abdde876bb73 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -892,30 +892,21 @@ bool IsCoinbaseValueValid(const CTransactionRef& tx, CAmount nBudgetAmt, CValida { assert(tx->IsCoinBase()); if (g_tiertwo_sync_state.IsSynced()) { - const CAmount nCBaseOutAmt = tx->GetValueOut(); - if (nBudgetAmt > 0) { - // Superblock - if (nCBaseOutAmt != nBudgetAmt) { - const std::string strError = strprintf("%s: invalid coinbase payment for budget (%s vs expected=%s)", - __func__, FormatMoney(nCBaseOutAmt), FormatMoney(nBudgetAmt)); - return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-superblock-cb-amt"); - } - return true; - } else { - // regular block - CAmount nMnAmt = GetMasternodePayment(); - // if enforcement is disabled, there could be no masternode payment - bool sporkEnforced = sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT); - const std::string strError = strprintf("%s: invalid coinbase payment for masternode (%s vs expected=%s)", - __func__, FormatMoney(nCBaseOutAmt), FormatMoney(nMnAmt)); - if (sporkEnforced && nCBaseOutAmt != nMnAmt) { - return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-cb-amt"); - } - if (!sporkEnforced && nCBaseOutAmt > nMnAmt) { - return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-cb-amt-spork8-disabled"); - } - return true; + const CAmount paid = tx->GetValueOut(); + const CAmount expected = GetMasternodePayment() + nBudgetAmt; + // if enforcement is disabled, there could be no masternode payment + bool sporkEnforced = sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT); + + const std::string strError = strprintf("%s: invalid coinbase payment (%s vs expected=%s)", + __func__, FormatMoney(paid), FormatMoney(expected)); + std::string rej_reason = (nBudgetAmt > 0 ? "bad-superblock-cb-amt" : "bad-cb-amt"); + if (sporkEnforced && paid != expected) { + return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, rej_reason); } + if (!sporkEnforced && paid > expected) { + return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, rej_reason+"-spork8-disabled"); + } + return true; } return true; } diff --git a/src/test/budget_tests.cpp b/src/test/budget_tests.cpp index 2ec3485d5778c..7e93d436d984b 100644 --- a/src/test/budget_tests.cpp +++ b/src/test/budget_tests.cpp @@ -417,36 +417,52 @@ BOOST_FIXTURE_TEST_CASE(IsCoinbaseValueValid_test, TestingSetup) // Exact cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt, cbaseScript); cbase.vout.emplace_back(budgAmt, cbaseScript); BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); - cbase.vout[0].nValue /= 2; - cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue /= 2; + cbase.vout.emplace_back(cbase.vout[1]); BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); // Underpaying cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt, cbaseScript); cbase.vout.emplace_back(budgAmt - 1, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); state = CValidationState(); - cbase.vout[0].nValue = budgAmt/2; - cbase.vout.emplace_back(cbase.vout[0]); - cbase.vout[1].nValue = budgAmt/2 - 1; + cbase.vout[1].nValue = budgAmt/2; + cbase.vout.emplace_back(cbase.vout[1]); + cbase.vout[2].nValue = budgAmt/2 - 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt - 1, cbaseScript); + cbase.vout.emplace_back(budgAmt, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); state = CValidationState(); // Overpaying cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt, cbaseScript); cbase.vout.emplace_back(budgAmt + 1, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); state = CValidationState(); - cbase.vout[0].nValue = budgAmt/2; - cbase.vout.emplace_back(cbase.vout[0]); - cbase.vout[1].nValue = budgAmt/2 + 1; + cbase.vout[1].nValue = budgAmt/2; + cbase.vout.emplace_back(cbase.vout[1]); + cbase.vout[2].nValue = budgAmt/2 + 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt + 1, cbaseScript); + cbase.vout.emplace_back(budgAmt, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); } BOOST_AUTO_TEST_CASE(fbv_signverify_bls) diff --git a/src/validation.cpp b/src/validation.cpp index b757e1d894052..b69e1ffd60a42 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1614,7 +1614,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd nExpectedMint += nFees; //Check that the block does not overmint - CAmount nBudgetAmt = 0; // If this is a superblock, amount to be paid to the winning proposal, otherwise 0 + CAmount nBudgetAmt = 0; // If this is a superblock, amount to be paid to the winning proposals, otherwise 0 if (!IsBlockValueValid(pindex->nHeight, nExpectedMint, nMint, nBudgetAmt)) { return state.DoS(100, error("%s: reward pays too much (actual=%s vs limit=%s)", __func__, FormatMoney(nMint), FormatMoney(nExpectedMint)), From 026606f0092737fa8ade34425f25ef8cab2a58ea Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 3 Mar 2022 22:16:29 +0100 Subject: [PATCH 50/82] [SQUASH ME] Fixed std::map in CFinalizedBudget::AllBudgetsPaid --- src/budget/finalizedbudget.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/budget/finalizedbudget.cpp b/src/budget/finalizedbudget.cpp index 7a0f9c3257ed1..1e9913a6f9744 100644 --- a/src/budget/finalizedbudget.cpp +++ b/src/budget/finalizedbudget.cpp @@ -394,15 +394,20 @@ bool CFinalizedBudget::GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, C bool CFinalizedBudget::AllBudgetsPaid(const CTransaction& tx) const { // make a map for faster lookup and deal with duplicate payees - auto cmp = [](CTxOut a, CTxOut b) { - return a.scriptPubKey < b.scriptPubKey || - (a.scriptPubKey == b.scriptPubKey && a.nValue < b.nValue); + struct cmp { + bool operator()(const CTxOut& a, const CTxOut& b) const { + return a.scriptPubKey < b.scriptPubKey || + (a.scriptPubKey == b.scriptPubKey && a.nValue < b.nValue); + } }; - std::set outs(tx.vout.begin(), tx.vout.end(), cmp); + std::map txouts; + for (const CTxOut& o: tx.vout) { + txouts[o]++; + } for (const CTxBudgetPayment& payment : vecBudgetPayments) { - const auto it = outs.find(CTxOut(payment.nAmount, payment.payee)); - if (it == outs.end()) { + const auto it = txouts.find(CTxOut(payment.nAmount, payment.payee)); + if (it == txouts.end() || it->second == 0) { // Payment not found CTxDestination addr; const std::string& payee = ExtractDestination(payment.payee, addr) ? EncodeDestination(addr) @@ -411,7 +416,7 @@ bool CFinalizedBudget::AllBudgetsPaid(const CTransaction& tx) const FormatMoney(payment.nAmount), payee, payment.nProposalHash.ToString()); return false; } - outs.erase(it); + it->second--; } // all budgets are paid by tx From 0245001eb410a5d382e4947751a00b4fe7876ca0 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 3 Mar 2022 22:44:10 +0100 Subject: [PATCH 51/82] [FIX] Don't use miner to receive budget proposals. Use an owner. --- test/functional/tiertwo_budget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py index 28e196e5e5df4..5526aabb5df5a 100755 --- a/test/functional/tiertwo_budget.py +++ b/test/functional/tiertwo_budget.py @@ -86,7 +86,7 @@ def vote_finalization(self, voting_node, budget_fin_hash, legacy): assert_equal(voteResult["detail"][0]["result"], "success") def check_address_balance(self, addr, expected_balance, has_balance=True): - addrInfo = self.miner.listreceivedbyaddress(0, False, False, addr) + addrInfo = self.nodes[self.ownerOnePos].listreceivedbyaddress(0, False, False, addr) if has_balance: assert_equal(addrInfo[0]["amount"], expected_balance) else: @@ -146,7 +146,7 @@ def run_test(self): props.append(Proposal("prop_"+str(i), "https://link_"+str(i)+".com", 3, - self.miner.getnewaddress(), + self.nodes[self.ownerOnePos].getnewaddress(), 11 * (i + 1))) self.submit_proposals(props) From aceb016ed05b3b2fb3e54d2e6492687a15bc56ce Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 3 Mar 2022 22:56:01 +0100 Subject: [PATCH 52/82] [FIX] last proposals check has not the first 2 proposals (they expired) --- test/functional/tiertwo_budget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py index 5526aabb5df5a..c6393ed57aa50 100755 --- a/test/functional/tiertwo_budget.py +++ b/test/functional/tiertwo_budget.py @@ -145,7 +145,7 @@ def run_test(self): for i in range(16): props.append(Proposal("prop_"+str(i), "https://link_"+str(i)+".com", - 3, + 4, self.nodes[self.ownerOnePos].getnewaddress(), 11 * (i + 1))) self.submit_proposals(props) From c3121240b3a00ac15f517b55f41949b1f6b49ccd Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 3 Mar 2022 17:15:29 +0100 Subject: [PATCH 53/82] [DON'T PICK] Modified pch message params --- src/chainparams.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 3529b4a3c19e1..bc1396f0113c0 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -452,10 +452,10 @@ class CTestNetParams : public CChainParams * The characters are rarely used upper ASCII, not valid as UTF-8, and produce * a large 4-byte int at any alignment. */ - pchMessageStart[0] = 0xf5; - pchMessageStart[1] = 0xe6; - pchMessageStart[2] = 0xd5; - pchMessageStart[3] = 0xca; + pchMessageStart[0] = 0xe5; + pchMessageStart[1] = 0xd1; + pchMessageStart[2] = 0xd4; + pchMessageStart[3] = 0xea; nDefaultPort = 51474; // nodes with support for servicebits filtering should be at the top From ddd68cc12929fe9c7cd2a4b7e0fea1428336d048 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 3 Mar 2022 17:19:41 +0100 Subject: [PATCH 54/82] [DON'T PICK] Temp activation height v6 at block 624600 --- src/chainparams.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index bc1396f0113c0..c280a0a7756da 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -444,8 +444,7 @@ class CTestNetParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_0].nActivationHeight = 201; consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 262525; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 332300; - consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = - Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = 624600; /** * The message start string is designed to be unlikely to occur in normal data. From 0ed59b2e991f429d7b0733ae6369b98abd94f649 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 7 Mar 2022 11:48:32 -0300 Subject: [PATCH 55/82] init: inform load tier two init error. --- src/init.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index 73dcb170b1020..dbd2f4c8d5ab8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1724,7 +1724,9 @@ bool AppInitMain() } } - LoadTierTwo(chain_active_height, load_cache_files); + if (!LoadTierTwo(chain_active_height, load_cache_files)) { + return false; // error informed inside the function + } RegisterTierTwoValidationInterface(); // set the mode of budget voting for this node From ab9a4b634bd54b5d98df2109bfd52af1a2e8e32a Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 7 Mar 2022 11:53:09 -0300 Subject: [PATCH 56/82] init: don't error out if MN metadata db wasn't loaded properly. the node will re-create its state all over. --- src/tiertwo/init.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tiertwo/init.cpp b/src/tiertwo/init.cpp index 3c8333d4b0b57..4fefae9f42a29 100644 --- a/src/tiertwo/init.cpp +++ b/src/tiertwo/init.cpp @@ -172,12 +172,12 @@ bool LoadTierTwo(int chain_active_height, bool load_cache_files) CFlatDB metadb(MN_META_CACHE_FILENAME, MN_META_CACHE_FILE_ID); if (load_cache_files) { if (!metadb.Load(g_mmetaman)) { - return UIError(strprintf(_("Failed to load masternode metadata cache from: %s"), metadb.GetDbPath().string())); + LogPrintf("Failed to load masternode metadata cache from: %s", metadb.GetDbPath().string()); } } else { CMasternodeMetaMan mmetamanTmp; if (!metadb.Dump(mmetamanTmp)) { - return UIError(strprintf(_("Failed to clear masternode metadata cache at: %s"), metadb.GetDbPath().string())); + LogPrintf("Failed to clear masternode metadata cache at: %s", metadb.GetDbPath().string()); } } From fec5ed3aa20e7138b0923ac529b53d02766edcf8 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Mar 2022 10:39:33 -0300 Subject: [PATCH 57/82] fixup with export MN --- src/qt/pivx/masternodeswidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 36128456dff2a..30364560c6526 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -339,7 +339,7 @@ void MasterNodesWidget::onInfoMNClicked() "externalip=" + address.left(address.lastIndexOf(":")) + "\n" "listen=1\n"; if (isLegacy) { - exportedMN = "masternodeaddr=" + address + +"\n" + + exportedMN += "masternodeaddr=" + address + +"\n" + "masternodeprivkey=" + index.sibling(index.row(), MNModel::PRIV_KEY).data(Qt::DisplayRole).toString() + "\n"; } else { From 47354db68991237e12834b7c0864848b465644dc Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Mar 2022 11:19:23 -0300 Subject: [PATCH 58/82] tiertwo: do not request list to already requested nodes. --- src/masternode-payments.cpp | 8 +++----- src/masternodeman.cpp | 16 +++++++--------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 7abdde876bb73..68c8f88475bad 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -478,11 +478,9 @@ bool CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st int nCountNeeded; vRecv >> nCountNeeded; - if (Params().NetworkIDString() == CBaseChainParams::MAIN) { - if (g_netfulfilledman.HasFulfilledRequest(pfrom->addr, NetMsgType::GETMNWINNERS)) { - LogPrint(BCLog::MASTERNODE, "%s: mnget - peer already asked me for the list\n", __func__); - return state.DoS(20, false, REJECT_INVALID, "getmnwinners-request-already-fulfilled"); - } + if (g_netfulfilledman.HasFulfilledRequest(pfrom->addr, NetMsgType::GETMNWINNERS)) { + LogPrint(BCLog::MASTERNODE, "%s: mnget - peer already asked me for the list\n", __func__); + return state.DoS(20, false, REJECT_INVALID, "getmnwinners-request-already-fulfilled"); } g_netfulfilledman.AddFulfilledRequest(pfrom->addr, NetMsgType::GETMNWINNERS); diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index 605ffea6fa017..58766dcae3ca1 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -455,14 +455,12 @@ bool CMasternodeMan::RequestMnList(CNode* pnode) } LOCK(cs); - if (Params().NetworkIDString() == CBaseChainParams::MAIN) { - if (!(pnode->addr.IsRFC1918() || pnode->addr.IsLocal())) { - std::map::iterator it = mWeAskedForMasternodeList.find(pnode->addr); - if (it != mWeAskedForMasternodeList.end()) { - if (GetTime() < (*it).second) { - LogPrint(BCLog::MASTERNODE, "dseg - we already asked peer %i for the list; skipping...\n", pnode->GetId()); - return false; - } + if (!(pnode->addr.IsRFC1918() || pnode->addr.IsLocal())) { + std::map::iterator it = mWeAskedForMasternodeList.find(pnode->addr); + if (it != mWeAskedForMasternodeList.end()) { + if (GetTime() < (*it).second) { + LogPrint(BCLog::MASTERNODE, "dseg - we already asked peer %i for the list; skipping...\n", pnode->GetId()); + return false; } } } @@ -915,7 +913,7 @@ int CMasternodeMan::ProcessGetMNList(CNode* pfrom, CTxIn& vin) if (itAskedUsMNList != mAskedUsForMasternodeList.end()) { int64_t t = (*itAskedUsMNList).second; if (GetTime() < t) { - LogPrintf("CMasternodeMan::ProcessMessage() : dseg - peer already asked me for the list\n"); + LogPrintf("%s : dseg - peer already asked me for the list, peer %s\n", __func__, pfrom->addr.ToString()); return 20; } } From 1119aa7ada1d271d7814577ab723ceb4df909ee7 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 9 Mar 2022 10:30:22 -0300 Subject: [PATCH 59/82] tiertwo: do not reset attempt and sync start time if the current sync phase hasn't changed. --- src/masternode-sync.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index aa4ccddaa95af..dbd93d86f230b 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -107,18 +107,20 @@ int CMasternodeSync::GetNextAsset(int currentAsset) void CMasternodeSync::SwitchToNextAsset() { - int RequestedMasternodeAssets = g_tiertwo_sync_state.GetSyncPhase(); - if (RequestedMasternodeAssets == MASTERNODE_SYNC_INITIAL || - RequestedMasternodeAssets == MASTERNODE_SYNC_FAILED) { + int current_sync_phase = g_tiertwo_sync_state.GetSyncPhase(); + if (current_sync_phase == MASTERNODE_SYNC_INITIAL || + current_sync_phase == MASTERNODE_SYNC_FAILED) { ClearFulfilledRequest(); } - const int nextAsset = GetNextAsset(RequestedMasternodeAssets); - if (nextAsset == MASTERNODE_SYNC_FINISHED) { - LogPrintf("%s - Sync has finished\n", __func__); - } - g_tiertwo_sync_state.SetCurrentSyncPhase(nextAsset); + const int next_sync_phase = GetNextAsset(current_sync_phase); + if (current_sync_phase == next_sync_phase) return; // nothing to do + g_tiertwo_sync_state.SetCurrentSyncPhase(next_sync_phase); RequestedMasternodeAttempt = 0; nAssetSyncStarted = GetTime(); + + if (next_sync_phase == MASTERNODE_SYNC_FINISHED) { + LogPrintf("%s - Sync has finished\n", __func__); + } } std::string CMasternodeSync::GetSyncStatus() From ad9dac297e5de7972e365b5860a47c85b19d06c6 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 9 Mar 2022 10:35:51 -0300 Subject: [PATCH 60/82] tiertwo: log synchronization phases. --- src/masternode-sync.cpp | 43 ++++++++++++++++++++++------------------- src/masternode-sync.h | 3 +++ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index dbd93d86f230b..7fc1485538c8b 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -117,31 +117,34 @@ void CMasternodeSync::SwitchToNextAsset() g_tiertwo_sync_state.SetCurrentSyncPhase(next_sync_phase); RequestedMasternodeAttempt = 0; nAssetSyncStarted = GetTime(); - - if (next_sync_phase == MASTERNODE_SYNC_FINISHED) { - LogPrintf("%s - Sync has finished\n", __func__); - } + LogPrintf("%s - %s\n", __func__, GetSyncStatus(next_sync_phase)); } std::string CMasternodeSync::GetSyncStatus() { - switch (g_tiertwo_sync_state.GetSyncPhase()) { - case MASTERNODE_SYNC_INITIAL: - return _("MNs synchronization pending..."); - case MASTERNODE_SYNC_SPORKS: - return _("Synchronizing sporks..."); - case MASTERNODE_SYNC_LIST: - return _("Synchronizing masternodes..."); - case MASTERNODE_SYNC_MNW: - return _("Synchronizing masternode winners..."); - case MASTERNODE_SYNC_BUDGET: - return _("Synchronizing budgets..."); - case MASTERNODE_SYNC_FAILED: - return _("Synchronization failed"); - case MASTERNODE_SYNC_FINISHED: - return _("Synchronization finished"); + return GetSyncStatus(g_tiertwo_sync_state.GetSyncPhase()); +} + +std::string CMasternodeSync::GetSyncStatus(int phase) +{ + switch (phase) { + case MASTERNODE_SYNC_INITIAL: + return _("MNs synchronization pending..."); + case MASTERNODE_SYNC_SPORKS: + return _("Synchronizing sporks..."); + case MASTERNODE_SYNC_LIST: + return _("Synchronizing masternodes..."); + case MASTERNODE_SYNC_MNW: + return _("Synchronizing masternode winners..."); + case MASTERNODE_SYNC_BUDGET: + return _("Synchronizing budgets..."); + case MASTERNODE_SYNC_FAILED: + return _("Synchronization failed"); + case MASTERNODE_SYNC_FINISHED: + return _("Synchronization finished"); + default: + return _("Unknown sync phase"); } - return ""; } void CMasternodeSync::ProcessSyncStatusMsg(int nItemID, int nCount) diff --git a/src/masternode-sync.h b/src/masternode-sync.h index 4280b568ff266..1ef13a34595d5 100644 --- a/src/masternode-sync.h +++ b/src/masternode-sync.h @@ -98,6 +98,9 @@ class CMasternodeSync // Mark sync timeout void syncTimeout(const std::string& reason); + + // Return human-readable string for 'phase' + std::string GetSyncStatus(int phase); }; #endif From 09307c3f97fc1e7628c969a375f0ab926f8bea6f Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 9 Mar 2022 10:44:26 -0300 Subject: [PATCH 61/82] tiertwo: clear only sync requests --- src/masternode-sync.cpp | 2 +- src/tiertwo/netfulfilledman.cpp | 14 ++++++++++++++ src/tiertwo/netfulfilledman.h | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index 7fc1485538c8b..40e5f79d0c9e9 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -183,7 +183,7 @@ void CMasternodeSync::ProcessSyncStatusMsg(int nItemID, int nCount) void CMasternodeSync::ClearFulfilledRequest() { - g_netfulfilledman.Clear(); + g_netfulfilledman.ClearRequestsGlobal({"getspork", "mnsync", "mnwsync", "busync"}); } void CMasternodeSync::Process() diff --git a/src/tiertwo/netfulfilledman.cpp b/src/tiertwo/netfulfilledman.cpp index 4615155d7b353..9dcd4572eae1b 100644 --- a/src/tiertwo/netfulfilledman.cpp +++ b/src/tiertwo/netfulfilledman.cpp @@ -38,6 +38,20 @@ bool CNetFulfilledRequestManager::HasFulfilledRequest(const CService& addr, cons return false; } +void CNetFulfilledRequestManager::ClearRequestsGlobal(const std::vector& strRequests) +{ + LOCK(cs_mapFulfilledRequests); + auto it = mapFulfilledRequests.begin(); + while (it != mapFulfilledRequests.end()) { + if (!it->second.empty()) { + for (const auto& request: strRequests) { + it->second.erase(request); + } + } + it++; + } +} + static std::vector convertElement(const CService& addr, const uint256& itemHash) { CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/tiertwo/netfulfilledman.h b/src/tiertwo/netfulfilledman.h index cb22cf4d573fd..aa6188477cfcc 100644 --- a/src/tiertwo/netfulfilledman.h +++ b/src/tiertwo/netfulfilledman.h @@ -49,6 +49,7 @@ class CNetFulfilledRequestManager void AddFulfilledRequest(const CService& addr, const std::string& strRequest); bool HasFulfilledRequest(const CService& addr, const std::string& strRequest) const; + void ClearRequestsGlobal(const std::vector& strRequests); // Faster lookup using bloom filter void AddItemRequest(const CService& addr, const uint256& itemHash); From c16095eacd9acfa98f00f7d1920b92c2617b9573 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 9 Mar 2022 12:21:41 -0300 Subject: [PATCH 62/82] tiertwo: do not trigger a sync reset if we just finished syncing. co-authored with random.zebra --- src/masternode-sync.cpp | 11 +++++++++++ src/masternode-sync.h | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index 40e5f79d0c9e9..d82cb06f52119 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -118,6 +118,11 @@ void CMasternodeSync::SwitchToNextAsset() RequestedMasternodeAttempt = 0; nAssetSyncStarted = GetTime(); LogPrintf("%s - %s\n", __func__, GetSyncStatus(next_sync_phase)); + + // Update the last sync finished time + if (next_sync_phase == MASTERNODE_SYNC_FINISHED) { + lastSyncFinishedTime = GetTime(); + } } std::string CMasternodeSync::GetSyncStatus() @@ -208,6 +213,12 @@ void CMasternodeSync::Process() if (isRegTestNet) { return; } + + // Only try to reset the sync process if the node finished syncing an hour ago + if (lastSyncFinishedTime + 60 * 60 > now) { + return; + } + bool legacy_obsolete = deterministicMNManager->LegacyMNObsolete(); // Check if we lost all masternodes (except the local one in case the node is a MN) // from sleep/wake or failure to sync originally (after spork 21, check if we lost diff --git a/src/masternode-sync.h b/src/masternode-sync.h index 1ef13a34595d5..edb7b467d6e4a 100644 --- a/src/masternode-sync.h +++ b/src/masternode-sync.h @@ -30,11 +30,14 @@ struct TierTwoPeerData { class CMasternodeSync { public: - int64_t lastFailure; + std::atomic lastFailure; int nCountFailures; std::atomic lastProcess; + // Last time when the node finished syncing + std::atomic lastSyncFinishedTime; + // sum of all counts int sumMasternodeList; int sumMasternodeWinner; From d101262d47f2feca50fa55842ce18104be39ecf6 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 9 Mar 2022 13:50:59 -0300 Subject: [PATCH 63/82] validationinterface: trigger NotifyMasternodeListChanged asynchronously. --- src/validationinterface.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 57ed996ba9930..7d67e48571b18 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -218,11 +218,14 @@ void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& sta } void CMainSignals::NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) { - m_internals->NotifyMasternodeListChanged(undo, oldMNList, diff); - LOG_EVENT("%s: (undo=%d) old list for=%s, added=%d, updated=%d, removed=%d", __func__, - undo, - oldMNList.GetBlockHash().ToString(), - diff.addedMNs.size(), - diff.updatedMNs.size(), - diff.removedMns.size()); + auto event = [undo, oldMNList, diff, this] { + m_internals->NotifyMasternodeListChanged(undo, oldMNList, diff); + }; + ENQUEUE_AND_LOG_EVENT(event, + "%s: (undo=%d) old list for=%s, added=%d, updated=%d, removed=%d", __func__, + undo, + oldMNList.GetBlockHash().ToString(), + diff.addedMNs.size(), + diff.updatedMNs.size(), + diff.removedMns.size()); } From d1d27968b6ff8282078426fdc1d607e2a249d6aa Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 6 Jan 2023 18:49:31 +0100 Subject: [PATCH 64/82] Fixed two bugs, one of which halted the chain --- src/evo/specialtx_validation.cpp | 7 ++----- src/qt/pivx/masternodewizarddialog.cpp | 21 +++++++++------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/evo/specialtx_validation.cpp b/src/evo/specialtx_validation.cpp index 76730feaf87c4..b184a83f123fd 100644 --- a/src/evo/specialtx_validation.cpp +++ b/src/evo/specialtx_validation.cpp @@ -182,12 +182,9 @@ static bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, // This is checked only when pindexPrev is not null (thus during ConnectBlock-->CheckSpecialTx), // because this is a contextual check: we need the updated utxo set, to verify that // the coin exists and it is unspent. - Coin coin; - if (!view->GetUTXOCoin(pl.collateralOutpoint, coin)) { - return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); - } + CTxOut out = tx.vout[pl.collateralOutpoint.n]; CTxDestination collateralTxDest; - if (!CheckCollateralOut(coin.out, pl, state, collateralTxDest)) { + if (!CheckCollateralOut(out, pl, state, collateralTxDest)) { // pass the state returned by the function above return false; } diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 8d8488caa986d..e830632e4f6fd 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -411,18 +411,15 @@ bool MasterNodeWizardDialog::createMN() // Look for a valid collateral utxo COutPoint collateralOut; - // If not found create a new collateral tx - if (!walletModel->getMNCollateralCandidate(collateralOut)) { - // New receive address - auto r = walletModel->getNewAddress(alias); - if (!r) return errorOut(tr(r.getError().c_str())); - if (!mnModel->createMNCollateral(addressLabel, - QString::fromStdString(r.getObjResult()->ToString()), - collateralOut, - returnStr)) { - // error str set internally - return false; - } + // New receive address + auto r = walletModel->getNewAddress(alias); + if (!r) return errorOut(tr(r.getError().c_str())); + if (!mnModel->createMNCollateral(addressLabel, + QString::fromStdString(r.getObjResult()->ToString()), + collateralOut, + returnStr)) { + // error str set internally + return false; } if (isDeterministic) { From 1aed7411ae9339429e789e11579df0c610dfd0c1 Mon Sep 17 00:00:00 2001 From: Liquid Date: Sat, 7 Jan 2023 05:54:42 -0600 Subject: [PATCH 65/82] Fix GetMasternodePayment bad merge --- src/masternode-payments.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 6986ea2b08ea6..96d7815c1bfae 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -891,7 +891,8 @@ bool IsCoinbaseValueValid(const CTransactionRef& tx, CAmount nBudgetAmt, CValida assert(tx->IsCoinBase()); if (g_tiertwo_sync_state.IsSynced()) { const CAmount paid = tx->GetValueOut(); - const CAmount expected = GetMasternodePayment() + nBudgetAmt; + int nHeight = mnodeman.GetBestHeight(); + const CAmount expected = GetMasternodePayment(nHeight) + nBudgetAmt; // if enforcement is disabled, there could be no masternode payment bool sporkEnforced = sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT); From 0b735c02e5eba0ce561df1c068fc3e717485d29e Mon Sep 17 00:00:00 2001 From: Liquid Date: Sat, 7 Jan 2023 08:43:43 -0600 Subject: [PATCH 66/82] Fix lint + dmn activation height --- src/chainparams.cpp | 3 +-- test/functional/test_framework/budget_util.py | 2 +- test/lint/lint-circular-dependencies.sh | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 0b7c56c9a4225..b1865cb08c550 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -596,8 +596,7 @@ class CRegTestParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 300; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 251; consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 576; - consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = - Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = 601; /** * The message start string is designed to be unlikely to occur in normal data. diff --git a/test/functional/test_framework/budget_util.py b/test/functional/test_framework/budget_util.py index edc5f8a5caf97..52e95cb183cf9 100644 --- a/test/functional/test_framework/budget_util.py +++ b/test/functional/test_framework/budget_util.py @@ -128,4 +128,4 @@ def propagate_proposals(miner, props): entry.amountPerCycle, entry.feeTxId) entry.proposalHash = proposalHash - return props \ No newline at end of file + return props diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 5dcf89d12f298..b326b145331db 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -11,6 +11,7 @@ export LC_ALL=C EXPECTED_CIRCULAR_DEPENDENCIES=( "activemasternode -> masternodeman -> activemasternode" "budget/budgetmanager -> validation -> budget/budgetmanager" + "budget/budgetmanager -> budget/budgetutil -> budget/budgetmanager" "chain -> legacy/stakemodifier -> chain" "chainparamsbase -> util/system -> chainparamsbase" "consensus/params -> consensus/upgrades -> consensus/params" From 4ef2ff08be7e8d6bd7bdf5fa5e95083a8eacb414 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Sun, 8 Jan 2023 12:13:30 +0100 Subject: [PATCH 67/82] Fixed collateral spent on proregtx creation bug --- src/evo/specialtx_validation.cpp | 16 ++++++++++++++-- src/qt/pivx/masternodewizarddialog.cpp | 7 ++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/evo/specialtx_validation.cpp b/src/evo/specialtx_validation.cpp index b184a83f123fd..9c1d7ce74b46a 100644 --- a/src/evo/specialtx_validation.cpp +++ b/src/evo/specialtx_validation.cpp @@ -182,9 +182,21 @@ static bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, // This is checked only when pindexPrev is not null (thus during ConnectBlock-->CheckSpecialTx), // because this is a contextual check: we need the updated utxo set, to verify that // the coin exists and it is unspent. - CTxOut out = tx.vout[pl.collateralOutpoint.n]; + + Coin coin; + if (!view->GetUTXOCoin(pl.collateralOutpoint, coin)) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); + } + + //Don't use the collateral as tx input of the proreg + for(auto txIn= tx.vin.begin();txInprevout == pl.collateralOutpoint){ + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-used-as-input"); + } + } + CTxDestination collateralTxDest; - if (!CheckCollateralOut(out, pl, state, collateralTxDest)) { + if (!CheckCollateralOut(coin.out, pl, state, collateralTxDest)) { // pass the state returned by the function above return false; } diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index e830632e4f6fd..074af1632bf5f 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -411,13 +411,14 @@ bool MasterNodeWizardDialog::createMN() // Look for a valid collateral utxo COutPoint collateralOut; + if (!walletModel->getMNCollateralCandidate(collateralOut)) { // New receive address auto r = walletModel->getNewAddress(alias); if (!r) return errorOut(tr(r.getError().c_str())); if (!mnModel->createMNCollateral(addressLabel, - QString::fromStdString(r.getObjResult()->ToString()), - collateralOut, - returnStr)) { + QString::fromStdString(r.getObjResult()->ToString()), + collateralOut, + returnStr)) { // error str set internally return false; } From 1f788dc83b1534b6e77dec6a68afd3de4483a510 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Sun, 8 Jan 2023 12:15:55 +0100 Subject: [PATCH 68/82] typo --- src/qt/pivx/masternodewizarddialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 074af1632bf5f..4d13daba8f250 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -422,6 +422,7 @@ bool MasterNodeWizardDialog::createMN() // error str set internally return false; } + } if (isDeterministic) { // 1) Get or create the owner addr From daa8680da4bc57e7498dd2cd4b880c9f2bb369ad Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Sun, 8 Jan 2023 13:31:14 +0100 Subject: [PATCH 69/82] Fix collateral used as input in ProRegTx --- src/qt/pivx/masternodewizarddialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 4d13daba8f250..a12a2da981795 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -464,6 +464,7 @@ bool MasterNodeWizardDialog::createMN() // For now, collateral key is always inside the wallet std::string error_str; + walletModel->lockCoin(collateralOut); auto res = mnModel->createDMN(alias, collateralOut, ipAddress, @@ -476,6 +477,7 @@ bool MasterNodeWizardDialog::createMN() (uint16_t) operatorPercentage * 100, // operator percentage operatorPayoutKeyId); // operator payout script if (!res) { + walletModel->unlockCoin(collateralOut); return errorOut(tr(error_str.c_str())); } From 391ef937653731ef559945a9650a70cb57d42813 Mon Sep 17 00:00:00 2001 From: Liquid Date: Sun, 8 Jan 2023 10:45:41 -0600 Subject: [PATCH 70/82] Slow down regtest --- src/validation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index a2ff75a42b899..d50682192abf5 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2844,8 +2844,8 @@ bool CheckWork(const CBlock& block, const CBlockIndex* const pindexPrev) bool CheckBlockTime(const CBlockHeader& block, CValidationState& state, CBlockIndex* const pindexPrev) { // Not enforced on RegTest - if (Params().IsRegTestNet()) - return true; + //if (Params().IsRegTestNet()) + // return true; const int64_t blockTime = block.GetBlockTime(); const int blockHeight = pindexPrev->nHeight + 1; From 224a5fca3b3b1f82f3a3ca1a9ac6e5a0de0d48b4 Mon Sep 17 00:00:00 2001 From: Liquid369 Date: Fri, 3 Feb 2023 14:43:25 +0000 Subject: [PATCH 71/82] Regtest params adjust --- src/chainparams.cpp | 12 ++++++------ src/pow.cpp | 8 ++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b1865cb08c550..ae18134469ac2 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -582,21 +582,21 @@ class CRegTestParams : public CChainParams Consensus::NetworkUpgrade::ALWAYS_ACTIVE; consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; - consensus.vUpgrades[Consensus::UPGRADE_POS].nActivationHeight = 251; - consensus.vUpgrades[Consensus::UPGRADE_POS_V2].nActivationHeight = 251; + consensus.vUpgrades[Consensus::UPGRADE_POS].nActivationHeight = 298; + consensus.vUpgrades[Consensus::UPGRADE_POS_V2].nActivationHeight = 298; consensus.vUpgrades[Consensus::UPGRADE_ZC].nActivationHeight = 300; consensus.vUpgrades[Consensus::UPGRADE_ZC_V2].nActivationHeight = 300; consensus.vUpgrades[Consensus::UPGRADE_BIP65].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; consensus.vUpgrades[Consensus::UPGRADE_ZC_PUBLIC].nActivationHeight = 400; - consensus.vUpgrades[Consensus::UPGRADE_V3_4].nActivationHeight = 251; + consensus.vUpgrades[Consensus::UPGRADE_V3_4].nActivationHeight = 298; consensus.vUpgrades[Consensus::UPGRADE_V4_0].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; consensus.vUpgrades[Consensus::UPGRADE_V5_0].nActivationHeight = 300; consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 300; - consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 251; - consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 576; - consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = 601; + consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 298; + consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 301; + consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = 301; /** * The message start string is designed to be unlikely to occur in normal data. diff --git a/src/pow.cpp b/src/pow.cpp index 3f281ca95a5aa..4d56941ab706f 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -20,6 +20,14 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead { const Consensus::Params& consensus = Params().GetConsensus(); + if(Params().IsRegTestNet() && consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { + arith_uint256 bnNew; + bnNew.SetCompact(470120567); + return bnNew.GetCompact(); + } else if (!consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { + return pindexLast->nBits; + } + if (consensus.fPowNoRetargeting) return pindexLast->nBits; From 83fdf1ea05e1306b630398f4e88eb18340a3a84f Mon Sep 17 00:00:00 2001 From: Liquid Date: Sat, 4 Feb 2023 09:03:20 -0600 Subject: [PATCH 72/82] [Budget] Fix DMN Voting --- src/qt/pivx/governancemodel.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 0f370fde2e429..6952b6ac2b42e 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -8,12 +8,14 @@ #include "budget/budgetutil.h" #include "destination_io.h" #include "guiconstants.h" +#include "optional.h" #include "qt/transactiontablemodel.h" #include "qt/transactionrecord.h" #include "qt/pivx/mnmodel.h" #include "tiertwo/tiertwo_sync_state.h" #include "utilmoneystr.h" #include "utilstrencodings.h" +#include "wallet/wallet.h" // TODO: Move to walletModel #include "walletmodel.h" #include @@ -262,14 +264,15 @@ OperationResult GovernanceModel::voteForProposal(const ProposalInfo& prop, const std::vector& mnVotingAlias) { UniValue ret; // future: don't use UniValue here. + auto p_wallet = vpwallets[0]; // TODO: Move to walletModel + bool fLegacyMN = walletModel->isRegTestNetwork() ? false : true; // For now, only DMN on regtest for (const auto& mnAlias : mnVotingAlias) { - bool fLegacyMN = true; // For now, only legacy MNs - ret = mnBudgetVoteInner(nullptr, + ret = mnBudgetVoteInner(p_wallet, fLegacyMN, prop.id, false, isVotePositive ? CBudgetVote::VoteDirection::VOTE_YES : CBudgetVote::VoteDirection::VOTE_NO, - mnAlias); + fLegacyMN ? mnAlias : boost::optional{}); if (ret.exists("detail") && ret["detail"].isArray()) { const UniValue& obj = ret["detail"].get_array()[0]; if (obj["result"].getValStr() != "success") { From 84b0e29e6aaff6e12cf7b7fb15fa9616fe6bee9f Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Sat, 4 Feb 2023 17:18:12 +0100 Subject: [PATCH 73/82] Fixed DMN creation system: the collateral can now be included in the proreg --- src/qt/pivx/masternodewizarddialog.cpp | 57 ++++++---- src/qt/pivx/mnmodel.cpp | 147 +++++++++++++++++++++---- src/qt/pivx/mnmodel.h | 20 +++- src/qt/walletmodel.cpp | 4 +- src/qt/walletmodel.h | 2 +- 5 files changed, 179 insertions(+), 51 deletions(-) diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index a12a2da981795..6ba191b4b8ba8 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -3,6 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "qt/pivx/masternodewizarddialog.h" +#include "optional.h" +#include "primitives/transaction.h" #include "qt/pivx/forms/ui_masternodewizarddialog.h" #include "key_io.h" @@ -408,24 +410,7 @@ bool MasterNodeWizardDialog::createMN() std::string ipAddress = addressStr.toStdString(); std::string port = portStr.toStdString(); - // Look for a valid collateral utxo - COutPoint collateralOut; - - if (!walletModel->getMNCollateralCandidate(collateralOut)) { - // New receive address - auto r = walletModel->getNewAddress(alias); - if (!r) return errorOut(tr(r.getError().c_str())); - if (!mnModel->createMNCollateral(addressLabel, - QString::fromStdString(r.getObjResult()->ToString()), - collateralOut, - returnStr)) { - // error str set internally - return false; - } - } - if (isDeterministic) { - // 1) Get or create the owner addr auto opOwnerAddrAndKeyId = getOrCreateOwnerAddress(alias); if (!opOwnerAddrAndKeyId.getRes()) { return errorOut(tr(opOwnerAddrAndKeyId.getError().c_str())); @@ -462,11 +447,20 @@ bool MasterNodeWizardDialog::createMN() votingAddr = ownerKeyId; } - // For now, collateral key is always inside the wallet + Optional collateralOut = COutPoint(); + bool foundCandidate = false; + if(!walletModel->getMNCollateralCandidate(*collateralOut)){ + collateralOut= nullopt; + }else{ + //We have to lock the collateral or the system could spend it + walletModel->lockCoin(*collateralOut); + foundCandidate = true; + } std::string error_str; - walletModel->lockCoin(collateralOut); + auto res = mnModel->createDMN(alias, collateralOut, + addressLabel, ipAddress, port, ownerKeyId, @@ -477,16 +471,18 @@ bool MasterNodeWizardDialog::createMN() (uint16_t) operatorPercentage * 100, // operator percentage operatorPayoutKeyId); // operator payout script if (!res) { - walletModel->unlockCoin(collateralOut); + //unlock in case of error + if(foundCandidate){ + walletModel->unlockCoin(*collateralOut); + } return errorOut(tr(error_str.c_str())); } - // If the operator key was created locally, let's get it for the summary // future: move "operatorSk" to a constant field std::string operatorSk = walletModel->getStrFromTxExtraData(*res.getObjResult(), "operatorSk"); mnSummary = std::make_unique(alias, ipAddress+":"+port, - collateralOut, + collateralOut?*collateralOut:COutPoint(UINT256_ZERO,0), //TODO: the outpoint index is not 0 in general, but it does not matter (just display) ownerAddrStr, payoutAddrStr, operatorSk.empty() ? operatorKey.toStdString() : operatorSk, @@ -496,7 +492,24 @@ bool MasterNodeWizardDialog::createMN() returnStr = tr("Deterministic Masternode created! It will appear on your list on the next mined block!"); } else { + // Legacy + // Look for a valid collateral utxo + COutPoint collateralOut; + + if (!walletModel->getMNCollateralCandidate(collateralOut)) { + // New receive address + auto r = walletModel->getNewAddress(alias); + if (!r) return errorOut(tr(r.getError().c_str())); + if (!mnModel->createDMNExternalCollateral(addressLabel, + QString::fromStdString(r.getObjResult()->ToString()), + collateralOut, + returnStr)) { + // error str set internally + return false; + } + } + CKey secret; secret.MakeNewKey(false); std::string mnKeyString = KeyIO::EncodeSecret(secret); diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index fddafbf541b21..c1865f2edad14 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -13,6 +13,7 @@ #include "net.h" // for validateMasternodeIP #include "netbase.h" #include "operationresult.h" +#include "primitives/transaction.h" #include "tiertwo/tiertwo_sync_state.h" #include "uint256.h" #include "qt/bitcoinunits.h" @@ -24,6 +25,7 @@ #include #include +#include uint16_t MasternodeWrapper::getType() const { @@ -311,8 +313,9 @@ std::string translateRejectionError(const std::string& rejection) return rejection; } -static CallResult createDMNInternal(const COutPoint& collateral, - const CKey& keyCollateral, +CallResult MNModel::createDMNInternal(const Optional& collateral, + const Optional& addr_label, + const Optional& keyCollateral, const CService& service, const CKeyID& ownerAddr, const CBLSPublicKey& operatorPubKey, @@ -328,7 +331,7 @@ static CallResult createDMNInternal(const COutPoint& collateral, pl.keyIDOwner = ownerAddr; pl.pubKeyOperator = operatorPubKey; pl.keyIDVoting = votingAddr ? *votingAddr : pl.keyIDOwner; - pl.collateralOutpoint = collateral; + pl.collateralOutpoint = (collateral ? *collateral : COutPoint(UINT256_ZERO, 0)); //dummy outpoint if collateral is nullopt pl.scriptPayout = GetScriptForDestination(payoutAddr); if (operatorPayoutAddr) { pl.nOperatorReward = *operatorPercentage; @@ -337,29 +340,68 @@ static CallResult createDMNInternal(const COutPoint& collateral, // make sure fee calculation works pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); - CMutableTransaction tx; - tx.nVersion = CTransaction::TxVersion::SAPLING; - tx.nType = CTransaction::TxType::PROREG; - - auto wallet = vpwallets[0]; // TODO: Move to walletModel - auto res = FundSpecialTx(wallet, tx, pl); - if (!res) return {res.getError()}; - - res = SignSpecialTxPayloadByString(pl, keyCollateral); - if (!res) return {res.getError()}; - std::map extraValues; if (operatorSk) { // Only if the operator sk was provided extraValues.emplace("operatorSk", bls::EncodeSecret(Params(), *operatorSk)); } - res = SignAndSendSpecialTx(wallet, tx, pl, &extraValues); - return res ? CallResult(tx.GetHash()) : + auto wallet = vpwallets[0]; // TODO: Move to walletModel + if(collateral){ + if(!keyCollateral){ + return CallResult("null key collateral"); + } + CMutableTransaction tx; + tx.nVersion = CTransaction::TxVersion::SAPLING; + tx.nType = CTransaction::TxType::PROREG; + auto res = FundSpecialTx(wallet, tx, pl); + if (!res) return {res.getError()}; + + res = SignSpecialTxPayloadByString(pl, *keyCollateral); + if (!res) return {res.getError()}; + res = SignAndSendSpecialTx(wallet, tx, pl, &extraValues); + return res ? CallResult(tx.GetHash()) : + CallResult(translateRejectionError(res.getError())); + }else{ + if(!addr_label){ + return CallResult("Null address label"); + } + std::string alias = addr_label->toStdString(); + CTransactionRef ret_tx; + auto r = walletModel->getNewAddress(alias); + QString returnStr; + + //CmutTx used only to compute the size of payload + CMutableTransaction tx_test; + tx_test.nVersion= CTransaction::TxVersion::SAPLING; + tx_test.nType = CTransaction::TxType::PROREG; + SetTxPayload(tx_test, pl); + const int nExtraSize = int(GetSerializeSize(tx_test.extraPayload)+GetSerializeSize(tx_test.sapData)); + + COutPoint collateral_outpoint; + if (!r) return CallResult(translateRejectionError(r.getError())); + if (!createDMNInternalCollateral(*addr_label, + QString::fromStdString(r.getObjResult()->ToString()), + ret_tx, + collateral_outpoint, + returnStr,nExtraSize)) { + //error str set internally + return CallResult(returnStr.toStdString()); + } + pl.collateralOutpoint = collateral_outpoint; + CMutableTransaction tx = CMutableTransaction(*ret_tx); + tx.nVersion = CTransaction::TxVersion::SAPLING; + tx.nType = CTransaction::TxType::PROREG; + pl.vchSig.clear(); + UpdateSpecialTxInputsHash(tx, pl); + auto res = SignAndSendSpecialTx(wallet, tx, pl, &extraValues); + return res ? CallResult(tx.GetHash()) : CallResult(translateRejectionError(res.getError())); + } } CallResult MNModel::createDMN(const std::string& alias, - const COutPoint& collateral, + const Optional& collateral, + const Optional& addr_label, std::string& serviceAddr, const std::string& servicePort, const CKeyID& ownerAddr, @@ -370,18 +412,16 @@ CallResult MNModel::createDMN(const std::string& alias, const Optional& operatorPercentage, const Optional& operatorPayoutAddr) { - // Parse and validate inputs - // Different DMN creation types: // 1. internal. // 2. external. // 3. fund. + //Either one of them must be non null auto p_wallet = vpwallets[0]; // TODO: Move to walletModel const auto& chainparams = Params(); // 1) Create the simplest DMN, the collateral was generated by this wallet. - CService service; if (!serviceAddr.empty()) { if (!Lookup(serviceAddr+":"+servicePort, service, chainparams.GetDefaultPort(), false)) { @@ -391,11 +431,15 @@ CallResult MNModel::createDMN(const std::string& alias, } CPubKey pubKeyCollateral; - CKey keyCollateral; - if (!p_wallet->GetMasternodeVinAndKeys(pubKeyCollateral, keyCollateral, collateral, false, strError)) { + Optional keyCollateral = nullopt; + + if (collateral){ + keyCollateral = CKey(); + if(!p_wallet->GetMasternodeVinAndKeys(pubKeyCollateral, *keyCollateral, *collateral, false, strError)) { return {strError}; + } } - + // parse operator pubkey or create one Optional operatorSk{nullopt}; CBLSPublicKey operatorPk; @@ -414,6 +458,7 @@ CallResult MNModel::createDMN(const std::string& alias, } auto res = createDMNInternal(collateral, + addr_label, keyCollateral, service, ownerAddr, @@ -475,8 +520,62 @@ OperationResult MNModel::killDMN(const uint256& collateralHash, unsigned int out return {true}; } +//This functions create a collateral that will be "locked" inside the ProRegTx (so INTERNAL collateral) +bool MNModel::createDMNInternalCollateral( + const QString& alias, + const QString& addr, + CTransactionRef& ret_tx, + COutPoint& ret_outpoint, + QString& ret_error, + int nExtraSize +){ + SendCoinsRecipient sendCoinsRecipient(addr, alias, getMNCollateralRequiredAmount(), ""); + + // Send the 10 tx to one of your address + QList recipients; + recipients.append(sendCoinsRecipient); + WalletModelTransaction currentTransaction(recipients); + WalletModel::SendCoinsReturn prepareStatus; + // no coincontrol, no P2CS delegations + prepareStatus = walletModel->prepareTransaction(¤tTransaction, nullptr, false,nExtraSize); + ret_tx = currentTransaction.getTransaction(); + + QString returnMsg = tr("Unknown error"); + // process prepareStatus and on error generate message shown to user + CClientUIInterface::MessageBoxFlags informType; + returnMsg = GuiTransactionsUtils::ProcessSendCoinsReturn( + prepareStatus, + walletModel, + informType, // this flag is not needed + BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), + currentTransaction.getTransactionFee()), + true + ); + + if (prepareStatus.status != WalletModel::OK) { + ret_error = tr("Prepare master node failed.\n\n%1\n").arg(returnMsg); + return false; + } + + int indexOut = -1; + for (int i=0; i < (int)ret_tx->vout.size(); i++) { + const CTxOut& out = ret_tx->vout[i]; + if (out.nValue == getMNCollateralRequiredAmount()) { + indexOut = i; + break; + } + } + if (indexOut == -1) { + ret_error = tr("Invalid collateral output index"); + return false; + } + // save the collateral outpoint + ret_outpoint = COutPoint(UINT256_ZERO, indexOut); //generalise to second case + return true; +} -bool MNModel::createMNCollateral( +//This functions creates and send an EXTERNAL collateral the ProRegTx will just reference it +bool MNModel::createDMNExternalCollateral( const QString& alias, const QString& addr, COutPoint& ret_outpoint, diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index f69e43ce26a2e..a74d7d5fded91 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -10,6 +10,8 @@ #include "masternodeconfig.h" #include "operationresult.h" #include "primitives/transaction.h" +#include "bls/key_io.h" +#include "wallet/wallet.h" // TODO: Move to walletModel class CMasternode; class DMNView; @@ -110,7 +112,8 @@ class MNModel : public QAbstractTableModel // Creates the DMN and return the hash of the proregtx CallResult createDMN(const std::string& alias, - const COutPoint& collateral, + const Optional& collateral, + const Optional& addr_label, std::string& serviceAddr, const std::string& servicePort, const CKeyID& ownerAddr, @@ -124,8 +127,10 @@ class MNModel : public QAbstractTableModel // Completely stops the Masternode spending the collateral OperationResult killDMN(const uint256& collateralHash, unsigned int outIndex); + // Generates the collateral transaction - bool createMNCollateral(const QString& alias, const QString& addr, COutPoint& ret_outpoint, QString& ret_error); + bool createDMNExternalCollateral(const QString& alias, const QString& addr, COutPoint& ret_outpoint, QString& ret_error); + bool createDMNInternalCollateral(const QString& alias, const QString& addr, CTransactionRef& ret_tx,COutPoint& ret_outpoint, QString& ret_error,int nExtraSize=0); // Creates the mnb and broadcast it to the network bool startLegacyMN(const CMasternodeConfig::CMasternodeEntry& mne, int chainHeight, std::string& strError); void startAllLegacyMNs(bool onlyMissing, int& amountOfMnFailed, int& amountOfMnStarted, @@ -148,6 +153,17 @@ class MNModel : public QAbstractTableModel QMap collateralTxAccepted; const MasternodeWrapper* getMNWrapper(const QString& mnAlias); + CallResult createDMNInternal(const Optional& collateral, + const Optional& addr_label, + const Optional& keyCollateral, + const CService& service, + const CKeyID& ownerAddr, + const CBLSPublicKey& operatorPubKey, + const Optional& votingAddr, + const CKeyID& payoutAddr, + const Optional& operatorSk, + const Optional& operatorPercentage, + const Optional& operatorPayoutAddr); }; #endif // MNMODEL_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e09a11c5395a8..2e761a2900da4 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -452,7 +452,7 @@ bool WalletModel::addKeys(const CKey& key, const CPubKey& pubkey, WalletRescanRe return true; } -WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction* transaction, const CCoinControl* coinControl, bool fIncludeDelegations) +WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction* transaction, const CCoinControl* coinControl, bool fIncludeDelegations, int nExtraSize) { CAmount total = 0; QList recipients = transaction->getRecipients(); @@ -540,7 +540,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact true, 0, fIncludeDelegations, - &transaction->fIsStakeDelegationVoided); + &transaction->fIsStakeDelegationVoided,nExtraSize); transaction->setTransactionFee(nFeeRequired); if (!fCreated) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index a7bcacdbee772..efb2165e7b257 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -214,7 +214,7 @@ class WalletModel : public QObject const CWalletTx* getTx(uint256 id); // prepare transaction for getting txfee before sending coins - SendCoinsReturn prepareTransaction(WalletModelTransaction* transaction, const CCoinControl* coinControl = NULL, bool fIncludeDelegations = true); + SendCoinsReturn prepareTransaction(WalletModelTransaction* transaction, const CCoinControl* coinControl = NULL, bool fIncludeDelegations = true, int nExtraSize=0); // Send coins to a list of recipients SendCoinsReturn sendCoins(WalletModelTransaction& transaction); From 6dd881ad18722317545de382d4816ea4601993b6 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Sat, 4 Feb 2023 17:48:39 +0100 Subject: [PATCH 74/82] Forgot to uncomment --- src/validation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index d50682192abf5..a2ff75a42b899 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2844,8 +2844,8 @@ bool CheckWork(const CBlock& block, const CBlockIndex* const pindexPrev) bool CheckBlockTime(const CBlockHeader& block, CValidationState& state, CBlockIndex* const pindexPrev) { // Not enforced on RegTest - //if (Params().IsRegTestNet()) - // return true; + if (Params().IsRegTestNet()) + return true; const int64_t blockTime = block.GetBlockTime(); const int blockHeight = pindexPrev->nHeight + 1; From 235a189747c2ff151afcc0baff8a29ec5c7607bd Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 6 Feb 2023 14:44:37 -0600 Subject: [PATCH 75/82] Adjust budget cycles on regtest --- src/chainparams.cpp | 2 +- src/pow.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ae18134469ac2..7968caed2a6e4 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -528,7 +528,7 @@ class CRegTestParams : public CChainParams consensus.powLimit = uint256S("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV1 = uint256S("0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV2 = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - consensus.nBudgetCycleBlocks = 144; // approx 10 cycles per day + consensus.nBudgetCycleBlocks = 200; // approx 10 cycles per day consensus.nBudgetFeeConfirmations = 3; // (only 8-blocks window for finalization on regtest) consensus.nCoinbaseMaturity = 100; consensus.nFutureTimeDriftPoW = 7200; diff --git a/src/pow.cpp b/src/pow.cpp index 4d56941ab706f..9c53a21fca428 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -20,13 +20,13 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead { const Consensus::Params& consensus = Params().GetConsensus(); - if(Params().IsRegTestNet() && consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { + /*if(Params().IsRegTestNet() && consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { arith_uint256 bnNew; bnNew.SetCompact(470120567); return bnNew.GetCompact(); } else if (!consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { return pindexLast->nBits; - } + }*/ if (consensus.fPowNoRetargeting) return pindexLast->nBits; From e5a2789da20ac455da13d4841a9c8450ab832044 Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 6 Feb 2023 15:30:09 -0600 Subject: [PATCH 76/82] Slow regtest down again --- src/pow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pow.cpp b/src/pow.cpp index 9c53a21fca428..e8a929750d083 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -20,13 +20,13 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead { const Consensus::Params& consensus = Params().GetConsensus(); - /*if(Params().IsRegTestNet() && consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { + if(Params().IsRegTestNet() && consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { arith_uint256 bnNew; - bnNew.SetCompact(470120567); + bnNew.SetCompact(470420567); return bnNew.GetCompact(); } else if (!consensus.NetworkUpgradeActive(pindexLast->nHeight + 1, Consensus::UPGRADE_POS)) { return pindexLast->nBits; - }*/ + } if (consensus.fPowNoRetargeting) return pindexLast->nBits; From 040567c2730be37b9135bb5b08edf86b3022f7ce Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 6 Feb 2023 15:33:14 -0600 Subject: [PATCH 77/82] Adjust budget cycle on regtest --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 7968caed2a6e4..54deda19db741 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -528,7 +528,7 @@ class CRegTestParams : public CChainParams consensus.powLimit = uint256S("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV1 = uint256S("0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV2 = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - consensus.nBudgetCycleBlocks = 200; // approx 10 cycles per day + consensus.nBudgetCycleBlocks = 30; // approx 10 cycles per day consensus.nBudgetFeeConfirmations = 3; // (only 8-blocks window for finalization on regtest) consensus.nCoinbaseMaturity = 100; consensus.nFutureTimeDriftPoW = 7200; From 81335d866886ef9cc92e4862a2a436f3a8dea744 Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 6 Feb 2023 18:04:50 -0600 Subject: [PATCH 78/82] Bump budgetcycleblocks --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 54deda19db741..b43cb024c858c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -528,7 +528,7 @@ class CRegTestParams : public CChainParams consensus.powLimit = uint256S("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV1 = uint256S("0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV2 = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - consensus.nBudgetCycleBlocks = 30; // approx 10 cycles per day + consensus.nBudgetCycleBlocks = 90; // approx 10 cycles per day consensus.nBudgetFeeConfirmations = 3; // (only 8-blocks window for finalization on regtest) consensus.nCoinbaseMaturity = 100; consensus.nFutureTimeDriftPoW = 7200; From 8156daeca80c8cf998b99032f242e88ebae373ea Mon Sep 17 00:00:00 2001 From: Liquid Date: Wed, 8 Feb 2023 08:55:25 -0600 Subject: [PATCH 79/82] Bump back Budget Cycle Blocks --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b43cb024c858c..ae18134469ac2 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -528,7 +528,7 @@ class CRegTestParams : public CChainParams consensus.powLimit = uint256S("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV1 = uint256S("0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV2 = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - consensus.nBudgetCycleBlocks = 90; // approx 10 cycles per day + consensus.nBudgetCycleBlocks = 144; // approx 10 cycles per day consensus.nBudgetFeeConfirmations = 3; // (only 8-blocks window for finalization on regtest) consensus.nCoinbaseMaturity = 100; consensus.nFutureTimeDriftPoW = 7200; From 9cd1521f16d47a75afd8130e3b108058ea9701fe Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Wed, 8 Feb 2023 22:47:40 +0100 Subject: [PATCH 80/82] Added possibility to POSE-UNBAN fom GUI --- src/qt/pivx/masternodeswidget.cpp | 28 +++++++++++++++++++++- src/qt/pivx/mnmodel.cpp | 40 ++++++++++++++++++++++++++++++- src/qt/pivx/mnmodel.h | 3 +++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 30364560c6526..b32c9fba22778 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "qt/pivx/masternodeswidget.h" +#include "bls/bls_wrapper.h" #include "qt/pivx/forms/ui_masternodeswidget.h" #include "qt/pivx/defaultdialog.h" @@ -17,6 +18,7 @@ #include "qt/pivx/mnmodel.h" #include "qt/pivx/optionbutton.h" #include "qt/walletmodel.h" +#include "uint256.h" #define DECORATION_SIZE 65 #define NUM_ITEMS 3 @@ -166,7 +168,7 @@ void MasterNodesWidget::onMNClicked(const QModelIndex& _index) if (!menu) { menu = new TooltipMenu(window, this); connect(menu, &TooltipMenu::message, this, &AddressesWidget::message); - menu->addBtn(0, tr("Start"), [this](){onEditMNClicked();}); + menu->addBtn(0, tr("Start"), [this](){onEditMNClicked();}); //TODO: change to UNBAN once 6.0 is out menu->addBtn(1, tr("Delete"), [this](){onDeleteMNClicked();}); menu->addBtn(2, tr("Info"), [this](){onInfoMNClicked();}); menu->adjustSize(); @@ -219,6 +221,30 @@ void MasterNodesWidget::onEditMNClicked() bool isEnabled = index.sibling(index.row(), MNModel::IS_POSE_ENABLED).data(Qt::DisplayRole).toBool(); if (isEnabled) { inform(tr("Cannot start an already started Masternode")); + }else{ + uint256 proTxHash =uint256S(index.sibling(index.row(), MNModel::PRO_TX_HASH).data(Qt::DisplayRole).toString().toStdString()); + Optional opDMN = interfaces::g_tiertwo->getDMNData(proTxHash, + clientModel->getLastBlockIndexProcessed()); + if(!opDMN){ + inform(tr("Masternode not found")); + }else{ + std::string operatorKeyS = opDMN->operatorSk; + if(operatorKeyS.empty()){ + inform("Operator secret key not found"); + }else{ + Optional operator_key = bls::DecodeSecret(Params(),operatorKeyS); + if(operator_key){ + std::string error_str = ""; + if(!mnModel->unbanDMN(*operator_key,proTxHash,error_str)){ + inform(QString::fromStdString(error_str)); + } + }else{ + inform("Could not decode operator secret key"); + } + } + + } + } } } diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index c1865f2edad14..30c6740845f4f 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -6,6 +6,7 @@ #include "bls/key_io.h" #include "coincontrol.h" +#include "evo/deterministicmns.h" #include "interfaces/tiertwo.h" #include "evo/specialtx_utils.h" #include "masternode.h" @@ -417,7 +418,6 @@ CallResult MNModel::createDMN(const std::string& alias, // 2. external. // 3. fund. - //Either one of them must be non null auto p_wallet = vpwallets[0]; // TODO: Move to walletModel const auto& chainparams = Params(); @@ -476,7 +476,45 @@ CallResult MNModel::createDMN(const std::string& alias, // All good return res; } +//unban a Pose-banned DMN +bool MNModel::unbanDMN(CBLSSecretKey& operatorKey,uint256 proTxHash, std::string& strError){ + ProUpServPL pl; + pl.nVersion = ProUpServPL::CURRENT_VERSION; + pl.proTxHash = proTxHash; + auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(pl.proTxHash); //make sure that the wallet is synced first? + if (!dmn) { + strError = "Masternode not found"; + return false; + } + if(!dmn->IsPoSeBanned()){ + strError = "Masternode is not Pose-banned"; + return false; + } + pl.addr = dmn->pdmnState->addr; + pl.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout; + CMutableTransaction tx; + tx.nVersion = CTransaction::TxVersion::SAPLING; + tx.nType = CTransaction::TxType::PROUPSERV; + + auto wallet = vpwallets[0]; // TODO: Move to walletModel + auto res = FundSpecialTx(wallet, tx, pl); + if(!res){ + strError = res.getError(); + return false; + } + res = SignSpecialTxPayloadByHash(tx, pl, operatorKey); + if(!res){ + strError = res.getError(); + return false; + } + res = SignAndSendSpecialTx(wallet, tx, pl); + if(!res){ + strError = res.getError(); + return false; + } + return true; +} OperationResult MNModel::killDMN(const uint256& collateralHash, unsigned int outIndex) { auto p_wallet = vpwallets[0]; // TODO: Move to walletModel diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index a74d7d5fded91..3127151158d37 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -11,6 +11,7 @@ #include "operationresult.h" #include "primitives/transaction.h" #include "bls/key_io.h" +#include "uint256.h" #include "wallet/wallet.h" // TODO: Move to walletModel class CMasternode; @@ -128,6 +129,8 @@ class MNModel : public QAbstractTableModel OperationResult killDMN(const uint256& collateralHash, unsigned int outIndex); + //Unban a Pose-banned DMN + bool unbanDMN(CBLSSecretKey& operatorKey,uint256 proTxHash, std::string& strError); // Generates the collateral transaction bool createDMNExternalCollateral(const QString& alias, const QString& addr, COutPoint& ret_outpoint, QString& ret_error); bool createDMNInternalCollateral(const QString& alias, const QString& addr, CTransactionRef& ret_tx,COutPoint& ret_outpoint, QString& ret_error,int nExtraSize=0); From 112c1d602209f4c1208a52c598d259659a9e3ead Mon Sep 17 00:00:00 2001 From: Liquid Date: Wed, 8 Feb 2023 16:30:44 -0600 Subject: [PATCH 81/82] Adjust finalization window for regtest too --- src/budget/budgetmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index fdd91e1458ac3..e114a3a86528c 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -114,7 +114,7 @@ uint256 CBudgetManager::SubmitFinalBudget() // Submit final budget during the last 2 days (2880 blocks) before payment for Mainnet, about 9 minutes (9 blocks) for Testnet int finalizationWindow = ((nBlocksPerCycle / 30) * 2); - if (Params().IsTestnet()) { + if (Params().IsTestnet() || Params().IsRegTestNet()) { // NOTE: 9 blocks for testnet is way to short to have any masternode submit an automatic vote on the finalized(!) budget, // because those votes are only submitted/relayed once every 56 blocks in CFinalizedBudget::AutoCheck() From 841c9de2cdcc2bcf5cd3c04c43dc5c05e5c15856 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Wed, 8 Feb 2023 23:33:23 +0100 Subject: [PATCH 82/82] Added confirmation --- src/qt/pivx/masternodeswidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index b32c9fba22778..4b601056a33a9 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -237,6 +237,8 @@ void MasterNodesWidget::onEditMNClicked() std::string error_str = ""; if(!mnModel->unbanDMN(*operator_key,proTxHash,error_str)){ inform(QString::fromStdString(error_str)); + }else{ + inform("Masternode successfully unbanned! Wait for the next minted block and it will update"); } }else{ inform("Could not decode operator secret key");