-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix: bound QGETDATA request tracking and reject requester-supplied nError #7519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| #include <deque> | ||
| #include <map> | ||
| #include <memory> | ||
| #include <optional> | ||
| #include <thread> | ||
|
|
||
| class CBLSSignature; | ||
|
|
@@ -48,6 +49,18 @@ class CDKGSessionManager; | |
| class CQuorumBlockProcessor; | ||
| class CQuorumSnapshotManager; | ||
|
|
||
| //! Per-identity budget for live peer-initiated QGETDATA tracking entries. | ||
| //! | ||
| //! Entries are keyed on the attacker-chosen quorumHash, so without a cap a peer that never | ||
| //! repeats a hash is never rate-limited and grows mapQuorumDataRequests unboundedly for the | ||
| //! 300s+bias expiry window (cleanup only runs per-block, and not at all during IBD). | ||
| //! The budget is per requesting identity rather than global so one peer cannot evict or | ||
| //! starve another; all qwatch peers share the null proRegTx identity and therefore one budget, | ||
| //! matching the existing rate-limit behaviour for that class of peer. | ||
| //! An honest peer requests at most vvec+contributions for a handful of quorums it is recovering, | ||
| //! so this is orders of magnitude above legitimate use. | ||
| static constexpr size_t MAX_INBOUND_DATA_REQUESTS{64}; | ||
|
|
||
| /** | ||
| * The quorum manager maintains quorums which were mined on chain. When a quorum is requested from the manager, | ||
| * it will lookup the commitment (through CQuorumBlockProcessor) and build a CQuorum object from it. | ||
|
|
@@ -70,6 +83,10 @@ class CQuorumManager final | |
| mutable Mutex cs_data_requests; | ||
| mutable std::unordered_map<CQuorumDataRequestKey, CQuorumDataRequest, StaticSaltedHasher> mapQuorumDataRequests | ||
| GUARDED_BY(cs_data_requests); | ||
| //! Live peer-initiated entries in mapQuorumDataRequests, counted per requesting identity so | ||
| //! the per-identity budget can be enforced without scanning the whole map. | ||
| mutable std::unordered_map<uint256, size_t, StaticSaltedHasher> m_inbound_request_counts | ||
| GUARDED_BY(cs_data_requests); | ||
|
|
||
| mutable Mutex m_cs_maps; | ||
| mutable std::map<Consensus::LLMQType, Uint256LruHashMap<CQuorumPtr>> mapQuorumsCache | ||
|
|
@@ -131,9 +148,12 @@ class CQuorumManager final | |
| bool IsMasternode() const; | ||
| bool IsWatching() const; | ||
|
|
||
| //! Request tracking for QGETDATA/QDATA — used by NetQuorum and RPC | ||
| bool RegisterDataRequest(const CQuorumDataRequestKey& key, const CQuorumDataRequest& request, | ||
| bool add_expiry_bias = true) const | ||
| //! Request tracking for QGETDATA/QDATA — used by NetQuorum and RPC. | ||
| //! Returns nullopt when a peer-initiated request would exceed that identity's tracking | ||
| //! budget, true when the entry was created or refreshed, and false when an unexpired entry | ||
| //! already exists (the rate limit applies). | ||
| std::optional<bool> RegisterDataRequest(const CQuorumDataRequestKey& key, const CQuorumDataRequest& request, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| bool add_expiry_bias = true) const | ||
| EXCLUSIVE_LOCKS_REQUIRED(!cs_data_requests); | ||
|
Comment on lines
+151
to
157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Find all callers of CQuorumManager::RegisterDataRequest to confirm they handle std::optional<bool>.
rg -nP -C5 '\bRegisterDataRequest\s*\(' --type=cppRepository: dashpay/dash Length of output: 150 🏁 Script executed: #!/bin/bash
set -u
echo "Tracked files matching quorumsman.h name:"
git ls-files | rg '(^|/)quorumsman\.h$|src/llmq/net_quorum\.cpp$|src/llmq/.*quorum.*data.*|quorums.*data|quorum.*data' || true
echo
echo "Search all tracked files for RegisterDataRequest and CQuorumDataRequestKey:"
rg -n -C4 '\bRegisterDataRequest\s*\(|\bCQuorumDataRequestKey\b' || true
echo
echo "Locate RPC files mentioning QGETDATA/QDATA or CQuorumDataRequest:"
rg -n -C3 '\b(QGETDATA|QDATA|CQuorumDataRequest|quorumdata)\b' --glob '*.{cpp,h,json,py,js}' . || true
echo
echo "Current CQuorumManager declaration context if available:"
file="$(git ls-files | rg '(^|/)quorumsman\.h$' | head -n1)"
if [ -n "$file" ]; then
wc -l "$file"
sed -n '1,260p' "$file" | nl -ba
fiRepository: dashpay/dash Length of output: 350 🏁 Script executed: #!/bin/bash
set -u
echo "Exact identifier search:"
python3 - <<'PY'
from pathlib import Path
for p in Path('.').rglob('*'):
if p.is_file() and not any(part in {'.git','build','vendor','node_modules'} for part in p.parts):
try:
s=p.read_text(errors='ignore')
except Exception:
continue
if 'RegisterDataRequest' in s or 'CQuorumDataRequestKey' in s:
print(p)
for i,line in enumerate(s.splitlines(),1):
if 'RegisterDataRequest' in line or 'CQuorumDataRequestKey' in line:
print(f" {i}: {line}")
PY
echo
echo "NetQuorum source relevant functions if present:"
sed -n '1,520p' src/llmq/net_quorum.cpp | nl -ba | sed -n '110,170p;328,365p'
echo
echo "Any QGETDATA/QDATA/CQuorumDataRequest mentions:"
python3 - <<'PY'
from pathlib import Path
terms = ['QGETDATA','QDATA','CQuorumDataRequest','quorumdata']
for p in Path('.').rglob('*'):
if p.is_file() and p.suffix in {'.cpp','.h','.py','.json',''}:
try:
s=p.read_text(errors='ignore').splitlines()
except Exception:
continue
hits=[(i,line) for i,line in enumerate(s,1) if any(t in line for t in terms)]
if hits:
print(p)
for i,line in hits[:40]:
print(f" {i}: {line[:220]}")
PYRepository: dashpay/dash Length of output: 15154 Fix the RPC call to use
Use the NetQuorum RPC pattern with 🤖 Prompt for AI Agents |
||
| enum class DataResponseValidation : uint8_t { OK, NotRequested, AlreadyReceived, Mismatch }; | ||
| DataResponseValidation ValidateDataResponse(const CQuorumDataRequestKey& key, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm; why only 10...?