From e5e8d1e84ea3d1243ddeffa9b2fdd502756c1007 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 8 Jun 2026 18:57:42 -0500 Subject: [PATCH 1/6] fix: limit signing share sessions per peer --- src/Makefile.test.include | 1 + src/llmq/signing_shares.cpp | 36 ++++++++++++++++++- src/llmq/signing_shares.h | 3 +- src/test/llmq_signing_shares_tests.cpp | 48 ++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/test/llmq_signing_shares_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 375c9940d059..d35d1f135d3b 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -136,6 +136,7 @@ BITCOIN_TESTS =\ test/llmq_commitment_tests.cpp \ test/llmq_hash_tests.cpp \ test/llmq_params_tests.cpp \ + test/llmq_signing_shares_tests.cpp \ test/llmq_snapshot_tests.cpp \ test/llmq_utils_tests.cpp \ test/logging_tests.cpp \ diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index f337d63ff19e..ce0bc459db18 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -22,10 +22,21 @@ #include +#include #include namespace llmq { +namespace { +constexpr size_t MAX_SESSIONS_PER_PEER_FACTOR{4}; +constexpr size_t MIN_SESSIONS_PER_PEER{100}; + +size_t GetMaxSessionsForPeer(const Consensus::LLMQParams& params) +{ + return std::max(size_t(params.size) * MAX_SESSIONS_PER_PEER_FACTOR, MIN_SESSIONS_PER_PEER); +} +} // namespace + void CSigShare::UpdateKey() { key.first = this->buildSignHash().Get(); @@ -133,6 +144,16 @@ CSigSharesNodeState::Session& CSigSharesNodeState::GetOrCreateSessionFromAnn(con return s; } +bool CSigSharesNodeState::CanCreateSessionFromAnn(const llmq::CSigSesAnn& ann, size_t maxSessions) const +{ + return sessions.count(ann.buildSignHash().Get()) != 0 || sessions.size() < maxSessions; +} + +size_t CSigSharesNodeState::GetSessionCount() const +{ + return sessions.size(); +} + CSigSharesNodeState::Session* CSigSharesNodeState::GetSessionBySignHash(const uint256& signHash) { auto it = sessions.find(signHash); @@ -206,7 +227,8 @@ void CSigSharesManager::UnregisterRecoveryInterface() bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSesAnn& ann) { auto llmqType = ann.getLlmqType(); - if (!Params().GetLLMQ(llmqType).has_value()) { + const auto& llmq_params_opt = Params().GetLLMQ(llmqType); + if (!llmq_params_opt.has_value()) { return false; } if (ann.getSessionId() == UNINITIALIZED_SESSION_ID || ann.getQuorumHash().IsNull() || ann.getId().IsNull() || ann.getMsgHash().IsNull()) { @@ -225,7 +247,14 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSe LOCK(cs); auto& nodeState = nodeStates[pfrom.GetId()]; + const size_t maxSessions = GetMaxSessionsForPeer(*llmq_params_opt); + if (!nodeState.CanCreateSessionFromAnn(ann, maxSessions)) { + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sessions. cnt=%d, max=%d, node=%d\n", + __func__, nodeState.GetSessionCount(), maxSessions, pfrom.GetId()); + return false; + } auto& session = nodeState.GetOrCreateSessionFromAnn(ann); + timeSeenForSessions.try_emplace(ann.buildSignHash().Get(), GetTime().count()); nodeState.sessionByRecvId.erase(session.recvSessionId); nodeState.sessionByRecvId.erase(ann.getSessionId()); session.recvSessionId = ann.getSessionId(); @@ -1247,6 +1276,11 @@ void CSigSharesManager::Cleanup() doneSessions.emplace(sigShare.GetSignHash()); } }); + for (const auto& [signHash, _] : timeSeenForSessions) { + if (doneSessions.count(signHash) == 0 && sigman.HasRecoveredSigForSession(signHash)) { + doneSessions.emplace(signHash); + } + } for (const auto& signHash : doneSessions) { RemoveSigSharesForSession(signHash); } diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 82c1e88de585..7ae4c5634cb7 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -327,7 +327,6 @@ class CSigSharesNodeState CSigSharesInv requested; CSigSharesInv knows; }; - // TODO limit number of sessions per node Uint256HashMap sessions; std::unordered_map sessionByRecvId; @@ -339,6 +338,8 @@ class CSigSharesNodeState Session& GetOrCreateSessionFromShare(const CSigShare& sigShare); Session& GetOrCreateSessionFromAnn(const CSigSesAnn& ann); + [[nodiscard]] bool CanCreateSessionFromAnn(const CSigSesAnn& ann, size_t maxSessions) const; + [[nodiscard]] size_t GetSessionCount() const; Session* GetSessionBySignHash(const uint256& signHash); Session* GetSessionByRecvId(uint32_t sessionId); bool GetSessionInfoByRecvId(uint32_t sessionId, SessionInfo& retInfo); diff --git a/src/test/llmq_signing_shares_tests.cpp b/src/test/llmq_signing_shares_tests.cpp new file mode 100644 index 000000000000..3f832ce66a5b --- /dev/null +++ b/src/test/llmq_signing_shares_tests.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include + +#include +#include + +#include + +using namespace llmq; +using namespace llmq::testutils; + +BOOST_FIXTURE_TEST_SUITE(llmq_signing_shares_tests, BasicTestingSetup) + +static CSigSesAnn MakeAnn(uint32_t session_id, uint32_t nonce) +{ + return CSigSesAnn{session_id, Consensus::LLMQType::LLMQ_50_60, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce)}; +} + +BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) +{ + CSigSharesNodeState node_state; + + const CSigSesAnn ann1{MakeAnn(1, 1)}; + const CSigSesAnn ann2{MakeAnn(2, 2)}; + const CSigSesAnn ann3{MakeAnn(3, 3)}; + constexpr size_t max_sessions{2}; + + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann1); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U); + + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann2, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann2); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); + + BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann3, max_sessions)); + + const CSigSesAnn ann1_refresh{4, Consensus::LLMQType::LLMQ_50_60, ann1.getQuorumHash(), ann1.getId(), ann1.getMsgHash()}; + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1_refresh, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann1_refresh); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); +} + +BOOST_AUTO_TEST_SUITE_END() From 135f8ce0b7fe4f9e22c324d00cad0a34b5a065c1 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 9 Jun 2026 12:18:34 -0500 Subject: [PATCH 2/6] fix: refine signing share session cap --- src/llmq/signing_shares.cpp | 13 +++++++++---- src/llmq/signing_shares.h | 1 + src/test/llmq_signing_shares_tests.cpp | 25 +++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index ce0bc459db18..47334b998da5 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -146,7 +146,7 @@ CSigSharesNodeState::Session& CSigSharesNodeState::GetOrCreateSessionFromAnn(con bool CSigSharesNodeState::CanCreateSessionFromAnn(const llmq::CSigSesAnn& ann, size_t maxSessions) const { - return sessions.count(ann.buildSignHash().Get()) != 0 || sessions.size() < maxSessions; + return sessions.count(ann.buildSignHash().Get()) != 0 || GetSessionCount(ann.getLlmqType()) < maxSessions; } size_t CSigSharesNodeState::GetSessionCount() const @@ -154,6 +154,11 @@ size_t CSigSharesNodeState::GetSessionCount() const return sessions.size(); } +size_t CSigSharesNodeState::GetSessionCount(Consensus::LLMQType llmqType) const +{ + return std::ranges::count_if(sessions, [&](const auto& kv) { return kv.second.llmqType == llmqType; }); +} + CSigSharesNodeState::Session* CSigSharesNodeState::GetSessionBySignHash(const uint256& signHash) { auto it = sessions.find(signHash); @@ -249,12 +254,12 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSe auto& nodeState = nodeStates[pfrom.GetId()]; const size_t maxSessions = GetMaxSessionsForPeer(*llmq_params_opt); if (!nodeState.CanCreateSessionFromAnn(ann, maxSessions)) { - LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sessions. cnt=%d, max=%d, node=%d\n", - __func__, nodeState.GetSessionCount(), maxSessions, pfrom.GetId()); + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sessions. cnt=%d, max=%d, llmqType=%d, node=%d\n", + __func__, nodeState.GetSessionCount(llmqType), maxSessions, static_cast(llmqType), pfrom.GetId()); return false; } auto& session = nodeState.GetOrCreateSessionFromAnn(ann); - timeSeenForSessions.try_emplace(ann.buildSignHash().Get(), GetTime().count()); + timeSeenForSessions.insert_or_assign(ann.buildSignHash().Get(), GetTime().count()); nodeState.sessionByRecvId.erase(session.recvSessionId); nodeState.sessionByRecvId.erase(ann.getSessionId()); session.recvSessionId = ann.getSessionId(); diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 7ae4c5634cb7..120a63dee439 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -340,6 +340,7 @@ class CSigSharesNodeState Session& GetOrCreateSessionFromAnn(const CSigSesAnn& ann); [[nodiscard]] bool CanCreateSessionFromAnn(const CSigSesAnn& ann, size_t maxSessions) const; [[nodiscard]] size_t GetSessionCount() const; + [[nodiscard]] size_t GetSessionCount(Consensus::LLMQType llmqType) const; Session* GetSessionBySignHash(const uint256& signHash); Session* GetSessionByRecvId(uint32_t sessionId); bool GetSessionInfoByRecvId(uint32_t sessionId, SessionInfo& retInfo); diff --git a/src/test/llmq_signing_shares_tests.cpp b/src/test/llmq_signing_shares_tests.cpp index 3f832ce66a5b..bb10f0d97cdf 100644 --- a/src/test/llmq_signing_shares_tests.cpp +++ b/src/test/llmq_signing_shares_tests.cpp @@ -15,9 +15,9 @@ using namespace llmq::testutils; BOOST_FIXTURE_TEST_SUITE(llmq_signing_shares_tests, BasicTestingSetup) -static CSigSesAnn MakeAnn(uint32_t session_id, uint32_t nonce) +static CSigSesAnn MakeAnn(uint32_t session_id, uint32_t nonce, Consensus::LLMQType llmq_type = Consensus::LLMQType::LLMQ_50_60) { - return CSigSesAnn{session_id, Consensus::LLMQType::LLMQ_50_60, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce)}; + return CSigSesAnn{session_id, llmq_type, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce)}; } BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) @@ -45,4 +45,25 @@ BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); } +BOOST_AUTO_TEST_CASE(sig_ses_ann_limit_is_per_llmq_type) +{ + CSigSharesNodeState node_state; + + constexpr size_t max_sessions{1}; + const CSigSesAnn ann1{MakeAnn(1, 1)}; + const CSigSesAnn ann2{MakeAnn(2, 2)}; + const CSigSesAnn other_type_ann{MakeAnn(3, 3, Consensus::LLMQType::LLMQ_400_60)}; + + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann1); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U); + + BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann2, max_sessions)); + BOOST_CHECK(node_state.CanCreateSessionFromAnn(other_type_ann, max_sessions)); + node_state.GetOrCreateSessionFromAnn(other_type_ann); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 2U); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_400_60), 1U); +} + BOOST_AUTO_TEST_SUITE_END() From 65959484823ba0b4202e6d0216fa0958793eb092 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 9 Jun 2026 12:26:19 -0500 Subject: [PATCH 3/6] test: avoid signing share Makefile conflict --- src/Makefile.test.include | 1 - src/test/llmq_signing_shares_tests.cpp | 69 -------------------------- src/test/llmq_utils_tests.cpp | 52 +++++++++++++++++++ 3 files changed, 52 insertions(+), 70 deletions(-) delete mode 100644 src/test/llmq_signing_shares_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index d35d1f135d3b..375c9940d059 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -136,7 +136,6 @@ BITCOIN_TESTS =\ test/llmq_commitment_tests.cpp \ test/llmq_hash_tests.cpp \ test/llmq_params_tests.cpp \ - test/llmq_signing_shares_tests.cpp \ test/llmq_snapshot_tests.cpp \ test/llmq_utils_tests.cpp \ test/logging_tests.cpp \ diff --git a/src/test/llmq_signing_shares_tests.cpp b/src/test/llmq_signing_shares_tests.cpp deleted file mode 100644 index bb10f0d97cdf..000000000000 --- a/src/test/llmq_signing_shares_tests.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2026 The Dash Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include - -#include -#include - -#include - -using namespace llmq; -using namespace llmq::testutils; - -BOOST_FIXTURE_TEST_SUITE(llmq_signing_shares_tests, BasicTestingSetup) - -static CSigSesAnn MakeAnn(uint32_t session_id, uint32_t nonce, Consensus::LLMQType llmq_type = Consensus::LLMQType::LLMQ_50_60) -{ - return CSigSesAnn{session_id, llmq_type, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce)}; -} - -BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) -{ - CSigSharesNodeState node_state; - - const CSigSesAnn ann1{MakeAnn(1, 1)}; - const CSigSesAnn ann2{MakeAnn(2, 2)}; - const CSigSesAnn ann3{MakeAnn(3, 3)}; - constexpr size_t max_sessions{2}; - - BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions)); - node_state.GetOrCreateSessionFromAnn(ann1); - BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U); - - BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann2, max_sessions)); - node_state.GetOrCreateSessionFromAnn(ann2); - BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); - - BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann3, max_sessions)); - - const CSigSesAnn ann1_refresh{4, Consensus::LLMQType::LLMQ_50_60, ann1.getQuorumHash(), ann1.getId(), ann1.getMsgHash()}; - BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1_refresh, max_sessions)); - node_state.GetOrCreateSessionFromAnn(ann1_refresh); - BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); -} - -BOOST_AUTO_TEST_CASE(sig_ses_ann_limit_is_per_llmq_type) -{ - CSigSharesNodeState node_state; - - constexpr size_t max_sessions{1}; - const CSigSesAnn ann1{MakeAnn(1, 1)}; - const CSigSesAnn ann2{MakeAnn(2, 2)}; - const CSigSesAnn other_type_ann{MakeAnn(3, 3, Consensus::LLMQType::LLMQ_400_60)}; - - BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions)); - node_state.GetOrCreateSessionFromAnn(ann1); - BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U); - BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U); - - BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann2, max_sessions)); - BOOST_CHECK(node_state.CanCreateSessionFromAnn(other_type_ann, max_sessions)); - node_state.GetOrCreateSessionFromAnn(other_type_ann); - BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 2U); - BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_400_60), 1U); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/llmq_utils_tests.cpp b/src/test/llmq_utils_tests.cpp index da67f4a5189a..7667dc92c083 100644 --- a/src/test/llmq_utils_tests.cpp +++ b/src/test/llmq_utils_tests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,57 @@ BOOST_FIXTURE_TEST_SUITE(llmq_utils_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(trivially_passes) { BOOST_CHECK(true); } +static CSigSesAnn MakeSigSesAnn(uint32_t session_id, uint32_t nonce, Consensus::LLMQType llmq_type = Consensus::LLMQType::LLMQ_50_60) +{ + return CSigSesAnn{session_id, llmq_type, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce)}; +} + +BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) +{ + CSigSharesNodeState node_state; + + const CSigSesAnn ann1{MakeSigSesAnn(1, 1)}; + const CSigSesAnn ann2{MakeSigSesAnn(2, 2)}; + const CSigSesAnn ann3{MakeSigSesAnn(3, 3)}; + constexpr size_t max_sessions{2}; + + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann1); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U); + + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann2, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann2); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); + + BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann3, max_sessions)); + + const CSigSesAnn ann1_refresh{4, Consensus::LLMQType::LLMQ_50_60, ann1.getQuorumHash(), ann1.getId(), ann1.getMsgHash()}; + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1_refresh, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann1_refresh); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); +} + +BOOST_AUTO_TEST_CASE(sig_ses_ann_limit_is_per_llmq_type) +{ + CSigSharesNodeState node_state; + + constexpr size_t max_sessions{1}; + const CSigSesAnn ann1{MakeSigSesAnn(1, 1)}; + const CSigSesAnn ann2{MakeSigSesAnn(2, 2)}; + const CSigSesAnn other_type_ann{MakeSigSesAnn(3, 3, Consensus::LLMQType::LLMQ_400_60)}; + + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann1); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U); + + BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann2, max_sessions)); + BOOST_CHECK(node_state.CanCreateSessionFromAnn(other_type_ann, max_sessions)); + node_state.GetOrCreateSessionFromAnn(other_type_ann); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 2U); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_400_60), 1U); +} + BOOST_AUTO_TEST_CASE(deterministic_outbound_connection_test) { // Test deterministic behavior From f6aa12a20fde19477adaabeae88916cdd14906b1 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 9 Jun 2026 16:51:15 -0500 Subject: [PATCH 4/6] fix: preserve signing share session expiry on refresh --- src/llmq/signing_shares.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index 47334b998da5..a2921cf882a1 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -258,8 +258,9 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSe __func__, nodeState.GetSessionCount(llmqType), maxSessions, static_cast(llmqType), pfrom.GetId()); return false; } + const uint256 signHash = ann.buildSignHash().Get(); auto& session = nodeState.GetOrCreateSessionFromAnn(ann); - timeSeenForSessions.insert_or_assign(ann.buildSignHash().Get(), GetTime().count()); + timeSeenForSessions.try_emplace(signHash, GetTime().count()); nodeState.sessionByRecvId.erase(session.recvSessionId); nodeState.sessionByRecvId.erase(ann.getSessionId()); session.recvSessionId = ann.getSessionId(); From 03dddeea35c8f7c523a7ac863359573919403832 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 9 Jun 2026 19:31:13 -0500 Subject: [PATCH 5/6] fix: count only announced signing share sessions --- src/llmq/signing_shares.cpp | 12 ++++++++++-- src/llmq/signing_shares.h | 3 +++ src/test/llmq_utils_tests.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index a2921cf882a1..6640cc3cf0cf 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -141,12 +141,13 @@ CSigSharesNodeState::Session& CSigSharesNodeState::GetOrCreateSessionFromAnn(con if (s.announced.inv.empty()) { InitSession(s, signHash, ann); } + s.receivedAnnouncement = true; return s; } bool CSigSharesNodeState::CanCreateSessionFromAnn(const llmq::CSigSesAnn& ann, size_t maxSessions) const { - return sessions.count(ann.buildSignHash().Get()) != 0 || GetSessionCount(ann.getLlmqType()) < maxSessions; + return sessions.count(ann.buildSignHash().Get()) != 0 || GetAnnouncementSessionCount(ann.getLlmqType()) < maxSessions; } size_t CSigSharesNodeState::GetSessionCount() const @@ -159,6 +160,13 @@ size_t CSigSharesNodeState::GetSessionCount(Consensus::LLMQType llmqType) const return std::ranges::count_if(sessions, [&](const auto& kv) { return kv.second.llmqType == llmqType; }); } +size_t CSigSharesNodeState::GetAnnouncementSessionCount(Consensus::LLMQType llmqType) const +{ + return std::ranges::count_if(sessions, [&](const auto& kv) { + return kv.second.receivedAnnouncement && kv.second.llmqType == llmqType; + }); +} + CSigSharesNodeState::Session* CSigSharesNodeState::GetSessionBySignHash(const uint256& signHash) { auto it = sessions.find(signHash); @@ -255,7 +263,7 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSe const size_t maxSessions = GetMaxSessionsForPeer(*llmq_params_opt); if (!nodeState.CanCreateSessionFromAnn(ann, maxSessions)) { LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sessions. cnt=%d, max=%d, llmqType=%d, node=%d\n", - __func__, nodeState.GetSessionCount(llmqType), maxSessions, static_cast(llmqType), pfrom.GetId()); + __func__, nodeState.GetAnnouncementSessionCount(llmqType), maxSessions, static_cast(llmqType), pfrom.GetId()); return false; } const uint256 signHash = ann.buildSignHash().Get(); diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 120a63dee439..da2f39043c1b 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -326,6 +326,8 @@ class CSigSharesNodeState CSigSharesInv announced; CSigSharesInv requested; CSigSharesInv knows; + + bool receivedAnnouncement{false}; }; Uint256HashMap sessions; @@ -341,6 +343,7 @@ class CSigSharesNodeState [[nodiscard]] bool CanCreateSessionFromAnn(const CSigSesAnn& ann, size_t maxSessions) const; [[nodiscard]] size_t GetSessionCount() const; [[nodiscard]] size_t GetSessionCount(Consensus::LLMQType llmqType) const; + [[nodiscard]] size_t GetAnnouncementSessionCount(Consensus::LLMQType llmqType) const; Session* GetSessionBySignHash(const uint256& signHash); Session* GetSessionByRecvId(uint32_t sessionId); bool GetSessionInfoByRecvId(uint32_t sessionId, SessionInfo& retInfo); diff --git a/src/test/llmq_utils_tests.cpp b/src/test/llmq_utils_tests.cpp index 7667dc92c083..ea51601a8b5a 100644 --- a/src/test/llmq_utils_tests.cpp +++ b/src/test/llmq_utils_tests.cpp @@ -29,6 +29,13 @@ static CSigSesAnn MakeSigSesAnn(uint32_t session_id, uint32_t nonce, Consensus:: return CSigSesAnn{session_id, llmq_type, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce)}; } +static CSigShare MakeSigShare(uint32_t nonce, Consensus::LLMQType llmq_type = Consensus::LLMQType::LLMQ_50_60) +{ + CSigShare sig_share{llmq_type, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce), 1, CBLSLazySignature{}}; + sig_share.UpdateKey(); + return sig_share; +} + BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) { CSigSharesNodeState node_state; @@ -41,10 +48,12 @@ BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions)); node_state.GetOrCreateSessionFromAnn(ann1); BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U); + BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U); BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann2, max_sessions)); node_state.GetOrCreateSessionFromAnn(ann2); BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); + BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), max_sessions); BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann3, max_sessions)); @@ -52,6 +61,25 @@ BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh) BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1_refresh, max_sessions)); node_state.GetOrCreateSessionFromAnn(ann1_refresh); BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions); + BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), max_sessions); +} + +BOOST_AUTO_TEST_CASE(sig_ses_ann_limit_ignores_send_only_sessions) +{ + CSigSharesNodeState node_state; + + constexpr size_t max_sessions{1}; + const CSigShare sig_share{MakeSigShare(1)}; + const CSigSesAnn ann{MakeSigSesAnn(1, 2)}; + + node_state.GetOrCreateSessionFromShare(sig_share); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U); + BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), 0U); + + BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann, max_sessions)); + node_state.GetOrCreateSessionFromAnn(ann); + BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 2U); + BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U); } BOOST_AUTO_TEST_CASE(sig_ses_ann_limit_is_per_llmq_type) From 76a6fd7295c36d5da8125febba64f50b3b6f7238 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 16 Jun 2026 17:01:48 -0500 Subject: [PATCH 6/6] fix: ignore excess signing share sessions --- src/llmq/signing_shares.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index 6640cc3cf0cf..4849e2940ee9 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -264,7 +264,7 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSe if (!nodeState.CanCreateSessionFromAnn(ann, maxSessions)) { LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sessions. cnt=%d, max=%d, llmqType=%d, node=%d\n", __func__, nodeState.GetAnnouncementSessionCount(llmqType), maxSessions, static_cast(llmqType), pfrom.GetId()); - return false; + return true; } const uint256 signHash = ann.buildSignHash().Get(); auto& session = nodeState.GetOrCreateSessionFromAnn(ann);