From 92576b2f9adc53a5887edb47ef14ba15e8937d05 Mon Sep 17 00:00:00 2001
From: Odysseas Gabrielides <odysseas.gabrielides@gmail.com>
Date: Tue, 18 Apr 2023 18:17:07 +0300
Subject: [PATCH 1/3] protx listdiff rpc

---
 src/evo/dmnstate.cpp | 62 +++++++++++++++++++++++++++++
 src/evo/dmnstate.h   |  2 +
 src/rpc/evo.cpp      | 94 +++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 156 insertions(+), 2 deletions(-)

diff --git a/src/evo/dmnstate.cpp b/src/evo/dmnstate.cpp
index 7cbdf373aa2a..94df628a8a74 100644
--- a/src/evo/dmnstate.cpp
+++ b/src/evo/dmnstate.cpp
@@ -61,3 +61,65 @@ void CDeterministicMNState::ToJson(UniValue& obj, MnType nType) const
         obj.pushKV("operatorPayoutAddress", EncodeDestination(dest));
     }
 }
+
+void CDeterministicMNStateDiff::ToJson(UniValue& obj, MnType nType) const
+{
+    obj.clear();
+    obj.setObject();
+    if (fields & Field_addr) {
+        obj.pushKV("service", state.addr.ToStringIPPort(false));
+    }
+    if (fields & Field_nRegisteredHeight) {
+        obj.pushKV("registeredHeight", state.nRegisteredHeight);
+    }
+    if (fields & Field_nLastPaidHeight) {
+        obj.pushKV("lastPaidHeight", state.nLastPaidHeight);
+    }
+    if (fields & Field_nConsecutivePayments) {
+        obj.pushKV("consecutivePayments", state.nConsecutivePayments);
+    }
+    if (fields & Field_nPoSePenalty) {
+        obj.pushKV("PoSePenalty", state.nPoSePenalty);
+    }
+    if (fields & Field_nPoSeRevivedHeight) {
+        obj.pushKV("PoSeRevivedHeight", state.nPoSeRevivedHeight);
+    }
+    if (fields & Field_nPoSeBanHeight) {
+        obj.pushKV("PoSeBanHeight", state.nPoSeBanHeight);
+    }
+    if (fields & Field_nRevocationReason) {
+        obj.pushKV("revocationReason", state.nRevocationReason);
+    }
+    if (fields & Field_keyIDOwner) {
+        obj.pushKV("ownerAddress", EncodeDestination(PKHash(state.keyIDOwner)));
+    }
+    if (fields & Field_keyIDVoting) {
+        obj.pushKV("votingAddress", EncodeDestination(PKHash(state.keyIDVoting)));
+    }
+    if (fields & Field_scriptPayout) {
+        CTxDestination dest;
+        if (ExtractDestination(state.scriptPayout, dest)) {
+            obj.pushKV("payoutAddress", EncodeDestination(dest));
+        }
+    }
+    if (fields & Field_scriptOperatorPayout) {
+        CTxDestination dest;
+        if (ExtractDestination(state.scriptOperatorPayout, dest)) {
+            obj.pushKV("operatorPayoutAddress", EncodeDestination(dest));
+        }
+    }
+    if (fields & Field_pubKeyOperator) {
+        obj.pushKV("pubKeyOperator", state.pubKeyOperator.Get().ToString());
+    }
+    if (nType == MnType::HighPerformance) {
+        if (fields & Field_platformNodeID) {
+            obj.pushKV("platformNodeID", state.platformNodeID.ToString());
+        }
+        if (fields & Field_platformP2PPort) {
+            obj.pushKV("platformP2PPort", state.platformP2PPort);
+        }
+        if (fields & Field_platformHTTPPort) {
+            obj.pushKV("platformHTTPPort", state.platformHTTPPort);
+        }
+    }
+}
diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h
index 9f3aa5208412..d000171ff974 100644
--- a/src/evo/dmnstate.h
+++ b/src/evo/dmnstate.h
@@ -261,6 +261,8 @@ class CDeterministicMNStateDiff
 #undef DMN_STATE_DIFF_LINE
     }
 
+    void ToJson(UniValue& obj, MnType nType) const;
+
     SERIALIZE_METHODS(CDeterministicMNStateDiff, obj)
     {
         READWRITE(VARINT(obj.fields));
diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp
index 66420c870810..cfa5b39bee15 100644
--- a/src/rpc/evo.cpp
+++ b/src/rpc/evo.cpp
@@ -1467,6 +1467,92 @@ static UniValue protx_diff(const JSONRPCRequest& request)
     return ret;
 }
 
