fix: stop unvalidated governance orphan-vote amplification - #7517
fix: stop unvalidated governance orphan-vote amplification#7517PastaPastaPasta wants to merge 1 commit into
Conversation
|
Warning Review limit reached
Next review available in: 34 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
⛔ Blockers found — Sonnet deferred (commit 1f9db83) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3dfcfc8403
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Shuffle(vecHashesFiltered.begin(), vecHashesFiltered.end(), FastRandomContext()); | ||
| vecHashesFiltered.resize(MAX_ORPHAN_OBJECT_REQUESTS_PER_TICK); |
There was a problem hiding this comment.
Retry retained orphans before they expire
When more than 100 signed orphan parents are cached and the immediate request to the announcing peer fails, random sampling does not prevent starvation: Schedule() runs every 5 minutes while each orphan expires after 10 minutes, so a cache of 1,000 gives each parent only one or two 10% chances of being retried through other peers before removal. Thus roughly 81–90% of those parents may never be requested again, whereas the previous scheduler retried every retained orphan; use rotating batches or otherwise ensure coverage within the expiration window.
AGENTS.md reference: AGENTS.md:L157-L175
Useful? React with 👍 / 👎.
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The orphan-cache bounds and pre-insertion validation address the original unauthenticated cache-filling vector, but cached orphan votes are not recognized before the newly added cryptographic checks. A peer can therefore replay a valid orphan vote and repeatedly force ECDSA/BLS verification while holding the governance-store lock; the corrective follow-up commit should also be folded into the implementation commit for a hygienic history.
Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking | 🟡 1 suggestion(s)
1 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/governance/governance.cpp`:
- [BLOCKING] src/governance/governance.cpp:860-861: Cached orphan replays repeatedly perform signature verification
The known-vote checks at the start of `ProcessVote()` cover only `cmapVoteToObject` and `cmapInvalidVotes`; duplicate orphan detection does not occur until `cmmapOrphanVotes.Insert()` at line 882, after the ECDSA/BLS checks shown here. The inventory path has the same gap because `ConfirmInventoryRequest()` and `HaveVoteForHash()` do not consult cached orphan votes. After each payload, `PeerConsumeObjectRequest()` consumes the request-tracker entry, so the same peer can announce the hash again, receive another GETDATA, and resend the vote. Each replay then performs cryptographic verification while `cs_store` is held, receives no penalty, and is rejected only by the late duplicate insertion. The 1,000-entry cache bound does not limit this revalidation rate. Maintain a hash-index of cached orphan votes and treat those hashes as known before signature verification and inventory retrieval; add a regression test that replays an already cached valid orphan and confirms the signature-validation path is not entered again.
In `<commit:3dfcfc84037>`:
- [SUGGESTION] <commit:3dfcfc84037>:1: Squash the corrective review follow-up into the main fix
Commit `3dfcfc84037` changes the same implementation introduced by `be6a351ed0e`: it removes the transient penalty for valid orphan relays, replaces biased fixed-prefix truncation with random sampling, and repairs the accompanying tests. `CONTRIBUTING.md` identifies commits that repeatedly change the same lines as fixup commits that may need squashing. Fold this follow-up into `be6a351ed0e` so the permanent implementation commit does not temporarily penalize honest relays or starve higher-sorting orphan parents.
| const bool sig_ok = vote.CheckSignature(dmn->pdmnState->keyIDVoting) || | ||
| vote.CheckSignature(dmn->pdmnState->pubKeyOperator.Get()); |
There was a problem hiding this comment.
🔴 Blocking: Cached orphan replays repeatedly perform signature verification
The known-vote checks at the start of ProcessVote() cover only cmapVoteToObject and cmapInvalidVotes; duplicate orphan detection does not occur until cmmapOrphanVotes.Insert() at line 882, after the ECDSA/BLS checks shown here. The inventory path has the same gap because ConfirmInventoryRequest() and HaveVoteForHash() do not consult cached orphan votes. After each payload, PeerConsumeObjectRequest() consumes the request-tracker entry, so the same peer can announce the hash again, receive another GETDATA, and resend the vote. Each replay then performs cryptographic verification while cs_store is held, receives no penalty, and is rejected only by the late duplicate insertion. The 1,000-entry cache bound does not limit this revalidation rate. Maintain a hash-index of cached orphan votes and treat those hashes as known before signature verification and inventory retrieval; add a regression test that replays an already cached valid orphan and confirms the signature-validation path is not entered again.
source: ['codex']
3dfcfc8 to
c31d324
Compare
ProcessVote inserted a vote into cmmapOrphanVotes as soon as the parent object lookup missed, before any masternode-membership or signature check and with a zero misbehaviour penalty. The cache holds a million entries keyed by an attacker-chosen parent hash, so a peer could park hundreds of megabytes of unvalidated data. Worse, every orphan key produced one MNGOVERNANCESYNC per connected peer every 5 minutes, ignoring fPauseSend, and the map was persisted to governance.dat so a flood survived restart and re-drove the fan-out on boot. Any unauthenticated peer can do this. Require a tip-list masternode and a valid voting-or-operator signature before a vote may enter the orphan cache; UpdateHash()/GetSignatureHash() cover nParentHash, so votes cannot be repointed at fresh parent hashes without masternode keys. Bound the cache, sample orphan-object requests instead of requesting every key, skip peers with a paused send buffer, and stop persisting orphan votes.
c31d324 to
1f9db83
Compare
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
Carried-forward prior findings: the cached-orphan replay blocker remains valid at the current head, while the commit-history squash suggestion is fixed by the rewritten single-commit stack. The existing retry-coverage suggestion also remains valid; the latest delta introduces no genuinely new findings.
Source: Codex general reviewer gpt-5.6-sol; Codex dash-core-commit-history reviewer gpt-5.6-sol; Codex verifier gpt-5.6-sol. The openclaw-agent coordinator is orchestration-only.
Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking | 🟡 1 suggestion(s)
1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/governance/governance.cpp`:
- [SUGGESTION] src/governance/governance.cpp:1178-1180: Retry retained orphans before they expire
`NetGovernance::Schedule()` first runs after five minutes and repeats every five minutes, while orphan votes expire after ten minutes. With 1,000 retained parent hashes and a random sample of 100, each parent typically gets only one or two 10% chances to enter the scheduled batch, leaving approximately 81–90% without any scheduled retry before expiration. The immediate request targets only the announcing peer, so if that peer does not provide the parent, most retained orphans can expire without querying another peer. Use coverage-tracked rotating batches, a shorter retry interval, a longer expiration period, or another bounded mechanism that covers retained parents before expiration.
| if (vecHashesFiltered.size() > MAX_ORPHAN_OBJECT_REQUESTS_PER_TICK) { | ||
| Shuffle(vecHashesFiltered.begin(), vecHashesFiltered.end(), FastRandomContext()); | ||
| vecHashesFiltered.resize(MAX_ORPHAN_OBJECT_REQUESTS_PER_TICK); |
There was a problem hiding this comment.
🟡 Suggestion: Retry retained orphans before they expire
NetGovernance::Schedule() first runs after five minutes and repeats every five minutes, while orphan votes expire after ten minutes. With 1,000 retained parent hashes and a random sample of 100, each parent typically gets only one or two 10% chances to enter the scheduled batch, leaving approximately 81–90% without any scheduled retry before expiration. The immediate request targets only the announcing peer, so if that peer does not provide the parent, most retained orphans can expire without querying another peer. Use coverage-tracked rotating batches, a shorter retry interval, a longer expiration period, or another bounded mechanism that covers retained parents before expiration.
source: ['codex']
Issue being fixed or feature implemented
CGovernanceManager::ProcessVote()inserted a vote intocmmapOrphanVotesas soon asmapObjects.find(vote.GetParentHash())missed - before any masternode-membership or signature check, and with a zero misbehaviour penalty. The cache is sizedMAX_CACHE_SIZE = 1'000'000and keyed by the attacker-chosen parent hash, andCacheMultiMapstores each vote twice, so a peer can park roughly 600 MB of unvalidated data there.The larger problem is fan-out.
GetOrphanVoteObjectHashes()returned every orphan key uncapped, andNetGovernance::Schedule()sends oneMNGOVERNANCESYNCper key per connected peer every 5 minutes, ignoringfPauseSend. The orphan map was also serialised intogovernance.dat, so a flood survived restart and re-drove the fan-out on boot.This is reachable by any unauthenticated P2P peer; the only gate is the standard announce-then-request tracker. No masternode, quorum membership or RPC access is required.
What was done?
UpdateHash()/GetSignatureHash()covernParentHash, so votes cannot be repointed at fresh parent hashes without masternode keys.MAX_ORPHAN_VOTES = 1000.governance.dat.Note that the added signature verification now runs under
cs_store; this is bounded by the new discouragement score on invalid votes.How Has This Been Tested?
The first commit adds a regression test demonstrating that unvalidated votes fill the orphan cache, ordered before the fix.
Full build and test validation is delegated to CI on this PR; the changes were not built locally.
Breaking Changes
governance.datis bumped to v17. Upgrading nodes will discard their existing governance cache and re-sync governance data on first start. No consensus or P2P protocol change.Checklist: