From e61967859c103b67b96e2dd99a04e88d74a29865 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Wed, 17 May 2023 20:56:45 +0300 Subject: [PATCH 01/20] quorums CL sigs for SPV clients --- src/evo/simplifiedmns.cpp | 81 ++++++++++++++++++++++ src/evo/simplifiedmns.h | 17 +++++ src/version.h | 7 +- test/functional/feature_llmq_rotation.py | 21 ++++-- test/functional/test_framework/messages.py | 14 +++- 5 files changed, 130 insertions(+), 10 deletions(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 73e6af05c852..66ec52aee9a9 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -6,9 +6,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -178,6 +180,66 @@ bool CSimplifiedMNListDiff::BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, newQuorums.emplace_back(*qc); } } + + return true; +} + +bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const CBlockIndex* blockIndex) +{ + // Group quorums (indexes corresponding to entries of newQuorums) per CBlockIndex containing the expected CL signature in CbTx. + // We want to avoid to load CbTx now, as more than one quorum will target the same block: hence we want to load CbTxs once per block (heavy operation). + std::multimap workBaseBlockIndexMap; + + uint16_t idx = 0; + for (const auto& e : newQuorums) { + auto opt_params = llmq::GetLLMQParams(e.llmqType); + assert(opt_params.has_value()); + Consensus::LLMQParams llmq_params = opt_params.value(); + auto q = llmq::quorumManager->GetQuorum(e.llmqType, e.quorumHash); + const CBlockIndex* pWorkBaseBlockIndex = nullptr; + if (llmq_params.useRotation) { + // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the first DKG) - 8 + pWorkBaseBlockIndex = blockIndex->GetAncestor(q->m_quorum_base_block_index->nHeight - q->qc->quorumIndex - 8); + } + else { + // In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 + pWorkBaseBlockIndex = blockIndex->GetAncestor(q->m_quorum_base_block_index->nHeight - 8); + } + + workBaseBlockIndexMap.insert(std::make_pair(pWorkBaseBlockIndex, idx)); + idx++; + } + + auto it = workBaseBlockIndexMap.begin(); + while (it != workBaseBlockIndexMap.end()) { + // Iterate the std::multimap once per key (CBlockIndex containing the expected CL signature in CbTx) + const CBlockIndex* pWorkBaseBlockIndex = it->first; + auto cbcl = GetNonNullCoinbaseChainlock(pWorkBaseBlockIndex); + CBLSSignature sig; + if (cbcl.has_value()) { + sig = cbcl.value().first; + } + // Get the range of indexes (values) for the current key and merge them into a single std::set + auto range = workBaseBlockIndexMap.equal_range(pWorkBaseBlockIndex); + std::set idx_set; + for (auto i = range.first; i != range.second; ++i) { + idx_set.insert(i->second); + } + + // Different CBlockIndex can contain the same CL sig in CbTx (both non-null or null during the first blocks after v20 activation) + // Hence, we need to merge the std::set if another std::set already exists for the same sig. + auto it_sig = quorumsCLSigs.find(sig); + if (it_sig != quorumsCLSigs.end()) { + it_sig->second.insert(idx_set.begin(), idx_set.end()); + } + else { + quorumsCLSigs.insert(std::make_pair(sig, idx_set)); + } + + // Advance the iterator to the next key + it = range.second; + } + return true; } @@ -233,6 +295,18 @@ void CSimplifiedMNListDiff::ToJson(UniValue& obj, bool extended) const obj.pushKV("merkleRootQuorums", cbTxPayload.merkleRootQuorums.ToString()); } } + + UniValue quorumsCLSigsArr(UniValue::VARR); + for (const auto& [signature, quorumsIndexes] : quorumsCLSigs) { + UniValue obj(UniValue::VOBJ); + UniValue idxArr(UniValue::VARR); + for (const auto& idx : quorumsIndexes) { + idxArr.push_back(idx); + } + obj.pushKV(signature.ToString(),idxArr); + quorumsCLSigsArr.push_back(obj); + } + obj.pushKV("quorumsCLSigs", quorumsCLSigsArr); } CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, const CDeterministicMNList& to, bool extended) @@ -310,6 +384,13 @@ bool BuildSimplifiedMNListDiff(const uint256& baseBlockHash, const uint256& bloc return false; } + if (llmq::utils::IsV20Active(blockIndex)) { + if (!mnListDiffRet.BuildQuorumChainlockInfo(blockIndex)) { + errorRet = strprintf("failed to build quorums chainlocks info"); + return false; + } + } + // TODO store coinbase TX in CBlockIndex CBlock block; if (!ReadBlockFromDisk(block, blockIndex, Params().GetConsensus())) { diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index 8becbee12137..223a7500e09b 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -123,6 +123,15 @@ class CGetSimplifiedMNListDiff class CSimplifiedMNListDiff { +private: + struct BLSSignatureCompare { + // CBLSSignature doesn't support operator< needed for std::map quorumsCLSigs + // Hence we compare instead the hash (uint256) of each CBLSSignature + // Hash of each CBLSSignature should already be calculated and cached: no perf penalty + bool operator() (const CBLSSignature& a, const CBLSSignature& b) const { + return a.GetHash() < b.GetHash(); + } + }; public: static constexpr uint16_t CURRENT_VERSION = 1; @@ -137,6 +146,10 @@ class CSimplifiedMNListDiff std::vector> deletedQuorums; // p std::vector newQuorums; + // Map of Chainlock Signature used for shuffling per set of quorums + // The set of quorums is the set of indexes corresponding to entries in newQuorums + std::map, BLSSignatureCompare> quorumsCLSigs; + SERIALIZE_METHODS(CSimplifiedMNListDiff, obj) { if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= MNLISTDIFF_VERSION_ORDER) { @@ -148,6 +161,9 @@ class CSimplifiedMNListDiff } READWRITE(obj.deletedMNs, obj.mnList); READWRITE(obj.deletedQuorums, obj.newQuorums); + if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= MNLISTDIFF_CHAINLOCKS_PROTO_VERSION) { + READWRITE(obj.quorumsCLSigs); + } } CSimplifiedMNListDiff(); @@ -155,6 +171,7 @@ class CSimplifiedMNListDiff bool BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex, const llmq::CQuorumBlockProcessor& quorum_block_processor); + bool BuildQuorumChainlockInfo(const CBlockIndex* blockIndex); void ToJson(UniValue& obj, bool extended = false) const; }; diff --git a/src/version.h b/src/version.h index e543137892ad..4944d9c05743 100644 --- a/src/version.h +++ b/src/version.h @@ -9,9 +9,7 @@ /** * network protocol versioning */ - - -static const int PROTOCOL_VERSION = 70229; +static const int PROTOCOL_VERSION = 70230; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; @@ -55,6 +53,9 @@ static const int SMNLE_VERSIONED_PROTO_VERSION = 70228; //! Versioned Simplified Masternode List Entries were introduced in this version static const int MNLISTDIFF_VERSION_ORDER = 70229; +//! Masternode type was introduced in this version +static const int MNLISTDIFF_CHAINLOCKS_PROTO_VERSION = 70230; + // Make sure that none of the values above collide with `ADDRV2_FORMAT`. #endif // BITCOIN_VERSION_H diff --git a/test/functional/feature_llmq_rotation.py b/test/functional/feature_llmq_rotation.py index 887f264996ae..4dccb4a2cc96 100755 --- a/test/functional/feature_llmq_rotation.py +++ b/test/functional/feature_llmq_rotation.py @@ -94,7 +94,7 @@ def run_test(self): expectedDeleted = [] expectedNew = [h_100_0, h_106_0, h_104_0, h_100_1, h_106_1, h_104_1] - quorumList = self.test_getmnlistdiff_quorums(b_h_0, b_h_1, {}, expectedDeleted, expectedNew) + quorumList = self.test_getmnlistdiff_quorums(b_h_0, b_h_1, {}, expectedDeleted, expectedNew, testQuorumsCLSigs=False) self.activate_v20(expected_activation_height=1440) self.log.info("Activated v20 at height:" + str(self.nodes[0].getblockcount())) @@ -207,8 +207,8 @@ def run_test(self): wait_until(lambda: self.nodes[0].getbestblockhash() == new_quorum_blockhash, sleep=1) assert_equal(self.nodes[0].quorum("list", llmq_type), new_quorum_list) - def test_getmnlistdiff_quorums(self, baseBlockHash, blockHash, baseQuorumList, expectedDeleted, expectedNew): - d = self.test_getmnlistdiff_base(baseBlockHash, blockHash) + def test_getmnlistdiff_quorums(self, baseBlockHash, blockHash, baseQuorumList, expectedDeleted, expectedNew, testQuorumsCLSigs = True): + d = self.test_getmnlistdiff_base(baseBlockHash, blockHash, testQuorumsCLSigs) assert_equal(set(d.deletedQuorums), set(expectedDeleted)) assert_equal(set([QuorumId(e.llmqType, e.quorumHash) for e in d.newQuorums]), set(expectedNew)) @@ -235,7 +235,7 @@ def test_getmnlistdiff_quorums(self, baseBlockHash, blockHash, baseQuorumList, e return newQuorumList - def test_getmnlistdiff_base(self, baseBlockHash, blockHash): + def test_getmnlistdiff_base(self, baseBlockHash, blockHash, testQuorumsCLSigs): hexstr = self.nodes[0].getblockheader(blockHash, False) header = FromHex(CBlockHeader(), hexstr) @@ -258,7 +258,18 @@ def test_getmnlistdiff_base(self, baseBlockHash, blockHash): assert_equal(set([int(e["proRegTxHash"], 16) for e in d2["mnList"]]), set([e.proRegTxHash for e in d.mnList])) assert_equal(set([QuorumId(e["llmqType"], int(e["quorumHash"], 16)) for e in d2["deletedQuorums"]]), set(d.deletedQuorums)) assert_equal(set([QuorumId(e["llmqType"], int(e["quorumHash"], 16)) for e in d2["newQuorums"]]), set([QuorumId(e.llmqType, e.quorumHash) for e in d.newQuorums])) - + # Check if P2P quorumsCLSigs matches with the corresponding in RPC + rpc_quorums_clsigs_dict = {k: v for d in d2["quorumsCLSigs"] for k, v in d.items()} + # p2p_quorums_clsigs_dict is constructed from the P2P message so it can be easily compared to rpc_quorums_clsigs_dict + p2p_quorums_clsigs_dict = dict() + for key, value in d.quorumsCLSigs.items(): + idx_list = list(value) + p2p_quorums_clsigs_dict[key.hex()] = idx_list + assert_equal(rpc_quorums_clsigs_dict, p2p_quorums_clsigs_dict) + # The following test must be checked only after v20 activation + if testQuorumsCLSigs: + # Total number of corresponding quorum indexes in quorumsCLSigs must be equal to the total of quorums in newQuorums + assert_equal(len(d2["newQuorums"]), sum(len(value) for value in rpc_quorums_clsigs_dict.values())) return d def test_quorum_listextended(self, quorum_info, llmq_type_name): diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index ae0e7c3f725f..4f21d1a64ba5 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -32,7 +32,7 @@ import dash_hash MIN_VERSION_SUPPORTED = 60001 -MY_VERSION = 70229 # MNLISTDIFF_VERSION_ORDER +MY_VERSION = 70230 # MNLISTDIFF_CHAINLOCKS_PROTO_VERSION MY_SUBVERSION = b"/python-mininode-tester:0.0.3%s/" MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37) @@ -2023,7 +2023,7 @@ def __repr__(self): QuorumId = namedtuple('QuorumId', ['llmqType', 'quorumHash']) class msg_mnlistdiff: - __slots__ = ("baseBlockHash", "blockHash", "merkleProof", "cbTx", "nVersion", "deletedMNs", "mnList", "deletedQuorums", "newQuorums",) + __slots__ = ("baseBlockHash", "blockHash", "merkleProof", "cbTx", "nVersion", "deletedMNs", "mnList", "deletedQuorums", "newQuorums", "quorumsCLSigs") command = b"mnlistdiff" def __init__(self): @@ -2036,6 +2036,8 @@ def __init__(self): self.mnList = [] self.deletedQuorums = [] self.newQuorums = [] + self.quorumsCLSigs = {} + def deserialize(self, f): self.nVersion = struct.unpack(" Date: Wed, 17 May 2023 21:26:05 +0300 Subject: [PATCH 02/20] linter fixes --- src/evo/simplifiedmns.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 66ec52aee9a9..7baca055da84 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -298,13 +298,13 @@ void CSimplifiedMNListDiff::ToJson(UniValue& obj, bool extended) const UniValue quorumsCLSigsArr(UniValue::VARR); for (const auto& [signature, quorumsIndexes] : quorumsCLSigs) { - UniValue obj(UniValue::VOBJ); + UniValue j(UniValue::VOBJ); UniValue idxArr(UniValue::VARR); for (const auto& idx : quorumsIndexes) { idxArr.push_back(idx); } - obj.pushKV(signature.ToString(),idxArr); - quorumsCLSigsArr.push_back(obj); + j.pushKV(signature.ToString(),idxArr); + quorumsCLSigsArr.push_back(j); } obj.pushKV("quorumsCLSigs", quorumsCLSigsArr); } From 5d9ffb1e787b0a21aff923b4e442877e803c833d Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Wed, 17 May 2023 21:58:23 +0300 Subject: [PATCH 03/20] linter fixes --- src/evo/simplifiedmns.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 7baca055da84..1b90e820a6f8 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include From 2dc0cdc7f60c83a662220d91e9488309a43cc796 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Wed, 17 May 2023 23:29:40 +0300 Subject: [PATCH 04/20] Update lint-cppcheck-dash.sh --- test/lint/lint-cppcheck-dash.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lint/lint-cppcheck-dash.sh b/test/lint/lint-cppcheck-dash.sh index af1bce5cdcce..56f090acb814 100755 --- a/test/lint/lint-cppcheck-dash.sh +++ b/test/lint/lint-cppcheck-dash.sh @@ -34,7 +34,7 @@ IGNORED_WARNINGS=( "src/test/dip0020opcodes_tests.cpp:.* warning: There is an unknown macro here somewhere. Configuration is required. If BOOST_FIXTURE_TEST_SUITE is a macro then please configure it." "src/ctpl_stl.h:.*22: warning: Dereferencing '_f' after it is deallocated / released" "src/cachemultimap.h:.*: warning: Variable 'mapIt' can be declared as reference to const" - + "src/evo/simplifiedmns.cpp:304:20: warning: Consider using std::copy algorithm instead of a raw loop." # "src/llmq/snapshot.cpp:.*:17: warning: Consider using std::copy algorithm instead of a raw loop." # "src/llmq/snapshot.cpp:.*:18: warning: Consider using std::copy algorithm instead of a raw loop." From 22f8606034438a5ff0716b032669d5034baf8819 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Wed, 17 May 2023 23:34:53 +0300 Subject: [PATCH 05/20] Create release-notes-5377.md --- doc/release-notes-5377.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 doc/release-notes-5377.md diff --git a/doc/release-notes-5377.md b/doc/release-notes-5377.md new file mode 100644 index 000000000000..11d5a433a012 --- /dev/null +++ b/doc/release-notes-5377.md @@ -0,0 +1,25 @@ +Updated RPCs +-------- + +- `protx diff` RPC returns a new field `quorumsCLSigs`. +This field is a list of: Chainlock signature and the list of corresponding quorum indexes in `newQuorums`. + +`MNLISTDIFF` P2P message +-------- + +Starting with protocol version `70228`, the following fields is added to the `MNLISTDIFF` after `newQuorums`. + +| Field | Type | Size | Description | +|--------------------|-----------------------|----------|---------------------------------------------------------------------| +| quorumsCLSigsCount | compactSize uint | 1-9 | Number of quorumsCLSigs elements | +| quorumsCLSigs | quorumsCLSigsObject[] | variable | CL Sig used to calculate members per quorum indexes (in newQuorums) | + +The content of `quorumsCLSigsObject`: + +| Field | Type | Size | Description | +|---------------|------------------|----------|---------------------------------------------------------------------------------------------| +| signature | BLSSig | 96 | Chainlock signature | +| indexSetCount | compactSize uint | 1-9 | Number of quorum indexes using the same `signature` for their member calculation | +| indexSet | uint16_t[] | variable | Quorum indexes corresponding in `newQuorums` using `signature` for their member calculation | + +Note: The field `quorumsCLSigs` in both RPC and P2P will be populated only after the v20 activation. \ No newline at end of file From 574edcb32a31cd7ea48c77a1875963df491d34a2 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Wed, 17 May 2023 23:41:27 +0300 Subject: [PATCH 06/20] fix --- test/lint/lint-cppcheck-dash.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lint/lint-cppcheck-dash.sh b/test/lint/lint-cppcheck-dash.sh index 56f090acb814..3dd1aaa4a95c 100755 --- a/test/lint/lint-cppcheck-dash.sh +++ b/test/lint/lint-cppcheck-dash.sh @@ -34,7 +34,7 @@ IGNORED_WARNINGS=( "src/test/dip0020opcodes_tests.cpp:.* warning: There is an unknown macro here somewhere. Configuration is required. If BOOST_FIXTURE_TEST_SUITE is a macro then please configure it." "src/ctpl_stl.h:.*22: warning: Dereferencing '_f' after it is deallocated / released" "src/cachemultimap.h:.*: warning: Variable 'mapIt' can be declared as reference to const" - "src/evo/simplifiedmns.cpp:304:20: warning: Consider using std::copy algorithm instead of a raw loop." + "src/evo/simplifiedmns.cpp:.*:20: warning: Consider using std::copy algorithm instead of a raw loop." # "src/llmq/snapshot.cpp:.*:17: warning: Consider using std::copy algorithm instead of a raw loop." # "src/llmq/snapshot.cpp:.*:18: warning: Consider using std::copy algorithm instead of a raw loop." From 0429ad7c8cdbf12d7397fc42a1747871127dc706 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Thu, 18 May 2023 16:10:55 +0300 Subject: [PATCH 07/20] refactoring --- src/evo/simplifiedmns.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 1b90e820a6f8..eda15913bf3e 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -209,21 +209,22 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const CBlockIndex* blockInd idx++; } - auto it = workBaseBlockIndexMap.begin(); - while (it != workBaseBlockIndexMap.end()) { - // Iterate the std::multimap once per key (CBlockIndex containing the expected CL signature in CbTx) + for(auto it = workBaseBlockIndexMap.begin(); it != workBaseBlockIndexMap.end(); ) { + // Process each key (CBlockIndex containing the expected CL signature in CbTx) of the std::multimap once const CBlockIndex* pWorkBaseBlockIndex = it->first; - auto cbcl = GetNonNullCoinbaseChainlock(pWorkBaseBlockIndex); + const auto cbcl = GetNonNullCoinbaseChainlock(pWorkBaseBlockIndex); CBLSSignature sig; if (cbcl.has_value()) { sig = cbcl.value().first; } // Get the range of indexes (values) for the current key and merge them into a single std::set - auto range = workBaseBlockIndexMap.equal_range(pWorkBaseBlockIndex); + const auto range = workBaseBlockIndexMap.equal_range(it->first); std::set idx_set; - for (auto i = range.first; i != range.second; ++i) { - idx_set.insert(i->second); + for (auto jt = range.first; jt != range.second; ++jt) { + idx_set.insert(jt->second); } + // Advance the iterator to the next key + it = range.second; // Different CBlockIndex can contain the same CL sig in CbTx (both non-null or null during the first blocks after v20 activation) // Hence, we need to merge the std::set if another std::set already exists for the same sig. @@ -234,9 +235,6 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const CBlockIndex* blockInd else { quorumsCLSigs.insert(std::make_pair(sig, idx_set)); } - - // Advance the iterator to the next key - it = range.second; } return true; From 94b141a34a16a626e173ef81b6643d3c46445be9 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Thu, 18 May 2023 23:49:34 +0300 Subject: [PATCH 08/20] Apply suggestions from code review Co-authored-by: thephez --- doc/release-notes-5377.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/release-notes-5377.md b/doc/release-notes-5377.md index 11d5a433a012..8454b25bcc88 100644 --- a/doc/release-notes-5377.md +++ b/doc/release-notes-5377.md @@ -2,12 +2,12 @@ Updated RPCs -------- - `protx diff` RPC returns a new field `quorumsCLSigs`. -This field is a list of: Chainlock signature and the list of corresponding quorum indexes in `newQuorums`. +This field is a list containing: a ChainLock signature and the list of corresponding quorum indexes in `newQuorums`. `MNLISTDIFF` P2P message -------- -Starting with protocol version `70228`, the following fields is added to the `MNLISTDIFF` after `newQuorums`. +Starting with protocol version `70228`, the following fields are added to the `MNLISTDIFF` after `newQuorums`. | Field | Type | Size | Description | |--------------------|-----------------------|----------|---------------------------------------------------------------------| @@ -18,8 +18,8 @@ The content of `quorumsCLSigsObject`: | Field | Type | Size | Description | |---------------|------------------|----------|---------------------------------------------------------------------------------------------| -| signature | BLSSig | 96 | Chainlock signature | +| signature | BLSSig | 96 | ChainLock signature | | indexSetCount | compactSize uint | 1-9 | Number of quorum indexes using the same `signature` for their member calculation | | indexSet | uint16_t[] | variable | Quorum indexes corresponding in `newQuorums` using `signature` for their member calculation | -Note: The field `quorumsCLSigs` in both RPC and P2P will be populated only after the v20 activation. \ No newline at end of file +Note: The `quorumsCLSigs` field in both RPC and P2P will only be populated after the v20 activation. \ No newline at end of file From ab5814e44eae23285926f650e07993ad07ee1483 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Thu, 18 May 2023 23:52:25 +0300 Subject: [PATCH 09/20] refactoring --- src/bls/bls.h | 4 ++++ src/evo/simplifiedmns.h | 11 +---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/bls/bls.h b/src/bls/bls.h index d16914c5683c..ca8bd01889c9 100644 --- a/src/bls/bls.h +++ b/src/bls/bls.h @@ -88,6 +88,10 @@ class CBLSWrapper { return !((*this) == r); } + bool operator<(const C& r) const + { + return GetHash() < r.GetHash(); + } bool IsValid() const { diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index 223a7500e09b..0102ef760cbe 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -123,15 +123,6 @@ class CGetSimplifiedMNListDiff class CSimplifiedMNListDiff { -private: - struct BLSSignatureCompare { - // CBLSSignature doesn't support operator< needed for std::map quorumsCLSigs - // Hence we compare instead the hash (uint256) of each CBLSSignature - // Hash of each CBLSSignature should already be calculated and cached: no perf penalty - bool operator() (const CBLSSignature& a, const CBLSSignature& b) const { - return a.GetHash() < b.GetHash(); - } - }; public: static constexpr uint16_t CURRENT_VERSION = 1; @@ -148,7 +139,7 @@ class CSimplifiedMNListDiff // Map of Chainlock Signature used for shuffling per set of quorums // The set of quorums is the set of indexes corresponding to entries in newQuorums - std::map, BLSSignatureCompare> quorumsCLSigs; + std::map> quorumsCLSigs; SERIALIZE_METHODS(CSimplifiedMNListDiff, obj) { From 592ae622a7acc33596599eebae226adfaea427f2 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Fri, 16 Jun 2023 16:13:43 +0300 Subject: [PATCH 10/20] Update doc/release-notes-5377.md Co-authored-by: UdjinM6 --- doc/release-notes-5377.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes-5377.md b/doc/release-notes-5377.md index 8454b25bcc88..8c469202528f 100644 --- a/doc/release-notes-5377.md +++ b/doc/release-notes-5377.md @@ -7,7 +7,7 @@ This field is a list containing: a ChainLock signature and the list of correspon `MNLISTDIFF` P2P message -------- -Starting with protocol version `70228`, the following fields are added to the `MNLISTDIFF` after `newQuorums`. +Starting with protocol version `70229`, the following fields are added to the `MNLISTDIFF` after `newQuorums`. | Field | Type | Size | Description | |--------------------|-----------------------|----------|---------------------------------------------------------------------| From c23d259d854f1d6f3f3a3b7fe74368b2f81960e0 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 20 Jun 2023 23:00:30 -0500 Subject: [PATCH 11/20] simplify logic by using the fact that quorumIndex is 0 for non-rotated quorums --- src/evo/simplifiedmns.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index eda15913bf3e..4c112fa0f810 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -191,19 +191,11 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const CBlockIndex* blockInd uint16_t idx = 0; for (const auto& e : newQuorums) { - auto opt_params = llmq::GetLLMQParams(e.llmqType); - assert(opt_params.has_value()); - Consensus::LLMQParams llmq_params = opt_params.value(); - auto q = llmq::quorumManager->GetQuorum(e.llmqType, e.quorumHash); - const CBlockIndex* pWorkBaseBlockIndex = nullptr; - if (llmq_params.useRotation) { - // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the first DKG) - 8 - pWorkBaseBlockIndex = blockIndex->GetAncestor(q->m_quorum_base_block_index->nHeight - q->qc->quorumIndex - 8); - } - else { - // In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 - pWorkBaseBlockIndex = blockIndex->GetAncestor(q->m_quorum_base_block_index->nHeight - 8); - } + auto quorum = llmq::quorumManager->GetQuorum(e.llmqType, e.quorumHash); + // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the first DKG) - 8 + // In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 + const CBlockIndex* pWorkBaseBlockIndex = + blockIndex->GetAncestor(quorum->m_quorum_base_block_index->nHeight - quorum->qc->quorumIndex - 8); workBaseBlockIndexMap.insert(std::make_pair(pWorkBaseBlockIndex, idx)); idx++; From cdb1b8ef711d87203a97c352b6d623d67cf3141d Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 20 Jun 2023 23:02:31 -0500 Subject: [PATCH 12/20] use enumerate --- src/evo/simplifiedmns.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 4c112fa0f810..9ddb4fe96742 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -24,6 +24,7 @@ #include #include #include +#include CSimplifiedMNListEntry::CSimplifiedMNListEntry(const CDeterministicMN& dmn) : proRegTxHash(dmn.proTxHash), @@ -189,8 +190,7 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const CBlockIndex* blockInd // We want to avoid to load CbTx now, as more than one quorum will target the same block: hence we want to load CbTxs once per block (heavy operation). std::multimap workBaseBlockIndexMap; - uint16_t idx = 0; - for (const auto& e : newQuorums) { + for (const auto [idx, e] : enumerate(newQuorums)) { auto quorum = llmq::quorumManager->GetQuorum(e.llmqType, e.quorumHash); // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the first DKG) - 8 // In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 @@ -198,7 +198,6 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const CBlockIndex* blockInd blockIndex->GetAncestor(quorum->m_quorum_base_block_index->nHeight - quorum->qc->quorumIndex - 8); workBaseBlockIndexMap.insert(std::make_pair(pWorkBaseBlockIndex, idx)); - idx++; } for(auto it = workBaseBlockIndexMap.begin(); it != workBaseBlockIndexMap.end(); ) { From d2232d6877d59c83fc710a81b2fede57321c5a46 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 20 Jun 2023 23:30:41 -0500 Subject: [PATCH 13/20] refactor / modernize --- src/evo/simplifiedmns.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 9ddb4fe96742..481f77a077fb 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -209,23 +209,17 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const CBlockIndex* blockInd sig = cbcl.value().first; } // Get the range of indexes (values) for the current key and merge them into a single std::set - const auto range = workBaseBlockIndexMap.equal_range(it->first); + const auto [begin, end] = workBaseBlockIndexMap.equal_range(it->first); std::set idx_set; - for (auto jt = range.first; jt != range.second; ++jt) { - idx_set.insert(jt->second); - } + std::transform(begin, end, std::inserter(idx_set, idx_set.end()), [](const auto& pair) { return pair.second; }); // Advance the iterator to the next key - it = range.second; + it = end; // Different CBlockIndex can contain the same CL sig in CbTx (both non-null or null during the first blocks after v20 activation) // Hence, we need to merge the std::set if another std::set already exists for the same sig. - auto it_sig = quorumsCLSigs.find(sig); - if (it_sig != quorumsCLSigs.end()) { + if (auto [it_sig, inserted] = quorumsCLSigs.insert({sig, idx_set}); !inserted) { it_sig->second.insert(idx_set.begin(), idx_set.end()); } - else { - quorumsCLSigs.insert(std::make_pair(sig, idx_set)); - } } return true; From ad529f4878f037a2334e8d1339656049a1279fe8 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Mon, 26 Jun 2023 10:55:56 +0300 Subject: [PATCH 14/20] Update release-notes-5377.md --- doc/release-notes-5377.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes-5377.md b/doc/release-notes-5377.md index 8c469202528f..2213a7592f4f 100644 --- a/doc/release-notes-5377.md +++ b/doc/release-notes-5377.md @@ -7,7 +7,7 @@ This field is a list containing: a ChainLock signature and the list of correspon `MNLISTDIFF` P2P message -------- -Starting with protocol version `70229`, the following fields are added to the `MNLISTDIFF` after `newQuorums`. +Starting with protocol version `70230`, the following fields are added to the `MNLISTDIFF` after `newQuorums`. | Field | Type | Size | Description | |--------------------|-----------------------|----------|---------------------------------------------------------------------| From 792bdc80da4bd036bec1d1a1ba0747dd11025c63 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Wed, 28 Jun 2023 17:24:52 +0300 Subject: [PATCH 15/20] verify quorum members based on clsigs --- test/functional/feature_llmq_rotation.py | 72 +++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/test/functional/feature_llmq_rotation.py b/test/functional/feature_llmq_rotation.py index 4dccb4a2cc96..65b655ccc2cf 100755 --- a/test/functional/feature_llmq_rotation.py +++ b/test/functional/feature_llmq_rotation.py @@ -9,10 +9,13 @@ Checks LLMQs Quorum Rotation ''' +import hashlib +import random +import struct from io import BytesIO from test_framework.test_framework import DashTestFramework -from test_framework.messages import CBlock, CBlockHeader, CCbTx, CMerkleBlock, FromHex, hash256, msg_getmnlistd, QuorumId +from test_framework.messages import CBlock, CBlockHeader, CCbTx, CMerkleBlock, FromHex, hash256, msg_getmnlistd, QuorumId, ser_uint256, sha256 from test_framework.mininode import P2PInterface from test_framework.util import ( assert_equal, @@ -270,8 +273,75 @@ def test_getmnlistdiff_base(self, baseBlockHash, blockHash, testQuorumsCLSigs): if testQuorumsCLSigs: # Total number of corresponding quorum indexes in quorumsCLSigs must be equal to the total of quorums in newQuorums assert_equal(len(d2["newQuorums"]), sum(len(value) for value in rpc_quorums_clsigs_dict.values())) + for cl_sig, value in rpc_quorums_clsigs_dict.items(): + for q in value: + self.test_verify_quorums(d2["newQuorums"][q], cl_sig) return d + def test_verify_quorums(self, quorum_info, quorum_cl_sig): + if int(quorum_cl_sig, 16) == 0: + # Skipping null-CLSig. No need to verify old way of shuffling (using BlockHash) + return + if quorum_info["version"] == 2 or quorum_info["version"] == 4: + # Skipping rotated quorums. Too complicated to implemented. + # TODO: Implement rotated quorum verification using CLSigs + return + quorum_height = self.nodes[0].getblock(quorum_info["quorumHash"])["height"] + work_height = quorum_height - 8 + modifier = self.get_hash_modifier(quorum_info["llmqType"], work_height, quorum_cl_sig) + mn_list = self.nodes[0].protx('diff', 1, work_height)["mnList"] + scored_mns = [] + # Compute each valid mn score and add them (mn, score) in scored_mns + for mn in mn_list: + if mn["isValid"] is False: + # Skip invalid mns + continue + score = self.compute_mn_score(mn, modifier) + scored_mns.append((mn, score)) + # Sort the list based on the score in descending order + scored_mns.sort(key=lambda x: x[1], reverse=True) + llmq_size = self.get_llmq_size(int(quorum_info["llmqType"])) + # Keep the first llmq_size mns + scored_mns = scored_mns[:llmq_size] + quorum_info_members = self.nodes[0].quorum('info', quorum_info["llmqType"], quorum_info["quorumHash"])["members"] + # Make sure that each quorum member returned from quorum info RPC is matched in our scored_mns list + for m in quorum_info_members: + found = False + for e in scored_mns: + if m["proTxHash"] == e[0]["proRegTxHash"]: + found = True + break + assert found + return + + def get_hash_modifier(self, llmq_type, height, cl_sig): + bytes = b"" + bytes += struct.pack(' Date: Wed, 28 Jun 2023 18:30:51 +0300 Subject: [PATCH 16/20] cleanup --- test/functional/feature_llmq_rotation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/functional/feature_llmq_rotation.py b/test/functional/feature_llmq_rotation.py index 65b655ccc2cf..61fe1bc4affd 100755 --- a/test/functional/feature_llmq_rotation.py +++ b/test/functional/feature_llmq_rotation.py @@ -9,8 +9,6 @@ Checks LLMQs Quorum Rotation ''' -import hashlib -import random import struct from io import BytesIO From 93fbd163111cff28c77c2064cbd60ab3ea7a275e Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Fri, 30 Jun 2023 15:33:16 +0300 Subject: [PATCH 17/20] Enforce divert CL in CbTx before one quorum cycle --- test/functional/feature_llmq_rotation.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/functional/feature_llmq_rotation.py b/test/functional/feature_llmq_rotation.py index 61fe1bc4affd..d9a9fe08d791 100755 --- a/test/functional/feature_llmq_rotation.py +++ b/test/functional/feature_llmq_rotation.py @@ -123,9 +123,21 @@ def run_test(self): b_0 = self.nodes[0].getbestblockhash() - self.log.info("Wait for chainlock") + # At this point, we want to wait for CLs just before the self.mine_cycle_quorum to diversify the CLs in CbTx. + # Although because here a new quorum cycle is starting, and we don't want to mine them now, mine 8 blocks (to skip all DKG phases) + nodes = [self.nodes[0]] + [mn.node for mn in self.mninfo.copy()] + self.nodes[0].generate(8) + self.sync_blocks(nodes) self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + # And for the remaining blocks, enforce new CL in CbTx + skip_count = 23 - (self.nodes[0].getblockcount() % 24) + for i in range(15): + self.nodes[0].generate(1) + self.sync_blocks(nodes) + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + + (quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type) assert(self.test_quorum_listextended(quorum_info_0_0, llmq_type_name)) assert(self.test_quorum_listextended(quorum_info_0_1, llmq_type_name)) From 735d34ac2317927191cca7dd90b237fccb7b80b8 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Fri, 30 Jun 2023 15:47:16 +0300 Subject: [PATCH 18/20] typo fix --- test/functional/feature_llmq_rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_llmq_rotation.py b/test/functional/feature_llmq_rotation.py index d9a9fe08d791..5c8207b983d0 100755 --- a/test/functional/feature_llmq_rotation.py +++ b/test/functional/feature_llmq_rotation.py @@ -132,7 +132,7 @@ def run_test(self): # And for the remaining blocks, enforce new CL in CbTx skip_count = 23 - (self.nodes[0].getblockcount() % 24) - for i in range(15): + for i in range(skip_count): self.nodes[0].generate(1) self.sync_blocks(nodes) self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) From 14a4d5ac99e47e7aa65680c0db6dbec4b5b7182c Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Tue, 4 Jul 2023 22:05:43 +0300 Subject: [PATCH 19/20] Bumped MIN_MASTERNODE_PROTO_VERSION --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 4944d9c05743..adc8e4349462 100644 --- a/src/version.h +++ b/src/version.h @@ -18,7 +18,7 @@ static const int INIT_PROTO_VERSION = 209; static const int MIN_PEER_PROTO_VERSION = 70215; //! minimum proto version of masternode to accept in DKGs -static const int MIN_MASTERNODE_PROTO_VERSION = 70227; +static const int MIN_MASTERNODE_PROTO_VERSION = 70230; //! protocol version is included in MNAUTH starting with this version static const int MNAUTH_NODE_VER_VERSION = 70218; From 410c19727c0f610cd8ca91ec83a222de41885a67 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Tue, 4 Jul 2023 22:06:08 +0300 Subject: [PATCH 20/20] restored empty lines --- src/version.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/version.h b/src/version.h index adc8e4349462..0c4eccf6af30 100644 --- a/src/version.h +++ b/src/version.h @@ -9,6 +9,8 @@ /** * network protocol versioning */ + + static const int PROTOCOL_VERSION = 70230; //! initial proto version, to be increased after version/verack negotiation