Skip to content

Commit 11f524b

Browse files
Merge #7351: fix: limit signing share sessions per peer
76a6fd7 fix: ignore excess signing share sessions (PastaClaw) 03dddee fix: count only announced signing share sessions (PastaClaw) f6aa12a fix: preserve signing share session expiry on refresh (PastaClaw) 6595948 test: avoid signing share Makefile conflict (PastaClaw) 135f8ce fix: refine signing share session cap (PastaClaw) e5e8d1e fix: limit signing share sessions per peer (PastaClaw) Pull request description: # fix: limit signing share sessions per peer ## Issue being fixed or feature implemented Peers can announce many distinct QSIGSESANN signing sessions. Without a per-peer cap, a peer can make its node state grow with announcement-only sessions. ## What was done? - Add a per-peer limit for sessions created from QSIGSESANN announcements. - Preserve refresh of an already-known session so normal re-announcements remain accepted. - Seed the existing session timeout state for newly announced sessions, without refreshing that timeout on repeated announcements. - Add unit coverage for the session cap helper. ## How Has This Been Tested? Tested on macOS arm64. - `git diff --check upstream/develop...HEAD` - `make -j8 src/test/test_dash` - `src/test/test_dash --run_test=llmq_signing_shares_tests` - Pre-PR code review gate: `ship` ## Breaking Changes None. ## Checklist - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [ ] I have assigned this pull request to a milestone ACKs for top commit: UdjinM6: utACK 76a6fd7 Tree-SHA512: 6703bc91c73fc116486d21393d4e69eed91615818974f03502147246f1cc249061b71605cd9e3246c97ffc2df9fbf3ab2b5e1e7135451b3ad1b82a97b4823d2a
2 parents a59ad9e + 76a6fd7 commit 11f524b

3 files changed

Lines changed: 135 additions & 2 deletions

File tree

src/llmq/signing_shares.cpp

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,21 @@
2222

2323
#include <cxxtimer.hpp>
2424

25+
#include <algorithm>
2526
#include <ranges>
2627

