// Copyright (c) 2014-2017 The Dash Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "governance-vote.h" #include "governance-object.h" #include "masternode-sync.h" #include "masternodeman.h" #include "messagesigner.h" #include "util.h" std::string CGovernanceVoting::ConvertOutcomeToString(vote_outcome_enum_t nOutcome) { switch(nOutcome) { case VOTE_OUTCOME_NONE: return "NONE"; break; case VOTE_OUTCOME_YES: return "YES"; break; case VOTE_OUTCOME_NO: return "NO"; break; case VOTE_OUTCOME_ABSTAIN: return "ABSTAIN"; break; } return "error"; } std::string CGovernanceVoting::ConvertSignalToString(vote_signal_enum_t nSignal) { std::string strReturn = "NONE"; switch(nSignal) { case VOTE_SIGNAL_NONE: strReturn = "NONE"; break; case VOTE_SIGNAL_FUNDING: strReturn = "FUNDING"; break; case VOTE_SIGNAL_VALID: strReturn = "VALID"; break; case VOTE_SIGNAL_DELETE: strReturn = "DELETE"; break; case VOTE_SIGNAL_ENDORSED: strReturn = "ENDORSED"; break; } return strReturn; } vote_outcome_enum_t CGovernanceVoting::ConvertVoteOutcome(const std::string& strVoteOutcome) { vote_outcome_enum_t eVote = VOTE_OUTCOME_NONE; if(strVoteOutcome == "yes") { eVote = VOTE_OUTCOME_YES; } else if(strVoteOutcome == "no") { eVote = VOTE_OUTCOME_NO; } else if(strVoteOutcome == "abstain") { eVote = VOTE_OUTCOME_ABSTAIN; } return eVote; } vote_signal_enum_t CGovernanceVoting::ConvertVoteSignal(const std::string& strVoteSignal) { static const std::map mapStrVoteSignals = { {"funding", VOTE_SIGNAL_FUNDING}, {"valid", VOTE_SIGNAL_VALID}, {"delete", VOTE_SIGNAL_DELETE}, {"endorsed", VOTE_SIGNAL_ENDORSED} }; const auto& it = mapStrVoteSignals.find(strVoteSignal); if (it == mapStrVoteSignals.end()) { LogPrintf("CGovernanceVoting::%s -- ERROR: Unknown signal %s\n", __func__, strVoteSignal); return VOTE_SIGNAL_NONE; } return it->second; } CGovernanceVote::CGovernanceVote() : fValid(true), fSynced(false), nVoteSignal(int(VOTE_SIGNAL_NONE)), masternodeOutpoint(), nParentHash(), nVoteOutcome(int(VOTE_OUTCOME_NONE)), nTime(0), vchSig() {} CGovernanceVote::CGovernanceVote(const COutPoint& outpointMasternodeIn, const uint256& nParentHashIn, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) : fValid(true), fSynced(false), nVoteSignal(eVoteSignalIn), masternodeOutpoint(outpointMasternodeIn), nParentHash(nParentHashIn), nVoteOutcome(eVoteOutcomeIn), nTime(GetAdjustedTime()), vchSig() { UpdateHash(); } std::string CGovernanceVote::ToString() const { std::ostringstream ostr; ostr << masternodeOutpoint.ToStringShort() << ":" << nTime << ":" << CGovernanceVoting::ConvertOutcomeToString(GetOutcome()) << ":" << CGovernanceVoting::ConvertSignalToString(GetSignal()); return ostr.str(); } void CGovernanceVote::Relay(CConnman& connman) const { // Do not relay until fully synced if(!masternodeSync.IsSynced()) { LogPrint("gobject", "CGovernanceVote::Relay -- won't relay until fully synced\n"); return; } CInv inv(MSG_GOVERNANCE_OBJECT_VOTE, GetHash()); connman.RelayInv(inv, MIN_GOVERNANCE_PEER_PROTO_VERSION); } void CGovernanceVote::UpdateHash() const { // Note: doesn't match serialization CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << masternodeOutpoint << uint8_t{} << 0xffffffff; // adding dummy values here to match old hashing format ss << nParentHash; ss << nVoteSignal; ss << nVoteOutcome; ss << nTime; *const_cast(&hash) = ss.GetHash(); } uint256 CGovernanceVote::GetHash() const { return hash; } uint256 CGovernanceVote::GetSignatureHash() const { return SerializeHash(*this); } bool CGovernanceVote::Sign(const CKey& key, const CKeyID& keyID) { std::string strError; if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if(!CHashSigner::SignHash(hash, key, vchSig)) { LogPrintf("CGovernanceVote::Sign -- SignHash() failed\n"); return false; } if (!CHashSigner::VerifyHash(hash, keyID, vchSig, strError)) { LogPrintf("CGovernanceVote::Sign -- VerifyHash() failed, error: %s\n", strError); return false; } } else { std::string strMessage = masternodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + std::to_string(nVoteSignal) + "|" + std::to_string(nVoteOutcome) + "|" + std::to_string(nTime); if(!CMessageSigner::SignMessage(strMessage, vchSig, key)) { LogPrintf("CGovernanceVote::Sign -- SignMessage() failed\n"); return false; } if(!CMessageSigner::VerifyMessage(keyID, vchSig, strMessage, strError)) { LogPrintf("CGovernanceVote::Sign -- VerifyMessage() failed, error: %s\n", strError); return false; } } return true; } bool CGovernanceVote::CheckSignature(const CKeyID& keyID) const { std::string strError; if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if (!CHashSigner::VerifyHash(hash, keyID, vchSig, strError)) { // could be a signature in old format std::string strMessage = masternodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + std::to_string(nVoteSignal) + "|" + std::to_string(nVoteOutcome) + "|" + std::to_string(nTime); if(!CMessageSigner::VerifyMessage(keyID, vchSig, strMessage, strError)) { // nope, not in old format either LogPrint("gobject", "CGovernanceVote::IsValid -- VerifyMessage() failed, error: %s\n", strError); return false; } } } else { std::string strMessage = masternodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + std::to_string(nVoteSignal) + "|" + std::to_string(nVoteOutcome) + "|" + std::to_string(nTime); if(!CMessageSigner::VerifyMessage(keyID, vchSig, strMessage, strError)) { LogPrint("gobject", "CGovernanceVote::IsValid -- VerifyMessage() failed, error: %s\n", strError); return false; } } return true; } bool CGovernanceVote::IsValid(bool useVotingKey) const { if(nTime > GetAdjustedTime() + (60*60)) { LogPrint("gobject", "CGovernanceVote::IsValid -- vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", GetHash().ToString(), nTime, GetAdjustedTime() + (60*60)); return false; } // support up to MAX_SUPPORTED_VOTE_SIGNAL, can be extended if(nVoteSignal > MAX_SUPPORTED_VOTE_SIGNAL) { LogPrint("gobject", "CGovernanceVote::IsValid -- Client attempted to vote on invalid signal(%d) - %s\n", nVoteSignal, GetHash().ToString()); return false; } // 0=none, 1=yes, 2=no, 3=abstain. Beyond that reject votes if(nVoteOutcome > 3) { LogPrint("gobject", "CGovernanceVote::IsValid -- Client attempted to vote on invalid outcome(%d) - %s\n", nVoteSignal, GetHash().ToString()); return false; } masternode_info_t infoMn; if(!mnodeman.GetMasternodeInfo(masternodeOutpoint, infoMn)) { LogPrint("gobject", "CGovernanceVote::IsValid -- Unknown Masternode - %s\n", masternodeOutpoint.ToStringShort()); return false; } return CheckSignature(useVotingKey ? infoMn.keyIDVoting : infoMn.keyIDOperator); } bool operator==(const CGovernanceVote& vote1, const CGovernanceVote& vote2) { bool fResult = ((vote1.masternodeOutpoint == vote2.masternodeOutpoint) && (vote1.nParentHash == vote2.nParentHash) && (vote1.nVoteOutcome == vote2.nVoteOutcome) && (vote1.nVoteSignal == vote2.nVoteSignal) && (vote1.nTime == vote2.nTime)); return fResult; } bool operator<(const CGovernanceVote& vote1, const CGovernanceVote& vote2) { bool fResult = (vote1.masternodeOutpoint < vote2.masternodeOutpoint); if(!fResult) { return false; } fResult = (vote1.masternodeOutpoint == vote2.masternodeOutpoint); fResult = fResult && (vote1.nParentHash < vote2.nParentHash); if(!fResult) { return false; } fResult = fResult && (vote1.nParentHash == vote2.nParentHash); fResult = fResult && (vote1.nVoteOutcome < vote2.nVoteOutcome); if(!fResult) { return false; } fResult = fResult && (vote1.nVoteOutcome == vote2.nVoteOutcome); fResult = fResult && (vote1.nVoteSignal == vote2.nVoteSignal); if(!fResult) { return false; } fResult = fResult && (vote1.nVoteSignal == vote2.nVoteSignal); fResult = fResult && (vote1.nTime < vote2.nTime); return fResult; }