diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 4f969eed2194a..dc15d7fa59197 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -32,9 +32,9 @@ std::string CDeterministicMNState::ToString() const operatorRewardAddress = CBitcoinAddress(dest).ToString(); } - return strprintf("CDeterministicMNState(nRegisteredHeight=%d, nLastPaidHeight=%d, nPoSePenalty=%d, nPoSeRevivedHeight=%d, nPoSeBanHeight=%d, " + return strprintf("CDeterministicMNState(nRegisteredHeight=%d, nLastPaidHeight=%d, nPoSePenalty=%d, nPoSeRevivedHeight=%d, nPoSeBanHeight=%d, nRevocationReason=%d, " "keyIDOwner=%s, keyIDOperator=%s, keyIDVoting=%s, addr=%s, nProtocolVersion=%d, payoutAddress=%s, operatorRewardAddress=%s)", - nRegisteredHeight, nLastPaidHeight, nPoSePenalty, nPoSeRevivedHeight, nPoSeBanHeight, + nRegisteredHeight, nLastPaidHeight, nPoSePenalty, nPoSeRevivedHeight, nPoSeBanHeight, nRevocationReason, keyIDOwner.ToString(), keyIDOperator.ToString(), keyIDVoting.ToString(), addr.ToStringIPPort(false), nProtocolVersion, payoutAddress, operatorRewardAddress); } @@ -47,6 +47,7 @@ void CDeterministicMNState::ToJson(UniValue& obj) const obj.push_back(Pair("PoSePenalty", nPoSePenalty)); obj.push_back(Pair("PoSeRevivedHeight", nPoSeRevivedHeight)); obj.push_back(Pair("PoSeBanHeight", nPoSeBanHeight)); + obj.push_back(Pair("revocationReason", nRevocationReason)); obj.push_back(Pair("keyIDOwner", keyIDOwner.ToString())); obj.push_back(Pair("keyIDOperator", keyIDOperator.ToString())); obj.push_back(Pair("keyIDVoting", keyIDVoting.ToString())); @@ -459,6 +460,25 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C LogPrintf("CDeterministicMNManager::%s -- MN %s updated at height %d: %s\n", __func__, proTx.proTxHash.ToString(), nHeight, proTx.ToString()); + } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REVOKE) { + CProUpRevTx proTx; + if (!GetTxPayload(tx, proTx)) { + assert(false); // this should have been handled already + } + + CDeterministicMNCPtr dmn = newList.GetMN(proTx.proTxHash); + if (!dmn) { + return _state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); + } + auto newState = std::make_shared(*dmn->pdmnState); + newState->ResetOperatorFields(); + newState->BanIfNotBanned(nHeight); + newState->nRevocationReason = proTx.nReason; + + newList.UpdateMN(proTx.proTxHash, newState); + + LogPrintf("CDeterministicMNManager::%s -- MN %s revoked operator key at height %d: %s\n", + __func__, proTx.proTxHash.ToString(), nHeight, proTx.ToString()); } } diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index a841df7f50cc3..a5758b218c05c 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -30,6 +30,7 @@ class CDeterministicMNState int nPoSePenalty{0}; int nPoSeRevivedHeight{-1}; int nPoSeBanHeight{-1}; + uint16_t nRevocationReason{CProUpRevTx::REASON_NOT_SPECIFIED}; CKeyID keyIDOwner; CKeyID keyIDOperator; @@ -63,6 +64,7 @@ class CDeterministicMNState READWRITE(nPoSePenalty); READWRITE(nPoSeRevivedHeight); READWRITE(nPoSeBanHeight); + READWRITE(nRevocationReason); READWRITE(keyIDOwner); READWRITE(keyIDOperator); READWRITE(keyIDVoting); @@ -78,7 +80,7 @@ class CDeterministicMNState addr = CService(); nProtocolVersion = 0; scriptOperatorPayout = CScript(); - revocationReason = CProUpRevTx::REASON_NOT_SPECIFIED; + nRevocationReason = CProUpRevTx::REASON_NOT_SPECIFIED; } void BanIfNotBanned(int height) { @@ -94,6 +96,7 @@ class CDeterministicMNState nPoSePenalty == rhs.nPoSePenalty && nPoSeRevivedHeight == rhs.nPoSeRevivedHeight && nPoSeBanHeight == rhs.nPoSeBanHeight && + nRevocationReason == rhs.nRevocationReason && keyIDOwner == rhs.keyIDOwner && keyIDOperator == rhs.keyIDOperator && keyIDVoting == rhs.keyIDVoting && diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 8b2a47dcbdb07..8c35fc7bcf3a5 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -217,6 +217,33 @@ bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVal return true; } +bool CheckProUpRevTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) +{ + AssertLockHeld(cs_main); + + CProUpRevTx ptx; + if (!GetTxPayload(tx, ptx)) + return state.DoS(100, false, REJECT_INVALID, "bad-tx-payload"); + + if (ptx.nVersion > CProRegTx::CURRENT_VERSION) + return state.DoS(100, false, REJECT_INVALID, "bad-protx-version"); + + if (ptx.nReason < CProUpRevTx::REASON_NOT_SPECIFIED || ptx.nReason > CProUpRevTx::REASON_LAST) + return state.DoS(100, false, REJECT_INVALID, "bad-protx-reason"); + + if (pindexPrev) { + auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); + auto dmn = mnList.GetMN(ptx.proTxHash); + if (!dmn) + return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); + + if (!CheckInputsHashAndSig(tx, ptx, dmn->pdmnState->keyIDOperator, state)) + return false; + } + + return true; +} + std::string CProRegTx::ToString() const { CTxDestination dest; @@ -307,6 +334,22 @@ void CProUpRegTx::ToJson(UniValue& obj) const obj.push_back(Pair("inputsHash", inputsHash.ToString())); } +std::string CProUpRevTx::ToString() const +{ + return strprintf("CProUpRevTx(nVersion=%d, proTxHash=%s, nReason=%d)", + nVersion, proTxHash.ToString(), nReason); +} + +void CProUpRevTx::ToJson(UniValue& obj) const +{ + obj.clear(); + obj.setObject(); + obj.push_back(Pair("version", nVersion)); + obj.push_back(Pair("proTxHash", proTxHash.ToString())); + obj.push_back(Pair("reason", (int)nReason)); + obj.push_back(Pair("inputsHash", inputsHash.ToString())); +} + bool IsProTxCollateral(const CTransaction& tx, uint32_t n) { return GetProTxCollateralIndex(tx) == n; diff --git a/src/evo/providertx.h b/src/evo/providertx.h index 085706d8137a8..1aa0f1729dede 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -129,10 +129,52 @@ class CProUpRegTx void ToJson(UniValue& obj) const; }; +class CProUpRevTx +{ +public: + static const uint16_t CURRENT_VERSION = 1; + + // these are just informational and do not have any effect on the revocation + enum { + REASON_NOT_SPECIFIED = 0, + REASON_TERMINATION_OF_SERVICE = 1, + REASON_COMPROMISED_KEYS = 2, + REASON_CHANGE_OF_KEYS = 3, + REASON_LAST = REASON_CHANGE_OF_KEYS + }; + +public: + uint16_t nVersion{CURRENT_VERSION}; // message version + uint256 proTxHash; + uint16_t nReason{REASON_NOT_SPECIFIED}; + uint256 inputsHash; // replay protection + std::vector vchSig; + +public: + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nVersion); + READWRITE(proTxHash); + READWRITE(nReason); + READWRITE(inputsHash); + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(vchSig); + } + } + +public: + std::string ToString() const; + void ToJson(UniValue& obj) const; +}; + bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); +bool CheckProUpRevTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); bool IsProTxCollateral(const CTransaction& tx, uint32_t n); uint32_t GetProTxCollateralIndex(const CTransaction& tx); diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 473a36055dfca..eb174bdb3612e 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -31,6 +31,8 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali return CheckProUpServTx(tx, pindexPrev, state); case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: return CheckProUpRegTx(tx, pindexPrev, state); + case TRANSACTION_PROVIDER_UPDATE_REVOKE: + return CheckProUpRevTx(tx, pindexPrev, state); } return state.DoS(10, false, REJECT_INVALID, "bad-tx-type"); @@ -45,6 +47,7 @@ bool ProcessSpecialTx(const CTransaction& tx, const CBlockIndex* pindex, CValida case TRANSACTION_PROVIDER_REGISTER: case TRANSACTION_PROVIDER_UPDATE_SERVICE: case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: + case TRANSACTION_PROVIDER_UPDATE_REVOKE: return true; // handled in batches per block } @@ -60,6 +63,7 @@ bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex) case TRANSACTION_PROVIDER_REGISTER: case TRANSACTION_PROVIDER_UPDATE_SERVICE: case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: + case TRANSACTION_PROVIDER_UPDATE_REVOKE: return true; // handled in batches per block } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 248b91235c201..71ad598804116 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -17,6 +17,7 @@ enum { TRANSACTION_PROVIDER_REGISTER = 1, TRANSACTION_PROVIDER_UPDATE_SERVICE = 2, TRANSACTION_PROVIDER_UPDATE_REGISTRAR = 3, + TRANSACTION_PROVIDER_UPDATE_REVOKE = 4, }; /** An outpoint - a combination of a transaction hash and an index n into its vout */ diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 0864ffcf72482..4818338c5b7b1 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -151,6 +151,13 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) proTx.ToJson(proTxObj); entry.push_back(Pair("proUpRegTx", proTxObj)); } + } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REVOKE) { + CProUpRevTx proTx; + if (GetTxPayload(tx, proTx)) { + UniValue proTxObj; + proTx.ToJson(proTxObj); + entry.push_back(Pair("proUpRevTx", proTxObj)); + } } if (!hashBlock.IsNull()) { diff --git a/src/validation.cpp b/src/validation.cpp index 946ba0186ec2d..c38b84a6c326e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -575,7 +575,8 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, if (tx.nType != TRANSACTION_NORMAL && tx.nType != TRANSACTION_PROVIDER_REGISTER && tx.nType != TRANSACTION_PROVIDER_UPDATE_SERVICE && - tx.nType != TRANSACTION_PROVIDER_UPDATE_REGISTRAR) { + tx.nType != TRANSACTION_PROVIDER_UPDATE_REGISTRAR && + tx.nType != TRANSACTION_PROVIDER_UPDATE_REVOKE) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); } if (tx.IsCoinBase() && tx.nType != TRANSACTION_NORMAL)