2728
namespace llmq
2829
{
30+
namespace {
31+
constexpr size_t MAX_SESSIONS_PER_PEER_FACTOR{4};
32+
constexpr size_t MIN_SESSIONS_PER_PEER{100};
33+
34+
size_t GetMaxSessionsForPeer(const Consensus::LLMQParams& params)
35+
{
36+
return std::max<size_t>(size_t(params.size) * MAX_SESSIONS_PER_PEER_FACTOR, MIN_SESSIONS_PER_PEER);
37+
}
38+
} // namespace
39+
2940
void CSigShare::UpdateKey()
3041
{
3142
key.first = this->buildSignHash().Get();
@@ -130,9 +141,32 @@ CSigSharesNodeState::Session& CSigSharesNodeState::GetOrCreateSessionFromAnn(con
130141
if (s.announced.inv.empty()) {
131142
InitSession(s, signHash, ann);
132143
}
144+
s.receivedAnnouncement = true;
133145
return s;
134146
}
135147

148+
bool CSigSharesNodeState::CanCreateSessionFromAnn(const llmq::CSigSesAnn& ann, size_t maxSessions) const
149+
{
150+
return sessions.count(ann.buildSignHash().Get()) != 0 || GetAnnouncementSessionCount(ann.getLlmqType()) < maxSessions;
151+
}
152+
153+
size_t CSigSharesNodeState::GetSessionCount() const
154+
{
155+
return sessions.size();
156+
}
157+
158+
size_t CSigSharesNodeState::GetSessionCount(Consensus::LLMQType llmqType) const
159+
{
160+
return std::ranges::count_if(sessions, [&](const auto& kv) { return kv.second.llmqType == llmqType; });
161+
}
162+
163+
size_t CSigSharesNodeState::GetAnnouncementSessionCount(Consensus::LLMQType llmqType) const
164+
{
165+
return std::ranges::count_if(sessions, [&](const auto& kv) {
166+
return kv.second.receivedAnnouncement && kv.second.llmqType == llmqType;
167+
});
168+
}
169+
136170
CSigSharesNodeState::Session* CSigSharesNodeState::GetSessionBySignHash(const uint256& signHash)
137171
{
138172
auto it = sessions.find(signHash);
@@ -206,7 +240,8 @@ void CSigSharesManager::UnregisterRecoveryInterface()
206240
bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSesAnn& ann)
207241
{
208242
auto llmqType = ann.getLlmqType();
209-
if (!Params().GetLLMQ(llmqType).has_value()) {
243+
const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
244+
if (!llmq_params_opt.has_value()) {
210245
return false;
211246
}
212247
if (ann.getSessionId() == UNINITIALIZED_SESSION_ID || ann.getQuorumHash().IsNull() || ann.getId().IsNull() || ann.getMsgHash().IsNull()) {
@@ -225,7 +260,15 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSe
225260

226261
LOCK(cs);
227262
auto& nodeState = nodeStates[pfrom.GetId()];
263+
const size_t maxSessions = GetMaxSessionsForPeer(*llmq_params_opt);
264+
if (!nodeState.CanCreateSessionFromAnn(ann, maxSessions)) {
265+
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sessions. cnt=%d, max=%d, llmqType=%d, node=%d\n",
266+
__func__, nodeState.GetAnnouncementSessionCount(llmqType), maxSessions, static_cast<int>(llmqType), pfrom.GetId());
267+
return true;
268+
}
269+
const uint256 signHash = ann.buildSignHash().Get();
228270
auto& session = nodeState.GetOrCreateSessionFromAnn(ann);
271+
timeSeenForSessions.try_emplace(signHash, GetTime<std::chrono::seconds>().count());
229272
nodeState.sessionByRecvId.erase(session.recvSessionId);
230273
nodeState.sessionByRecvId.erase(ann.getSessionId());
231274
session.recvSessionId = ann.getSessionId();
@@ -1247,6 +1290,11 @@ void CSigSharesManager::Cleanup()
12471290
doneSessions.emplace(sigShare.GetSignHash());
12481291
}
12491292
});
1293+
for (const auto& [signHash, _] : timeSeenForSessions) {
1294+
if (doneSessions.count(signHash) == 0 && sigman.HasRecoveredSigForSession(signHash)) {
1295+
doneSessions.emplace(signHash);
1296+
}
1297+
}
12501298
for (const auto& signHash : doneSessions) {
12511299
RemoveSigSharesForSession(signHash);
12521300
}

src/llmq/signing_shares.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,9 @@ class CSigSharesNodeState
326326
CSigSharesInv announced;
327327
CSigSharesInv requested;
328328
CSigSharesInv knows;
329+
330+
bool receivedAnnouncement{false};
329331
};
330-
// TODO limit number of sessions per node
331332
Uint256HashMap<Session> sessions;
332333

333334
std::unordered_map<uint32_t, Session*> sessionByRecvId;
@@ -339,6 +340,10 @@ class CSigSharesNodeState
339340

340341
Session& GetOrCreateSessionFromShare(const CSigShare& sigShare);
341342
Session& GetOrCreateSessionFromAnn(const CSigSesAnn& ann);
343+
[[nodiscard]] bool CanCreateSessionFromAnn(const CSigSesAnn& ann, size_t maxSessions) const;
344+
[[nodiscard]] size_t GetSessionCount() const;
345+
[[nodiscard]] size_t GetSessionCount(Consensus::LLMQType llmqType) const;
346+
[[nodiscard]] size_t GetAnnouncementSessionCount(Consensus::LLMQType llmqType) const;
342347
Session* GetSessionBySignHash(const uint256& signHash);
343348
Session* GetSessionByRecvId(uint32_t sessionId);
344349
bool GetSessionInfoByRecvId(uint32_t sessionId, SessionInfo& retInfo);

src/test/llmq_utils_tests.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <consensus/params.h>
99
#include <llmq/params.h>
10+
#include <llmq/signing_shares.h>
1011
#include <llmq/utils.h>
1112
#include <netaddress.h>
1213
#include <random.h>
@@ -23,6 +24,85 @@ BOOST_FIXTURE_TEST_SUITE(llmq_utils_tests, BasicTestingSetup)
2324

2425
BOOST_AUTO_TEST_CASE(trivially_passes) { BOOST_CHECK(true); }
2526