+static void protx_listdiff_help(const JSONRPCRequest& request)
+{
+    RPCHelpMan{"protx listdiff",
+               "\nCalculate a full MN list diff between two masternode lists.\n",
+               {
+                       {"baseBlock", RPCArg::Type::NUM, RPCArg::Optional::NO, "The starting block height."},
+                       {"block", RPCArg::Type::NUM, RPCArg::Optional::NO, "The ending block height."},
+               },
+               RPCResults{},
+               RPCExamples{""},
+    }.Check(request);
+}
+
+static const CBlockIndex* ParseBlockIndex(const UniValue& v, std::string strName) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+{
+    AssertLockHeld(cs_main);
+
+    try {
+        uint256 hash = ParseHashV(v, strName);
+        const CBlockIndex* pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
+        if (!pblockindex)
+            throw std::runtime_error(strprintf("Block with hash not found %s", strName, v.getValStr()));
+        return pblockindex;
+    } catch (...) {
+        int h = ParseInt32V(v, strName);
+        if (h < 1 || h > ::ChainActive().Height())
+            throw std::runtime_error(strprintf("%s must be a chain height and not %s", strName, v.getValStr()));
+        return ::ChainActive()[h];
+    }
+}
+
+static UniValue protx_listdiff(const JSONRPCRequest& request)
+{
+    protx_listdiff_help(request);
+
+    LOCK(cs_main);
+    UniValue ret(UniValue::VOBJ);
+
+    const CBlockIndex* pBaseBlockIndex = ParseBlockIndex(request.params[0], "baseBlock");
+    const CBlockIndex* pTargetBlockIndex = ParseBlockIndex(request.params[1], "block");
+
+    if (pBaseBlockIndex == nullptr) {
+        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Base block not found");
+    }
+
+    if (pTargetBlockIndex == nullptr) {
+        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
+    }
+
+    ret.pushKV("baseHeight", pBaseBlockIndex->nHeight);
+    ret.pushKV("blockHeight", pTargetBlockIndex->nHeight);
+
+    auto baseBlockMNList = deterministicMNManager->GetListForBlock(pBaseBlockIndex);
+    auto blockMNList = deterministicMNManager->GetListForBlock(pTargetBlockIndex);
+
+    auto mnDiff = baseBlockMNList.BuildDiff(blockMNList);
+
+    UniValue jaddedMNs(UniValue::VARR);
+    for(const auto& mn : mnDiff.addedMNs) {
+        UniValue obj;
+        mn->ToJson(obj);
+        jaddedMNs.push_back(obj);
+    }
+    ret.pushKV("addedMNs", jaddedMNs);
+
+    UniValue jremovedMNs(UniValue::VARR);
+    for(const auto& internal_id : mnDiff.removedMns) {
+        auto dmn = baseBlockMNList.GetMNByInternalId(internal_id);
+        jremovedMNs.push_back(dmn->proTxHash.ToString());
+    }
+    ret.pushKV("removedMNs", jremovedMNs);
+
+    UniValue jupdatedMNs(UniValue::VARR);
+    for(const auto& [internal_id, stateDiff] : mnDiff.updatedMNs) {
+        auto dmn = baseBlockMNList.GetMNByInternalId(internal_id);
+        UniValue s(UniValue::VOBJ);
+        stateDiff.ToJson(s, dmn->nType);
+        UniValue obj(UniValue::VOBJ);
+        obj.pushKV(dmn->proTxHash.ToString(), s);
+        jupdatedMNs.push_back(obj);
+    }
+    ret.pushKV("updatedMNs", jupdatedMNs);
+
+    return ret;
+}
+
 [[ noreturn ]] static void protx_help()
 {
     RPCHelpMan{
@@ -1495,7 +1581,8 @@ static UniValue protx_diff(const JSONRPCRequest& request)
         "  update_registrar_legacy  - Create ProUpRegTx by parsing BLS using the legacy scheme, then send it to network\n"
         "  revoke                   - Create and send ProUpRevTx to network\n"
 #endif
-        "  diff                     - Calculate a diff and a proof between two masternode lists\n",
+        "  diff                     - Calculate a diff and a proof between two masternode lists\n"
+        "  listdiff                 - Calculate a full MN list diff between two masternode lists\n",
         {
             {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
         },
@@ -1536,7 +1623,10 @@ static UniValue protx(const JSONRPCRequest& request)
         return protx_info(new_request);
     } else if (command == "protxdiff") {
         return protx_diff(new_request);
-    } else {
+    } else if (command == "protxlistdiff") {
+        return protx_listdiff(new_request);
+    }
+    else {
         protx_help();
     }
 }

From f3e291560c1f30d4d200de0e12f4362df6c0fd0b Mon Sep 17 00:00:00 2001
From: Odysseas Gabrielides <odysseas.gabrielides@gmail.com>
Date: Tue, 18 Apr 2023 18:19:20 +0300
Subject: [PATCH 2/3] Create release-notes-5338.md

---
 doc/release-notes-5338.md | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 doc/release-notes-5338.md

diff --git a/doc/release-notes-5338.md b/doc/release-notes-5338.md
new file mode 100644
index 000000000000..ba532311b8bb
--- /dev/null
+++ b/doc/release-notes-5338.md
@@ -0,0 +1,4 @@
+Added RPCs
+--------
+
+- `protx listdiff` RPC returns a full deterministic masternode list diff between two heigts.
\ No newline at end of file

From 4af77e4bfdc78067e3abad721fa88772c766e666 Mon Sep 17 00:00:00 2001
From: Odysseas Gabrielides <odysseas.gabrielides@gmail.com>
Date: Tue, 18 Apr 2023 19:50:54 +0300
Subject: [PATCH 3/3] linter fix

---
 src/rpc/evo.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp
index cfa5b39bee15..aa2bd82fd439 100644
--- a/src/rpc/evo.cpp
+++ b/src/rpc/evo.cpp
@@ -1488,7 +1488,7 @@ static const CBlockIndex* ParseBlockIndex(const UniValue& v, std::string strName
         uint256 hash = ParseHashV(v, strName);
         const CBlockIndex* pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
         if (!pblockindex)
-            throw std::runtime_error(strprintf("Block with hash not found %s", strName, v.getValStr()));
+            throw std::runtime_error(strprintf("Block %s with hash %s not found", strName, v.getValStr()));
         return pblockindex;
     } catch (...) {
         int h = ParseInt32V(v, strName);
