core/parsigex: bound work triggered by received messages - #4637
Conversation
Add three guards before the expensive decode and signature verification in the parsigex handler: - Cap the wire size of a parsigex message at 32MB via p2p.WithReadLimit, matching the treatment consensus and peerinfo already receive. The protocol previously used the 128MB default. - Reject sets holding more partial signatures than the cluster has validators. Every entry is SSZ/JSON unmarshalled before any of it is authenticated, so set cardinality drove decode and allocation cost. - Drop duties whose deadline has passed. The duty gater only bounds how far in the future a duty may be, so partial signatures for arbitrarily old slots reached full verification before parsigdb discarded them. Duty types that never expire (exits, builder registrations) are exempt from the deadline check. DKG duty slots are sigType constants rather than beacon slots, so the DKG exchanger passes a never-expires deadline. category: bug ticket: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR hardens the core/parsigex libp2p handler against inbound-work DoS by bounding allocation and decode/verify work for received messages, and updates DKG/core wiring and tests to provide the new guard parameters (deadline + max set size).
Changes:
- Add a 32MB per-message read limit for parsigex traffic and introduce pre-decode guards (expired-duty drop + max set cardinality bound).
- Extend
NewParSigExto accept a duty deadline function and a maximum received set size (cluster validator count), and thread these through the core workflow and DKG exchanger. - Add internal tests covering the new handler guard behavior.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| dkg/protocolsteps_internal_test.go | Update DKG test to pass validator-bound to the exchanger. |
| dkg/protocol_reshare.go | Pass validator count into the DKG exchanger. |
| dkg/protocol_replaceoperator.go | Pass validator count into the DKG exchanger. |
| dkg/protocol_removeoperators.go | Pass validator count into the DKG exchanger. |
| dkg/protocol_addoperators.go | Pass validator count into the DKG exchanger. |
| dkg/exchanger.go | Extend exchanger constructor, provide non-expiring deadline func for DKG duties, and pass max set size into parsigex. |
| dkg/exchanger_internal_test.go | Update DKG exchanger tests for new constructor signature. |
| dkg/dkg.go | Pass total validator count into exchanger creation. |
| core/parsigex/parsigex.go | Add message read limit + pre-decode guards; extend constructor signature and internal state. |
| core/parsigex/parsigex_test.go | Update integration-style parsigex test to provide deadline/maxSetSize args. |
| core/parsigex/parsigex_internal_test.go | Add unit tests verifying expired-duty and oversized-set handling occurs before verification. |
| app/app.go | Wire deadline function + validator-count bound into core workflow parsigex creation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Apply the read limit last so it holds regardless of caller options, restore the noopDeadliner doc comment, and assert the test helper builds a set of the requested cardinality. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4637 +/- ##
==========================================
+ Coverage 58.16% 58.21% +0.05%
==========================================
Files 247 247
Lines 34056 34066 +10
==========================================
+ Hits 19807 19830 +23
+ Misses 11779 11761 -18
- Partials 2470 2475 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The duty that can differ the most in size between consensus and parsigex is probably the attestation. Other duties are either the same data or infrequent enough / with small amount of validators. Attestations though agree on 1 single small piece of data during consensus and multiple validators send their partial signatures during parsigex. Have we checked the size of a parsigex for attestations when the cluster runs 1000 validators? And 20 000? I know 20k is not a cluster we have atm, but we have seen inquiries for that big clusters before. With that said, I'd rather we do this research first, before merging that.
This is simply... an optimisation to fail faster, is that right? |



Add three guards before the expensive decode and signature verification in the parsigex handler, so a peer cannot make a node do unbounded work.
p2p.WithReadLimit. Parsigex registered with no options and so used the 128MB default, while consensus (32MB) and peerinfo (1MB) already set tighter limits. The reader allocates the full declared length before reading the body, so this bounds allocation before parsing.ParSignedDataSetFromProtoSSZ/JSON unmarshals every entry before any of it is authenticated, so cardinality — not signature verification — drove decode and allocation cost.NewDutyGateronly bounds how far in the future a duty may be (its doc notes past duties are the Deadliner's job), but the deadliner runs inparsigdb.StoreExternal, downstream of verification. Partial signatures for arbitrarily old slots therefore reached full verification before being discarded, which also meant the work was not rate-limited by the duty schedule.Duty types that never expire (exits, builder registrations) are exempt from the deadline check. DKG duty slots are
sigTypeconstants rather than beacon slots, so the DKG exchanger passes a never-expires deadline func and the number of validators as the set bound.Note this hardens parsigex only. The same 128MB default applies to the priority protocol and the DKG handlers, and charon disables the libp2p resource manager entirely (
libp2p.ResourceManager(new(network.NullResourceManager))inapp/app.go), which leaves inbound concurrent streams unbounded since yamux delegates that limit to the resource manager. Both are worth addressing separately.category: bug
ticket: none