27+
static CSigSesAnn MakeSigSesAnn(uint32_t session_id, uint32_t nonce, Consensus::LLMQType llmq_type = Consensus::LLMQType::LLMQ_50_60)
28+
{
29+
return CSigSesAnn{session_id, llmq_type, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce)};
30+
}
31+
32+
static CSigShare MakeSigShare(uint32_t nonce, Consensus::LLMQType llmq_type = Consensus::LLMQType::LLMQ_50_60)
33+
{
34+
CSigShare sig_share{llmq_type, GetTestQuorumHash(1), GetTestQuorumHash(2), GetTestQuorumHash(nonce), 1, CBLSLazySignature{}};
35+
sig_share.UpdateKey();
36+
return sig_share;
37+
}
38+
39+
BOOST_AUTO_TEST_CASE(sig_ses_ann_respects_session_limit_but_allows_refresh)
40+
{
41+
CSigSharesNodeState node_state;
42+
43+
const CSigSesAnn ann1{MakeSigSesAnn(1, 1)};
44+
const CSigSesAnn ann2{MakeSigSesAnn(2, 2)};
45+
const CSigSesAnn ann3{MakeSigSesAnn(3, 3)};
46+
constexpr size_t max_sessions{2};
47+
48+
BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions));
49+
node_state.GetOrCreateSessionFromAnn(ann1);
50+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U);
51+
BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U);
52+
53+
BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann2, max_sessions));
54+
node_state.GetOrCreateSessionFromAnn(ann2);
55+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions);
56+
BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), max_sessions);
57+
58+
BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann3, max_sessions));
59+
60+
const CSigSesAnn ann1_refresh{4, Consensus::LLMQType::LLMQ_50_60, ann1.getQuorumHash(), ann1.getId(), ann1.getMsgHash()};
61+
BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1_refresh, max_sessions));
62+
node_state.GetOrCreateSessionFromAnn(ann1_refresh);
63+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(), max_sessions);
64+
BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), max_sessions);
65+
}
66+
67+
BOOST_AUTO_TEST_CASE(sig_ses_ann_limit_ignores_send_only_sessions)
68+
{
69+
CSigSharesNodeState node_state;
70+
71+
constexpr size_t max_sessions{1};
72+
const CSigShare sig_share{MakeSigShare(1)};
73+
const CSigSesAnn ann{MakeSigSesAnn(1, 2)};
74+
75+
node_state.GetOrCreateSessionFromShare(sig_share);
76+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U);
77+
BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), 0U);
78+
79+
BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann, max_sessions));
80+
node_state.GetOrCreateSessionFromAnn(ann);
81+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 2U);
82+
BOOST_CHECK_EQUAL(node_state.GetAnnouncementSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U);
83+
}
84+
85+
BOOST_AUTO_TEST_CASE(sig_ses_ann_limit_is_per_llmq_type)
86+
{
87+
CSigSharesNodeState node_state;
88+
89+
constexpr size_t max_sessions{1};
90+
const CSigSesAnn ann1{MakeSigSesAnn(1, 1)};
91+
const CSigSesAnn ann2{MakeSigSesAnn(2, 2)};
92+
const CSigSesAnn other_type_ann{MakeSigSesAnn(3, 3, Consensus::LLMQType::LLMQ_400_60)};
93+
94+
BOOST_CHECK(node_state.CanCreateSessionFromAnn(ann1, max_sessions));
95+
node_state.GetOrCreateSessionFromAnn(ann1);
96+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 1U);
97+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_50_60), 1U);
98+
99+
BOOST_CHECK(!node_state.CanCreateSessionFromAnn(ann2, max_sessions));
100+
BOOST_CHECK(node_state.CanCreateSessionFromAnn(other_type_ann, max_sessions));
101+
node_state.GetOrCreateSessionFromAnn(other_type_ann);
102+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(), 2U);
103+
BOOST_CHECK_EQUAL(node_state.GetSessionCount(Consensus::LLMQType::LLMQ_400_60), 1U);
104+
}
105+
26106
BOOST_AUTO_TEST_CASE(deterministic_outbound_connection_test)
27107
{
28108
// Test deterministic behavior

0 commit comments

Comments
 (0)