diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bce785e898b4..8a7643a5f10b5 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 @@ -417,6 +418,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 6f932722dd041..bdad84598584a 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 \ @@ -213,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 \ @@ -367,6 +369,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 \ @@ -594,6 +597,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/Makefile.qt.include b/src/Makefile.qt.include index 58e9bdd99deb8..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 \ @@ -511,6 +513,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/chainparams.cpp b/src/chainparams.cpp index 3e9e331184dbd..5910a96e2f63a 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 = 301; /** * The message start string is designed to be unlikely to occur in normal data. 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/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/evo/specialtx_utils.cpp b/src/evo/specialtx_utils.cpp new file mode 100644 index 0000000000000..ef3d2aa0256b5 --- /dev/null +++ b/src/evo/specialtx_utils.cpp @@ -0,0 +1,82 @@ +// 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 "net.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}; +} + +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(), extras); + CValidationState& state = res.state; + if (state.IsInvalid()) { + std::string debugMsg = state.GetDebugMessage(); + return {false, debugMsg.empty() ? state.GetRejectReason() : + strprintf("%s: %s", state.GetRejectReason(), debugMsg)}; + } + + return {true}; +} + +#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 new file mode 100644 index 0000000000000..ace08fded90ec --- /dev/null +++ b/src/evo/specialtx_utils.h @@ -0,0 +1,73 @@ +// 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 "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) +{ + 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; +} + +OperationResult SignAndSendSpecialTx(CWallet* pwallet, CMutableTransaction& tx, std::map* extras = nullptr); + +template +OperationResult SignAndSendSpecialTx(CWallet* const pwallet, CMutableTransaction& tx, const SpecialTxPayload& pl, std::map* extras = nullptr) +{ + SetTxPayload(tx, pl); + return SignAndSendSpecialTx(pwallet, tx, extras); +} + +#endif // ENABLE_WALLET + +Optional ParsePubKeyIDFromAddress(const std::string& strAddress, std::string& strError); + +#endif //PIVX_SPECIALTX_UTILS_H diff --git a/src/evo/specialtx_validation.cpp b/src/evo/specialtx_validation.cpp index 56b3691d4967d..9c1d7ce74b46a 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"); @@ -182,10 +182,19 @@ 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"); } + + //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(coin.out, pl, state, collateralTxDest)) { // pass the state returned by the function above @@ -641,18 +650,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..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. @@ -36,6 +39,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/interfaces/tiertwo.cpp b/src/interfaces/tiertwo.cpp new file mode 100644 index 0000000000000..0c319bbb2f402 --- /dev/null +++ b/src/interfaces/tiertwo.cpp @@ -0,0 +1,165 @@ +// 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 "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" + +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); + 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; + 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; + 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..be8b0cb5ee190 --- /dev/null +++ b/src/interfaces/tiertwo.h @@ -0,0 +1,84 @@ +// 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 "operationresult.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; +}; + +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 { +// 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 true if spork21 is enabled + bool isLegacySystemObsolete(); + + // 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;); }; + + // 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; +}; + +extern std::unique_ptr g_tiertwo; + +} // end namespace interfaces + + +#endif //PIVX_INTERFACES_TIERTWO_H 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/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 diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 8d236b43e3737..9652091065892 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -155,10 +155,13 @@ 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 ) qt5_generate_moc(pivx/pfborderimage.h ${CMAKE_CURRENT_SOURCE_DIR}/pivx/moc_pfborderimage.cpp) +qt5_generate_moc(pivx/clickablelabel.h ${CMAKE_CURRENT_SOURCE_DIR}/pivx/moc_clickablelabel.cpp) 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/clientmodel.cpp b/src/qt/clientmodel.cpp index 0c61354528a93..02d091de7a4ad 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -114,6 +114,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 fd531ea190dfc..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; @@ -69,6 +71,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.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/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/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/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/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/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); 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/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index e7d8a8f7a02c6..9742075c67152 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 @@ -100,6 +100,12 @@ 24 + + + 16777215 + 40 + + 0 @@ -208,7 +214,7 @@ - + @@ -234,7 +240,7 @@ - + 22 @@ -280,7 +286,7 @@ 0 - + 22 @@ -309,11 +315,11 @@ - + - + 0 @@ -335,7 +341,7 @@ - + 22 @@ -381,7 +387,7 @@ 0 - + 22 @@ -410,135 +416,394 @@ - + - - - Qt::Horizontal - - - QSizePolicy::Fixed + + + + 0 + 1 + - + - 100 - 20 + 16777215 + 1 - + + + + + + + - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - true + + + + 22 + 22 + - - + + + 22 + 22 + - - - 0 - - - 0 + + 0 + + + + + 22 + 22 + - - 0 + + + 22 + 22 + - - 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 4 + + + true + + + false + + + true + + + + + + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + + + + + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 0 + + + + + 22 + 22 + - - 0 + + + 22 + 22 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 5 + + + 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 + + + + 6 + + + true + + + false + + + true + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 100 + 20 + + + + + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 70 - 20 - - - - - - - - - 80 - 0 - - - - - 80 - 16777215 - - - - Qt::NoFocus - - - Intro - - - true - - - true - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 65 + 20 + + + + + + 80 @@ -547,7 +812,7 @@ - 80 + 83 16777215 @@ -555,18 +820,21 @@ Qt::NoFocus - Name + Intro true + + true + false - + Qt::Horizontal @@ -579,7 +847,7 @@ - + 80 @@ -588,7 +856,7 @@ - 80 + 83 16777215 @@ -596,7 +864,7 @@ Qt::NoFocus - Address + Name true @@ -607,41 +875,214 @@ - + Qt::Horizontal - - QSizePolicy::Fixed - - 70 + 40 20 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 30 - - + + + + + 80 + 0 + + + + + 83 + 16777215 + + + + Qt::NoFocus + + + Service + + + true + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 80 + 0 + + + + + 83 + 16777215 + + + + Qt::NoFocus + + + Owner + + + true + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 80 + 0 + + + + + 83 + 16777215 + + + + Qt::NoFocus + + + Operator + + + true + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 80 + 0 + + + + + 83 + 16777215 + + + + Qt::NoFocus + + + Voter + + + true + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 65 + 20 + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 12 + + @@ -655,11 +1096,11 @@ 16777215 - 350 + 370 - + 6 @@ -695,7 +1136,445 @@ - + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 75 + true + + + + Make sure you have this amount of coins. + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 140 + + + 12 + + + 140 + + + 12 + + + + + + 0 + 46 + + + + + 16777215 + 46 + + + + Set Masternode Name + + + + + + + + 16777215 + 28 + + + + Used for the collateral address label in the addressbook + + + Qt::AlignCenter + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + 25 + + + e.g. user_masternode + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 50 + + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + + + + 6 + + + 140 + + + 12 + + + 140 + + + 12 + + + + + + 0 + 50 + + + + + 16777215 + 50 + + + + Set Masternode IP and Port + + + + + + + Service of the node that must always be online + + + Qt::AlignCenter + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Enter IP address + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + e.g. 18.255.255.255 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + Enter port + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 6 + + + 78 + + + 12 + + + 78 + + + 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 @@ -705,23 +1584,23 @@ 20 - 20 + 30 - - - Qt::AlignHCenter|Qt::AlignTop + + + Enter Payout Address - - true + + Qt::AlignCenter - + Qt::Vertical @@ -731,29 +1610,20 @@ 20 - 20 + 14 - - - - 75 - true - - - - Make sure you have this amount of coins. - - - Qt::AlignCenter + + + e.g. Dxxx - + Qt::Vertical @@ -767,29 +1637,29 @@ - - + + 6 - 140 + 85 12 - 140 + 85 12 - + 0 - 50 + 40 @@ -799,35 +1669,80 @@ - Set Masternode Name + Set Operator Data + + + + + + + + 360 + 30 + + + + Customize the operator key, the operator reward percentage and payout address + + + Qt::AlignCenter + + + true - + Qt::Vertical + + QSizePolicy::Fixed + 20 - 40 + 20 - - - 25 + + + Enter address (if empty, the wallet will create one for you) + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + - e.g. user_masternode + e.g. bls-pubewlknlkweqkwli1j3 - + Qt::Vertical @@ -837,46 +1752,167 @@ 20 - 40 + 30 - + + + Optional: operator rewards percentage and payout address + - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignCenter - - true + + + + + + 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 - 140 + 85 12 - 140 + 85 12 - + 0 - 50 + 40 @@ -886,14 +1922,20 @@ - Set Masternode IP and Port + Set Voter Data - + + + + 360 + 30 + + - Address of the node that must always be online running the actual master node. + Customize who will be able to vote for proposal Qt::AlignCenter @@ -904,7 +1946,7 @@ - + Qt::Vertical @@ -920,9 +1962,9 @@ - + - Enter IP address + Enter address (if empty, the wallet will create one for you) Qt::AlignCenter @@ -930,7 +1972,7 @@ - + Qt::Vertical @@ -946,69 +1988,647 @@ - + - e.g. 18.255.255.255 + e.g. Dxx - + Qt::Vertical - - QSizePolicy::Fixed - 20 - 30 + 40 - - + + + + + + 0 + + + 0 + + + 12 + + + 0 + + + 12 + + + + + + 0 + 25 + + + + + 16777215 + 50 + + - Enter port + Masternode Created! + + + + + + + + 360 + 0 + + + + (Press on any label to copy the content) Qt::AlignCenter + + true + - - - Qt::Vertical + + + Qt::NoFocus - - QSizePolicy::Fixed + + false - - - 20 - 14 - + + #scrollAreaSummary{ + border: 0; + } - - - - - - - - - Qt::Vertical + + 0 - - - 20 - 40 - + + true - + + + + 0 + 0 + 214 + 352 + + + + Qt::NoFocus + + + #containerSummary{ + border: 0; + } + + + + + + + 8 + + + 9 + + + 9 + + + 12 + + + + + Owner Section + + + + + + + + 0 + 0 + + + + + 0 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Main Address + + + + + + + N/A + + + + + + + + + + Qt::LeftToRight + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Payout Address + + + + + + + N/A + + + + + + + + + + + + + + 60 + 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 + + + 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 + + + + + + + + + + + + + + + + + 8 + + + 9 + + + 12 + + + + + Voting Section + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 4 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Voting Address + + + + + + + N/A + + + + + + + + + + + + + + + @@ -1019,10 +2639,13 @@ Qt::Vertical + + QSizePolicy::Minimum + 20 - 40 + 10 @@ -1093,6 +2716,13 @@ + + + ClickableLabel + QLabel +
qt/pivx/clickablelabel.h
+
+
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/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/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") { diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 4e772ab26f472..55693e416461f 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 8782b9a79ac06..ffe0f960a0851 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -3,8 +3,10 @@ // 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" #include "qt/pivx/qtutils.h" #include "qt/pivx/mnrow.h" #include "qt/pivx/mninfodialog.h" @@ -12,9 +14,11 @@ #include "clientmodel.h" #include "guiutil.h" +#include "interfaces/tiertwo.h" #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 @@ -39,7 +43,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); + uint8_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 +124,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); } @@ -153,22 +163,23 @@ 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();}); //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(); } else { - this->menu->hide(); + menu->hide(); } - this->index = _index; + + index = _index; + uint8_t mnType = index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt(); + menu->showHideBtn(0, mnType == MNViewType::LEGACY); + menu->move(pos); menu->show(); @@ -189,21 +200,57 @@ 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; + 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 + 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")); + } 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("Masternode successfully unbanned! Wait for the next minted block and it will update"); + } + } else { + inform("Could not decode operator secret key"); + } + } + } + } } } } @@ -302,20 +349,33 @@ 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 = ((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(); + 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) { + 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.")); } @@ -329,6 +389,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 = ((uint8_t) index.sibling(index.row(), MNModel::TYPE).data(Qt::DisplayRole).toUInt()) == MNViewType::LEGACY; bool convertOK = false; unsigned int indexOut = outIndex.toUInt(&convertOK); @@ -337,18 +398,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() @@ -366,19 +443,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) { - // Update list - mnModel->addMn(dialog->mnEntry); - updateListState(); - // add mn - inform(dialog->returnStr); - } else { - warn(tr("Error creating masternode"), dialog->returnStr); + MasterNodeWizardDialog* dialog = new MasterNodeWizardDialog(walletModel, mnModel, clientModel, 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 0136adb520741..a07abd2104122 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -6,12 +6,15 @@ #include "qt/pivx/forms/ui_masternodewizarddialog.h" #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" -#include +#include #include +#include static inline QString formatParagraph(const QString& str) { return "

" + str + "

"; @@ -21,72 +24,121 @@ 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); } } -MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, MNModel* _mnModel, QWidget *parent) : +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, ClientModel* _clientModel, QWidget *parent) : FocusedDialog(parent), ui(new Ui::MasterNodeWizardDialog), - icConfirm1(new QPushButton(this)), - icConfirm3(new QPushButton(this)), - icConfirm4(new QPushButton(this)), walletModel(model), - mnModel(_mnModel) + mnModel(_mnModel), + clientModel(_clientModel) { 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"); + isDeterministic = walletModel->isV6Enforced(); + + 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"); - setCssProperty({ui->pushNumber1, ui->pushNumber3, ui->pushNumber4}, "btn-number-check"); - setCssProperty({ui->pushName1, ui->pushName3, ui->pushName4}, "btn-name-check"); - ui->pushNumber1->setEnabled(false); - ui->pushNumber3->setEnabled(false); - ui->pushNumber4->setEnabled(false); - ui->pushName1->setEnabled(false); - ui->pushName3->setEnabled(false); - ui->pushName4->setEnabled(false); + QString collateralAmountStr(GUIUtil::formatBalance(mnModel->getMNCollateralRequiredAmount())); + initIntroPage(collateralAmountStr); + initCollateralPage(collateralAmountStr); + initServicePage(); + initOwnerPage(); + initOperatorPage(); + initVoterPage(); + initSummaryPage(); - // Frame 1 + // 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 3 +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 4 +void MasterNodeWizardDialog::initServicePage() +{ setCssProperty(ui->labelTitle4, "text-title-dialog"); setCssProperty({ui->labelSubtitleIp, ui->labelSubtitlePort}, "text-title"); setCssSubtitleScreen(ui->labelSubtitleAddressIp); @@ -94,31 +146,87 @@ 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->setText("51476"); - } else 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())); +} - // 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"); +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}); +} - // Connect btns - setCssBtnPrimary(ui->btnNext); - setCssProperty(ui->btnBack , "btn-dialog-cancel"); - ui->btnBack->setVisible(false); - setCssProperty(ui->pushButtonSkip, "ic-close"); +void MasterNodeWizardDialog::initOperatorPage() +{ + 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 QDoubleValidator(0.00, 100.00, 2, ui->lineEditPercentage)); +} - 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() +{ + setCssProperty({ui->scrollAreaSummary, ui->containerSummary}, "container"); + setCssProperty(ui->labelSummary, "text-title-dialog"); + setCssSubtitleScreen(ui->labelSubtitleSummary); + 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, ui->labelTitleVoterAddress}, "text-title-right"); + setCssProperty({ui->labelMainAddr, ui->labelPayoutAddr, ui->labelCollateralIndex, ui->labelCollateralHash, + 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)); + 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")); + }); + + connect(ui->labelVoterAddress, &ClickableLabel::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(mnSummary->votingKey)); + inform(tr("Voting address copied to clipboard")); + }); } void MasterNodeWizardDialog::showEvent(QShowEvent *event) @@ -126,50 +234,151 @@ 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); + if (list_pushNumber.size() != nextPos) list_pushNumber[nextPos]->setChecked(true); + setBtnsChecked(list_pushName, pos, nextPos); +} + void MasterNodeWizardDialog::accept() { - 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); + int nextPos = pos + 1; + switch (pos) { + 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); 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; } - case 2: { + case Pages::SERVICE: { // No empty address accepted if (ui->lineEditIpAddress->text().isEmpty()) { return; } - icConfirm4->setVisible(true); - isOk = createMN(); + if (!isDeterministic) { + 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; + hide(); + } + break; + } + case Pages::OWNER: { + if (!validateOwner()) return; // invalid state informed internally + moveToNextPage(pos, nextPos); + break; + } + 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; + } + 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(); +} + +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)); + 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")); + } + setShortText(ui->labelVoterAddress, QString::fromStdString(mnSummary->votingKey), 14); +} + +CallResult> getOrCreateAddress(const QString& input, + const std::string& alias, + const std::string& label_prefix, + WalletModel* walletModel, + bool checkIsMine) +{ + 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 { + std::string addrStr = input.toStdString(); + auto opKeyId = walletModel->getKeyIDFromAddr(addrStr); + 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}}; + } +} + +CallResult> MasterNodeWizardDialog::getOrCreateOwnerAddress(const std::string& alias) +{ + return getOrCreateAddress(ui->lineEditOwnerAddress->text(), alias, "owner", walletModel, true); +} + +CallResult> MasterNodeWizardDialog::getOrCreatePayoutAddress(const std::string& alias) +{ + 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() { if (!walletModel) { @@ -199,75 +408,258 @@ 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); - - // 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) { - // generate address fail - returnStr = tr(r.getError().c_str()); - return false; + if (isDeterministic) { + 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; + + // 3) Get operator data + QString operatorKey = ui->lineEditOperatorKey->text(); + Optional operatorPayoutKeyId = walletModel->getKeyIDFromAddr(ui->lineEditOperatorPayoutAddress->text().toStdString()); + double operatorPercentage = ui->lineEditPercentage->text().isEmpty() ? 0 : (double)ui->lineEditPercentage->text().toDouble(); + + // 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; } - if (!mnModel->createMNCollateral(addressLabel, - QString::fromStdString(r.getObjResult()->ToString()), - collateralOut, - returnStr)) { - // error str set internally - return false; + 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; + + auto res = mnModel->createDMN(alias, + collateralOut, + addressLabel, + ipAddress, + port, + ownerKeyId, + operatorKey.isEmpty() ? nullopt : Optional(operatorKey.toStdString()), + votingAddr, + payoutKeyId, + error_str, + (uint16_t)operatorPercentage * 100, // operator percentage + operatorPayoutKeyId); // operator payout script + if (!res) { + // 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 : 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, + 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 + // 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); + 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()); + } + 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()); + 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; } - mnEntry = mnModel->createLegacyMN(collateralOut, alias, ipAddress, port, mnKeyString, returnStr); - if (!mnEntry) { - // error str set inside createLegacyMN + QString payoutAddress(ui->lineEditPayoutAddress->text()); + if (!payoutAddress.isEmpty() && !walletModel->validateAddress(payoutAddress)) { + setCssEditLine(ui->lineEditPayoutAddress, false, true); + inform(tr("Invalid payout address")); return false; } - returnStr = tr("Masternode created! Wait %1 confirmations before starting it.").arg(mnModel->getMasternodeCollateralMinConf()); 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()); + 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); + 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; } } } +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) @@ -277,6 +669,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..f80240f197e39 100644 --- a/src/qt/pivx/masternodewizarddialog.h +++ b/src/qt/pivx/masternodewizarddialog.h @@ -10,7 +10,10 @@ #include "masternodeconfig.h" #include "qt/pivx/pwidget.h" +class ClientModel; +class ContactsDropdown; class MNModel; +class QLineEdit; class WalletModel; namespace Ui { @@ -18,37 +21,116 @@ 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 +enum Pages { + INTRO = 0, + ALIAS = 1, + SERVICE = 2, + OWNER = 3, + OPERATOR = 4, + VOTER = 5, + SUMMARY = 6 +}; + public: explicit MasterNodeWizardDialog(WalletModel* walletMode, MNModel* mnModel, + ClientModel* clientModel, QWidget *parent = nullptr); ~MasterNodeWizardDialog() override; 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; void onBackClicked(); private: Ui::MasterNodeWizardDialog *ui; - QPushButton* icConfirm1; - QPushButton* icConfirm3; - QPushButton* icConfirm4; - SnackBar *snackBar = nullptr; + ContactsDropdown* dropdownMenu{nullptr}; + QAction* actOwnerAddrList{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}; + ClientModel* clientModel{nullptr}; + + void initIntroPage(const QString& collateralAmountStr); + void initCollateralPage(const QString& collateralAmountStr); + void initServicePage(); + void initOwnerPage(); + void initOperatorPage(); + void initVoterPage(); + void initSummaryPage(); + bool createMN(); + void setSummary(); void inform(const QString& text); + bool errorOut(const QString& err); + + void moveToNextPage(int currentPos, int nextPos); + void moveBack(int backPos); + + bool validateService(); + bool validateVoter(); + bool validateOwner(); + bool validateOperator(); + + 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); }; #endif // MASTERNODEWIZARDDIALOG_H 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 82130dab2b3c8..148b291b8c8f4 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -4,19 +4,48 @@ #include "qt/pivx/mnmodel.h" +#include "bls/key_io.h" +#include "coincontrol.h" +#include "evo/deterministicmns.h" +#include "interfaces/tiertwo.h" +#include "evo/specialtx_utils.h" #include "masternode.h" #include "masternodeman.h" #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" #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" #include #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) {} @@ -28,26 +57,61 @@ void MNModel::init() void MNModel::updateMNList() { int mnMinConf = getMasternodeCollateralMinConf(); - int end = nodes.size(); nodes.clear(); 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())), + 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 @@ -61,51 +125,62 @@ int MNModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; - return 6; + 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 { 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(); - if (role == Qt::DisplayRole || role == Qt::EditRole) { + const MasternodeWrapper& mnWrapper = nodes.at(row); + switch (role) { + case Qt::DisplayRole: + case 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(); - // 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()) { - 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); } 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 +188,32 @@ 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()); + } + case TYPE:{ + return mnWrapper.getType(); + } + 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: + return formatTooltip(mnWrapper); + } // end role switch 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); + 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()) ); return true; @@ -153,17 +226,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())), + nullptr)); 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 +271,16 @@ 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::isLegacySystemObsolete() +{ + return interfaces::g_tiertwo->isLegacySystemObsolete(); } bool MNModel::isMNsNetworkSynced() @@ -205,11 +303,320 @@ int MNModel::getMasternodeCollateralMinConf() return Params().GetConsensus().MasternodeCollateralMinConf(); } -bool MNModel::createMNCollateral( - const QString& alias, - const QString& addr, - COutPoint& ret_outpoint, - QString& ret_error) +// 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; +} + +CallResult MNModel::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) +{ + 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 ? *collateral : COutPoint(UINT256_ZERO, 0)); // dummy outpoint if collateral is nullopt + pl.scriptPayout = GetScriptForDestination(payoutAddr); + if (operatorPayoutAddr) { + pl.nOperatorReward = *operatorPercentage; + pl.scriptOperatorPayout = GetScriptForDestination(*operatorPayoutAddr); + } + // make sure fee calculation works + pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); + + std::map extraValues; + if (operatorSk) { + // Only if the operator sk was provided + extraValues.emplace("operatorSk", bls::EncodeSecret(Params(), *operatorSk)); + } + 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 Optional& collateral, + const Optional& addr_label, + 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) +{ + // 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 {strError}; + } + } + + CPubKey pubKeyCollateral; + 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; + if (operatorPubKey) { + auto opPk = bls::DecodePublic(Params(), *operatorPubKey); + if (!opPk || !opPk->IsValid()) { + strError = "invalid operator pubkey"; + return {strError}; + } + operatorPk = *opPk; + } else { + // Stored within the register tx + operatorSk = CBLSSecretKey(); + operatorSk->MakeNewKey(); + operatorPk = operatorSk->GetPublicKey(); + } + + auto res = createDMNInternal(collateral, + addr_label, + keyCollateral, + service, + ownerAddr, + operatorPk, + votingAddr, // 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 {strError}; + } + + // 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 + 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}; +} +// 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; +} + +// 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, + QString& ret_error) { SendCoinsRecipient sendCoinsRecipient(addr, alias, getMNCollateralRequiredAmount(), ""); @@ -226,13 +633,12 @@ bool MNModel::createMNCollateral( // 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 - ); + 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); @@ -252,7 +658,7 @@ bool MNModel::createMNCollateral( CTransactionRef walletTx = currentTransaction.getTransaction(); std::string txID = walletTx->GetHash().GetHex(); int indexOut = -1; - for (int i=0; i < (int)walletTx->vout.size(); i++) { + for (int i = 0; i < (int)walletTx->vout.size(); i++) { const CTxOut& out = walletTx->vout[i]; if (out.nValue == getMNCollateralRequiredAmount()) { indexOut = i; @@ -319,6 +725,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 +812,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..3127151158d37 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -6,10 +6,53 @@ #define MNMODEL_H #include +#include "amount.h" #include "masternodeconfig.h" -#include "qt/walletmodel.h" +#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; +class DMNView; +class WalletModel; + +enum MNViewType : uint8_t +{ + LEGACY = 0, + DMN_OWNER = (1 << 0), + DMN_OPERATOR = (1 << 1), + DMN_VOTER = (1 << 2) +}; + +class MasternodeWrapper +{ +public: + explicit MasternodeWrapper( + const QString& _label, + const QString& _ipPortStr, + CMasternode* _masternode, + COutPoint& _collateralId, + const Optional& _mnPubKey, + const std::shared_ptr& _dmnView) : + label(_label), ipPort(_ipPortStr), masternode(_masternode), + collateralId(_collateralId), mnPubKey(_mnPubKey), dmnView(_dmnView) { }; + + 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}; + + // DMN data + std::shared_ptr dmnView{nullptr}; + + uint16_t getType() const; +}; class MNModel : public QAbstractTableModel { @@ -34,18 +77,23 @@ class MNModel : public QAbstractTableModel COLLATERAL_ID = 6, COLLATERAL_OUT_INDEX = 7, PRIV_KEY = 8, - WAS_COLLATERAL_ACCEPTED = 9 + 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 }; 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(); - + // 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); @@ -62,8 +110,30 @@ class MNModel : public QAbstractTableModel CAmount getMNCollateralRequiredAmount(); // Return the specific chain min conf for the collateral tx int getMasternodeCollateralMinConf(); + + // Creates the DMN and return the hash of the proregtx + CallResult createDMN(const std::string& alias, + const Optional& collateral, + const Optional& addr_label, + 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); + + + //Unban a Pose-banned DMN + bool unbanDMN(CBLSSecretKey& operatorKey,uint256 proTxHash, std::string& strError); // 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, @@ -74,15 +144,29 @@ 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); + 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/pivx/mnrow.cpp b/src/qt/pivx/mnrow.cpp index d24982e3a4e5f..74dc42c0c6eba 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, + uint8_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..37feba9f1b74b 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, + 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 diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index ee5adae401e91..f6bf865d8e2ad 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; @@ -355,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 d1bf4c839d30b..8b69724b674d4 100644 --- a/src/qt/pivx/qtutils.h +++ b/src/qt/pivx/qtutils.h @@ -71,9 +71,13 @@ 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); 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 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; 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 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 diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index b179c4c8d4da6..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) { @@ -1063,6 +1063,19 @@ 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) +{ + return wallet->GetStrFromTxExtraData(txHash, key); +} + 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..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); @@ -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; diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index b18ae85ad7bf9..9c86e46653055 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, RPCErrorCode errorCode = RPC_INTERNAL_ERROR) { + if (!res) throw JSONRPCError(errorCode, res.getError()); +} + #ifdef ENABLE_WALLET static CKey GetKeyFromWallet(CWallet* pwallet, const CKeyID& keyID) { @@ -198,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"); + std::string strError; + auto ret = ParsePubKeyIDFromAddress(strAddress, strError); + if (!ret) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strError); } - 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)); - } - return *keyID; + return *ret; } static CBLSPublicKey ParseBLSPubKey(const CChainParams& params, const std::string& strKey) @@ -262,130 +260,6 @@ static UniValue DmnToJson(const CDeterministicMNCPtr dmn) #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) -{ - 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); -} - -#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); - - 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()); - } - - TryATMP(tx, false); - const uint256& hashTx = tx.GetHash(); - RelayTx(hashTx); - return hashTx.GetHex(); -} - // Parses inputs (starting from index paramIdx) and returns ProReg payload static ProRegPL ParseProRegPLParams(const UniValue& params, unsigned int paramIdx) { @@ -418,7 +292,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)) { @@ -506,10 +379,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); )) { @@ -532,12 +401,16 @@ static UniValue ProTxRegister(const JSONRPCRequest& request, bool fSignAndSend) // make sure fee calculation works pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); - FundSpecialTx(pwallet, tx, pl); + CMutableTransaction tx; + tx.nVersion = CTransaction::TxVersion::SAPLING; + tx.nType = CTransaction::TxType::PROREG; + 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); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); + return tx.GetHash().GetHex(); } // external signing with collateral key pl.vchSig.clear(); @@ -606,7 +479,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), RPC_VERIFY_REJECTED); + return tx.GetHash().GetHex(); } UniValue protx_register_fund(const JSONRPCRequest& request) @@ -656,7 +530,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) { @@ -668,7 +542,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), RPC_VERIFY_REJECTED); + return tx.GetHash().GetHex(); } #endif //ENABLE_WALLET @@ -720,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); @@ -887,10 +762,11 @@ UniValue protx_update_service(const JSONRPCRequest& request) tx.nVersion = CTransaction::TxVersion::SAPLING; tx.nType = CTransaction::TxType::PROUPSERV; - FundSpecialTx(pwallet, tx, pl); - SignSpecialTxPayloadByHash(tx, pl, operatorKey); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); + return tx.GetHash().GetHex(); } UniValue protx_update_registrar(const JSONRPCRequest& request) @@ -961,10 +837,11 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) // make sure fee calculation works pl.vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); - FundSpecialTx(pwallet, tx, pl); - SignSpecialTxPayloadByHash(tx, pl, ownerKey); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, ownerKey)); - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); + return tx.GetHash().GetHex(); } UniValue protx_revoke(const JSONRPCRequest& request) @@ -1027,10 +904,11 @@ UniValue protx_revoke(const JSONRPCRequest& request) tx.nVersion = CTransaction::TxVersion::SAPLING; tx.nType = CTransaction::TxType::PROUPREV; - FundSpecialTx(pwallet, tx, pl); - SignSpecialTxPayloadByHash(tx, pl, operatorKey); + CheckOpResult(FundSpecialTx(pwallet, tx, pl)); + CheckOpResult(SignSpecialTxPayloadByHash(tx, pl, operatorKey)); - return SignAndSendSpecialTx(pwallet, tx, pl); + CheckOpResult(SignAndSendSpecialTx(pwallet, tx, pl), RPC_VERIFY_REJECTED); + return tx.GetHash().GetHex(); } #endif 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 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()); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c430877bfa44d..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: @@ -2906,6 +2914,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 +3240,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 +3247,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..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. @@ -1056,6 +1059,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 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,