Skip to content

perf: deserialize DKG messages once#7401

Open
thepastaclaw wants to merge 1 commit into
dashpay:developfrom
thepastaclaw:perf/dkg-single-deserialize
Open

perf: deserialize DKG messages once#7401
thepastaclaw wants to merge 1 commit into
dashpay:developfrom
thepastaclaw:perf/dkg-single-deserialize

Conversation

@thepastaclaw

@thepastaclaw thepastaclaw commented Jul 2, 2026

Copy link
Copy Markdown

perf: deserialize DKG messages once

Issue being fixed or feature implemented

DKG network message intake currently deserializes each accepted payload twice:

  • the p2p message handler deserializes a copied payload to validate the
    message structure and bounds
  • the DKG worker thread later deserializes the same bytes again to process the
    queued message

These messages contain BLS objects, so the redundant pass repeats non-trivial
decompression work on a hot network path during DKG rounds.

What was done?

  • Added typed pending-message queues for DKG contributions, complaints,
    justifications, and premature commitments.
  • Changed DKG intake to deserialize the payload once, run the existing
    structural checks on the typed object, and queue that validated object for
    the worker.
  • Kept oversize checks before deserialization.
  • Kept malformed-payload rejection and ban behavior.
  • Kept inventory hashes and duplicate tracking based on the original serialized
    wire bytes.

Benchmark evidence

A temporary local benchmark compared representative old double-deserialize and
new single-deserialize costs for QCONTRIB and QPCOMMITMENT payloads. The
benchmark was removed from the codebase because it modeled the deleted legacy
path and would be maintenance noise as a permanent bench_dash target.

Benchmark notes are preserved here:

https://gist.github.com/thepastaclaw/b612f49db3d2870d45c6a6ace5c4b0ad

Representative results:

DKG_QCONTRIB_DoubleDeserialize:      8,665,346.67 ns/op
DKG_QCONTRIB_SingleDeserialize:      4,296,081.31 ns/op
DKG_QPCOMMITMENT_DoubleDeserialize:    872,053.32 ns/op
DKG_QPCOMMITMENT_SingleDeserialize:    454,025.78 ns/op

This showed roughly a 2x reduction in the deserialization/decompression portion
of the DKG message intake path for representative payloads.

How Has This Been Tested?

Built on macOS arm64 with:

./autogen.sh
./configure --without-gui --disable-tests --disable-bench \
  --disable-wallet --without-miniupnpc --without-natpmp
make -C src llmq/libbitcoin_node_a-net_dkg.o -j4
make -C src llmq/libbitcoin_node_a-dkgsessionhandler.o -j4

Additional validation:

python3 test/lint/lint-includes.py
python3 test/lint/lint-circular-dependencies.py
git diff --check upstream/develop
COMMIT_RANGE=upstream/develop..HEAD test/lint/lint-whitespace.py

Breaking Changes

None.

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • 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 (for repository
    code-owners and collaborators only)

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@thepastaclaw

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thepastaclaw

thepastaclaw commented Jul 2, 2026

Copy link
Copy Markdown
Author

⏭️ Review skipped (commit e2265ef): local verification completed, but PR head moved to d291993 before posting.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR changes DKG pending-message handling to deduplicate before per-node quota accounting, removes header-side queued-message deserialization, and rewires NetDKG intake and batch processing to validate serialized wire data first and typed messages later. It adds wire-size and wire-structure checks, hardens intake parsing, and extends functional coverage for malformed BLS pubkey rejection.

Estimated code review effort: 4 (Complex) | ~50 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Peer
  participant NetDKG
  participant CDKGPendingMessages
  participant PendingBatch
  participant PreVerifyMessage

  Peer->>NetDKG: ProcessMessage(vRecv)
  NetDKG->>NetDKG: check wire size and wire structure
  NetDKG->>CDKGPendingMessages: store serialized pending message
  PendingBatch->>PendingBatch: deserialize queued CDataStream
  PendingBatch->>PendingBatch: validate typed message
  PendingBatch->>PreVerifyMessage: verify accepted message
Loading

Possibly related PRs

  • dashpay/dash#7314: Directly related refactor of CDKGPendingMessages and the DKG pending-message flow in the same files.
  • dashpay/dash#7397: Related DKG intake and structural validation hardening in src/llmq/net_dkg.cpp.
  • dashpay/dash#7408: Related src/llmq/net_dkg.cpp validation changes in the same typed DKG message path.

Suggested reviewers: knst, UdjinM6

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately captures the main change: deserializing DKG messages once.
Description check ✅ Passed The description is clearly related to the DKG intake refactor and performance goal described by the changeset.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/bench/dkg_deserialize.cpp (1)

115-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mirror the worker allocation in the double-deserialize benchmark.

The single-deserialize path includes std::make_shared, but the simulated old worker deserialize uses stack objects. If the removed worker path materialized shared typed messages, use make_shared here too so the comparison measures only the intended deserialize-count delta.

Proposed adjustment
-            llmq::CDKGContribution qc;
-            s >> qc;
-            ankerl::nanobench::doNotOptimizeAway(qc.contributions->blobs.size());
+            auto qc = std::make_shared<llmq::CDKGContribution>();
+            s >> *qc;
+            ankerl::nanobench::doNotOptimizeAway(qc->contributions->blobs.size());
-            llmq::CDKGPrematureCommitment qc;
-            s >> qc;
-            ankerl::nanobench::doNotOptimizeAway(qc.validMembers.size());
+            auto qc = std::make_shared<llmq::CDKGPrematureCommitment>();
+            s >> *qc;
+            ankerl::nanobench::doNotOptimizeAway(qc->validMembers.size());

Also applies to: 151-155

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bench/dkg_deserialize.cpp` around lines 115 - 120, The old worker
deserialize benchmark path in the double-deserialize setup is not mirroring the
shared-allocation behavior used by the single-deserialize path. Update the
worker-side deserialize blocks in the benchmark function(s) that use CDataStream
and llmq::CDKGContribution so they materialize the typed message with
std::make_shared, matching the removed worker path’s allocation pattern. Keep
the deserialize-count comparison focused on the intended cost difference by
aligning the allocation behavior between the single and double-deserialize
cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/llmq/dkgsessionhandler.h`:
- Around line 84-94: In CDKGPendingMessages, the duplicate-hash check currently
happens after incrementing messagesPerNode[from], so repeated duplicates can
consume the peer quota without adding work. Move the seenMessages.emplace(hash)
check ahead of the quota accounting in the same flow, then only increment
messagesPerNode[from] and continue enqueueing when the hash is new; keep the
existing logging and early returns in CDKGPendingMessages::AddMessage (or the
corresponding message-handling method).

---

Nitpick comments:
In `@src/bench/dkg_deserialize.cpp`:
- Around line 115-120: The old worker deserialize benchmark path in the
double-deserialize setup is not mirroring the shared-allocation behavior used by
the single-deserialize path. Update the worker-side deserialize blocks in the
benchmark function(s) that use CDataStream and llmq::CDKGContribution so they
materialize the typed message with std::make_shared, matching the removed worker
path’s allocation pattern. Keep the deserialize-count comparison focused on the
intended cost difference by aligning the allocation behavior between the single
and double-deserialize cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7c05047c-80ac-49ea-b4c5-0035e6ec8d3f

📥 Commits

Reviewing files that changed from the base of the PR and between 59d36a9 and c7e3cad.

📒 Files selected for processing (5)
  • src/Makefile.bench.include
  • src/bench/dkg_deserialize.cpp
  • src/llmq/dkgsessionhandler.cpp
  • src/llmq/dkgsessionhandler.h
  • src/llmq/net_dkg.cpp
💤 Files with no reviewable changes (1)
  • src/llmq/dkgsessionhandler.cpp

Comment thread src/llmq/dkgsessionhandler.h Outdated

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Clean, well-scoped perf refactor eliminating a redundant DKG deserialize. All intake invariants (oversize ban, malformed ban, hash-over-wire-bytes, per-node/duplicate suppression, own-message enqueue form) are preserved and independently verified by multiple reviewers. Only remaining suggestion is a Dash-specific lint tracking update for the new benchmark file.

Source: reviewers: opus (general), claude-sonnet-5 (general), gpt-5.5[high] (general), opus (dash-core-commit-history), claude-sonnet-5 (dash-core-commit-history), gpt-5.5[high] (dash-core-commit-history); verifier: opus; specialists: dash-core-commit-history

🟡 1 suggestion

Findings not posted inline (1)

These findings could not be anchored to the current diff, but they are still part of this review.

  • [SUGGESTION] test/util/data/non-backported.txt:4: Add the new Dash-specific benchmark to non-backported tracking — This PR adds src/bench/dkg_deserialize.cpp, a Dash-specific benchmark that is not matched by the existing src/bench/bls*.cpp glob. test/lint/lint-cppcheck-dash.py (line 69-73) builds its Dash-specific file list exclusively from patterns in this file via git ls-files, so the new benchmark will be silently skipped by that extra lint coverage until it is added. Add a matching entry to keep Dash-specific bench files under cppcheck.
🤖 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 `test/util/data/non-backported.txt`:
- [SUGGESTION] test/util/data/non-backported.txt:4: Add the new Dash-specific benchmark to non-backported tracking
  This PR adds `src/bench/dkg_deserialize.cpp`, a Dash-specific benchmark that is not matched by the existing `src/bench/bls*.cpp` glob. `test/lint/lint-cppcheck-dash.py` (line 69-73) builds its Dash-specific file list exclusively from patterns in this file via `git ls-files`, so the new benchmark will be silently skipped by that extra lint coverage until it is added. Add a matching entry to keep Dash-specific bench files under cppcheck.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Two suggestions remain against head 029fcc2. New finding in the latest delta: the dedupe-before-quota reorder correctly fixes duplicate resend quota charging, but a quota-exceeded drop now records the hash in seenMessages, so the same message can be poisoned for later legitimate delivery paths in the round. Carried-forward prior finding: src/bench/dkg_deserialize.cpp is still missing from test/util/data/non-backported.txt, so the extra cppcheck-dash lint pass will silently skip the new Dash-specific benchmark. No blocking issues; the benchmark allocation-shape fix is otherwise correct and well-scoped.

Source: reviewers: opus (general), claude-sonnet-5 (general), gpt-5.5[high] (general, failed), opus (dash-core-commit-history), claude-sonnet-5 (dash-core-commit-history), gpt-5.5[high] (dash-core-commit-history, failed); verifier: opus; specialists: dash-core-commit-history

🟡 2 suggestion(s)

Findings not posted inline (1)

These findings could not be anchored to the current diff, but they are still part of this review.

  • [SUGGESTION] test/util/data/non-backported.txt:4: Track new Dash-specific benchmark in non-backported.txt — This PR adds src/bench/dkg_deserialize.cpp, a Dash-specific benchmark that is not matched by the existing src/bench/bls*.cpp glob on line 4. test/lint/lint-cppcheck-dash.py builds its Dash-specific file list exclusively from the patterns in this file via git ls-files, so the new benchmark will be silently excluded from the extra cppcheck-dash coverage until it is listed here. Carried forward from the prior automated review at c7e3cad — still unresolved at head 029fcc2 (the tracked file was not touched by the latest push).
🤖 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.

New finding in the latest delta:
In `src/llmq/dkgsessionhandler.h`:
- [SUGGESTION] src/llmq/dkgsessionhandler.h:82-100: Quota-exceeded drop still poisons the message hash in seenMessages
  The reordered `PushPendingMessage` inserts `hash` into `seenMessages` at line 86 via `emplace(hash).second` before the quota check at line 92. If a sender is already at `maxMessagesPerNode` distinct-message quota, the function returns after having already committed the hash to `seenMessages`. Because `seenMessages` is keyed only by hash (not by sender), the same message subsequently delivered from any other peer — or via `AlreadyHave`/`HasSeen` paths — is now treated as "already seen" for the rest of the round and can never reach `pendingMessages`.
  
  This is a narrower variant of the bug fixed in e8d9b6d803: that commit correctly stopped duplicate resends from silently consuming a peer's quota, but the current ordering trades it for a case where a quota-exceeded drop poisons the hash for other, legitimate delivery paths. DKG already tolerates a single peer's messages being lost, so the operational impact is small, but the current shape unnecessarily couples the two intake failure modes. Splitting the seen-check (read-only) from the seen-insert (commit) so the hash is only recorded once the message is actually accepted preserves both invariants.

Carried-forward prior finding:
In `test/util/data/non-backported.txt`:
- [SUGGESTION] test/util/data/non-backported.txt:4: Track new Dash-specific benchmark in non-backported.txt
  This PR adds `src/bench/dkg_deserialize.cpp`, a Dash-specific benchmark that is not matched by the existing `src/bench/bls*.cpp` glob on line 4. `test/lint/lint-cppcheck-dash.py` builds its Dash-specific file list exclusively from the patterns in this file via `git ls-files`, so the new benchmark will be silently excluded from the extra cppcheck-dash coverage until it is listed here. Carried forward from the prior automated review at c7e3cad4 — still unresolved at head 029fcc228b (the tracked file was not touched by the latest push).

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Both prior findings are cleanly resolved by the latest delta: PushPendingMessage now only marks a hash as seen after the per-node quota check passes (no more quota-drop poisoning), and src/bench/dkg_deserialize.cpp is now tracked in non-backported.txt. No new issues in the delta; the full PR stack (single-deserialize refactor, benchmark, dedupe/quota fix) preserves the documented intake invariants. Commit-history agents suggested squashing fixup commits, but Dash merges PRs without squashing and these are low-value stylistic notes that don't warrant blocking.

Source: reviewers: opus (general), claude-sonnet-5 (general), gpt-5.5[high] (general, failed), opus (dash-core-commit-history), claude-sonnet-5 (dash-core-commit-history), gpt-5.5[high] (dash-core-commit-history, failed); verifier: opus; specialists: dash-core-commit-history

@thepastaclaw

Copy link
Copy Markdown
Author

CI note: the current Build source failures are the same depends-cache infrastructure issue tracked in #7403, not a failure in this PR's DKG changes. The source jobs are exiting before compilation at actions/cache/restore with fail-on-cache-miss for freshly built depends cache keys, for example run 28630838726 / linux64-build job 84908995980. No branch change is appropriate for this PR; the workflow/cache fix belongs in #7403.

@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from 506a5e4 to 55e1d5e Compare July 4, 2026 14:26
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from 55e1d5e to 98f20b1 Compare July 8, 2026 04:28
@thepastaclaw

Copy link
Copy Markdown
Author

Rebased this PR onto current upstream/develop and resolved the DKG conflict.

Preserved the single-deserialize typed queue path, kept the upstream contribution minSize lower-bound check, and retained the duplicate-before-quota / seen-after-quota behavior from the prior review fix.

Validation run locally on macOS arm64:

  • ./autogen.sh
  • ./configure --without-gui
  • make -C src llmq/libbitcoin_node_a-net_dkg.o bench/bench_dash -j4
  • git diff --check upstream/develop..HEAD
  • COMMIT_RANGE=upstream/develop..HEAD test/lint/lint-whitespace.py
  • COMMIT_RANGE=upstream/develop..HEAD test/lint/lint-cppcheck-dash.py
  • src/bench/bench_dash -filter='DKG_Q.*Deserialize'

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

@thepastaclaw Thanks for the update — rebasing onto upstream/develop and preserving the single-deserialize typed queue path, the contribution minSize check, and the duplicate-before-quota/seen-after-quota behavior all sound correct. The local validation steps (build, whitespace/cppcheck lint, and the DKG benchmark filter) look thorough. I'll kick off a fresh review of the rebased changes now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from 98f20b1 to 51cf424 Compare July 8, 2026 16:32
@thepastaclaw

thepastaclaw commented Jul 8, 2026

Copy link
Copy Markdown
Author

Reworked this PR to remove the temporary old-vs-new benchmark from the codebase.

Current head 51cf424a2167d106c7677d4d7b8b444989396bfe is now a single production commit touching only:

  • src/llmq/dkgsessionhandler.cpp
  • src/llmq/dkgsessionhandler.h
  • src/llmq/net_dkg.cpp

The benchmark evidence is preserved in the PR description and in this gist:

https://gist.github.com/thepastaclaw/b612f49db3d2870d45c6a6ace5c4b0ad

Validation run locally on macOS arm64:

./autogen.sh
./configure --without-gui --disable-tests --disable-bench \
  --disable-wallet --without-miniupnpc --without-natpmp
make -C src llmq/libbitcoin_node_a-net_dkg.o -j4
make -C src llmq/libbitcoin_node_a-dkgsessionhandler.o -j4
python3 test/lint/lint-includes.py
python3 test/lint/lint-circular-dependencies.py
git diff --check upstream/develop
COMMIT_RANGE=upstream/develop..HEAD test/lint/lint-whitespace.py
npx markdownlint-cli2 /tmp/dash-7401-body-updated.md

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

@thepastaclaw Thanks for the update. Reducing the PR to a single production commit scoped to src/llmq/dkgsessionhandler.cpp, src/llmq/dkgsessionhandler.h, and src/llmq/net_dkg.cpp, while preserving the benchmark evidence in the PR description and gist, makes sense for keeping the diff focused. I'll trigger a fresh review of the current head now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@PastaPastaPasta PastaPastaPasta marked this pull request as ready for review July 8, 2026 16:50
PastaPastaPasta
PastaPastaPasta previously approved these changes Jul 8, 2026

@PastaPastaPasta PastaPastaPasta left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 51cf424

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51cf424a21

ℹ️ 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".

Comment thread src/llmq/net_dkg.cpp Outdated
Comment thread src/llmq/dkgsessionhandler.h Outdated

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

I verified the current head 51cf424 against the active reviewer outputs and the older-SHA findings. The current PR diff is confined to the DKG typed pending-queue refactor; the quota poisoning issue is fixed, and the benchmark/non-backported findings are obsolete because src/bench/dkg_deserialize.cpp and test/util/data/non-backported.txt are no longer in the PR diff. No in-scope findings remain.

Source: reviewers: opus (claude general, failed), gpt-5.5 (codex general), opus (claude dash-core-commit-history, failed), gpt-5.5 (codex dash-core-commit-history); verifier: gpt-5.5 (codex; fallback: claude verifier unavailable); specialists: dash-core-commit-history

Prior finding reconciliation
  • Quota-exceeded drop still poisons the message hash in seenMessages — Fixed in the current code. src/llmq/dkgsessionhandler.h now performs a read-only duplicate check with seenMessages.count(hash) before the quota gate, returns on quota overflow without touching seenMessages, and only calls seenMessages.emplace(hash) after the quota check passes immediately before enqueue.
  • Track new Dash-specific benchmark in non-backported.txt — Obsolete at the assigned head. src/bench/dkg_deserialize.cpp is no longer present or tracked, and test/util/data/non-backported.txt is not part of the current PR diff.
  • Add the new Dash-specific benchmark to non-backported tracking — Obsolete at the assigned head for the same reason: the benchmark-only files from the older review are no longer in the PR diff.

Note: Claude/Opus lanes were unavailable due quota during this run; Codex reviewer and verifier lanes completed.

@thepastaclaw

Copy link
Copy Markdown
Author

Updated perf/dkg-single-deserialize with ce4180d18e8 to address the current review threads:

  • Pending DKG queues now retain bounded canonical CDataStream bytes rather than typed shared_ptr<Message> objects, preserving the previous compact retained-memory behavior.
  • DKG payload deserialization and structural checks now happen in the DKG phase worker immediately before PreVerifyMessage, so BLS-bearing objects are materialized under the active scheme at processing time while still deserializing only once.

Local validation:

  • ./autogen.sh
  • ./configure --without-gui --disable-tests --disable-bench --disable-wallet --without-miniupnpc --without-natpmp
  • make -C src llmq/libbitcoin_node_a-net_dkg.o -j4
  • make -C src llmq/libbitcoin_node_a-dkgsessionhandler.o -j4
  • git diff --check upstream/develop..HEAD
  • python3 test/lint/lint-includes.py
  • python3 test/lint/lint-circular-dependencies.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce4180d18e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/llmq/net_dkg.cpp Outdated
@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from ce4180d to 48de1d0 Compare July 8, 2026 17:37
@thepastaclaw

Copy link
Copy Markdown
Author

Updated perf/dkg-single-deserialize with 48de1d0c4f2 to address the latest Codex review thread.

What changed:

  • DKG intake now validates a temporary copy of the canonical wire bytes before retaining anything in the pending queue.
  • Malformed or structurally invalid DKG payloads are banned immediately with malformed DKG message before PeerEraseObjectRequest / PushPendingMessage.
  • The pending queues still retain canonical CDataStream bytes, and the DKG worker still deserializes those bytes once at processing time.

Local validation:

  • ./configure --without-gui --disable-tests --disable-bench --without-miniupnpc --without-natpmp
  • make -C src llmq/libbitcoin_node_a-net_dkg.o -j4
  • make -C src llmq/libbitcoin_node_a-dkgsessionhandler.o -j4
  • make -C src dashd -j4
  • test/functional/feature_llmq_dkg_intake.py
  • git diff --check upstream/develop..HEAD
  • COMMIT_RANGE=upstream/develop..HEAD test/lint/lint-whitespace.py
  • python3 test/lint/lint-includes.py
  • python3 test/lint/lint-circular-dependencies.py
  • python3 -m py_compile test/functional/feature_llmq_dkg_intake.py

@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from c39cfca to 99ef16e Compare July 8, 2026 17:46
@thepastaclaw

Copy link
Copy Markdown
Author

CI note for current head 99ef16ea6b4f4796dc49c21f938aeb681cc3b67d: win64-build / Build source failed in run 28963666826, but the log does not point at this PR's DKG changes.

The failed job reports checking for makensis... none / configure: WARNING: makensis not found. Cannot create installer., then exits at the packaging step with error: could not build dashcore-23.1.7-win64-setup.exe. The same log also shows a relic_conf.h include race in bls/bls.cpp, outside this PR's touched files. No branch change looks appropriate from this failure; the remaining source-build jobs are still running/pending.

CodeRabbit is still paused on this branch and the latest command review covered an older head, so triggering one current-head review now.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

@thepastaclaw Thanks for the CI note. The win64-build failure — missing makensis blocking installer packaging, plus the unrelated relic_conf.h include race in src/bls/bls.cpp — indeed looks like pre-existing environment/packaging noise unrelated to the DKG changes in this PR, since none of src/llmq/dkgsessionhandler.cpp, src/llmq/dkgsessionhandler.h, or src/llmq/net_dkg.cpp touch BLS headers or the installer toolchain. No branch changes needed on your end for this.

I'll trigger a fresh review of the current head (99ef16ea6b4f4796dc49c21f938aeb681cc3b67d) now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thepastaclaw

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/llmq/net_dkg.cpp`:
- Around line 270-286: DeserializeAndCheckDKGMessage currently accepts
serialized DKG payloads with extra trailing bytes after ds >> *msg, which can
let the same logical message be treated as distinct. Update the
DeserializeAndCheckDKGMessage template in net_dkg.cpp to verify the stream is
fully consumed after deserialization, and reject the message if any unread bytes
remain; keep the existing structure check and reset msg on failure as it does
today.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8c5ab992-4b94-448f-ae2f-030acfc930bf

📥 Commits

Reviewing files that changed from the base of the PR and between ce4180d and 99ef16e.

📒 Files selected for processing (3)
  • src/llmq/dkgsessionhandler.cpp
  • src/llmq/dkgsessionhandler.h
  • src/llmq/net_dkg.cpp
💤 Files with no reviewable changes (1)
  • src/llmq/dkgsessionhandler.cpp

Comment thread src/llmq/net_dkg.cpp
@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from 99ef16e to ea155ab Compare July 8, 2026 19:29
@thepastaclaw

Copy link
Copy Markdown
Author

Updated perf/dkg-single-deserialize with ea155abd7ea to address CodeRabbit’s trailing-bytes finding.

What changed:

  • DeserializeAndCheckDKGMessage now rejects unread trailing bytes after typed DKG message deserialization.
  • The failure path matches the existing malformed deserialize / structural-check failures: reset the typed message and return false, which keeps the existing malformed DKG message peer-misbehavior path.

Local validation:

  • git diff --check upstream/develop..HEAD
  • make -C src llmq/libbitcoin_node_a-net_dkg.o -j4

Replied to and resolved the review thread: #7401 (comment)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

@thepastaclaw Thanks for the update. Rejecting unread trailing bytes in DeserializeAndCheckDKGMessage and routing that failure through the existing malformed-message/misbehavior path is the right fix for the earlier finding. I'll trigger a fresh review of the current head now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Source: reviewers: opus/Claude general failed quota; gpt-5.5/Codex general completed; opus/Claude dash-core-commit-history failed quota; gpt-5.5/Codex dash-core-commit-history completed. Verifier: gpt-5.5/Codex fallback after opus verifier quota failure. Specialist: dash-core-commit-history.

Prior reconciliation: no carried-forward prior findings remain active. The quota-exceeded seenMessages poisoning finding remains fixed at ea155abd, and the benchmark/non-backported findings remain obsolete because the cumulative PR diff contains no benchmark, Makefile, or non-backported tracking changes.

New findings in latest delta: none. Additional cumulative findings: none. I checked the latest ce4180d1..ea155abd delta and the full DKG typed pending-queue/message-intake refactor and found no in-scope defects.

Note: GitHub does not allow me to submit APPROVE on my own PR, so this clean review is posted as a COMMENT.

@thepastaclaw

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from ea155ab to d1111fd Compare July 8, 2026 20:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1111fdc9b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/llmq/net_dkg.cpp

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Source: reviewers: opus/Claude general failed quota; gpt-5.5/Codex general completed; opus/Claude dash-core-commit-history failed quota; gpt-5.5/Codex dash-core-commit-history completed. Verifier: gpt-5.5/Codex fallback after opus verifier quota failure. Specialist: dash-core-commit-history.

Carried-forward prior findings: none; the prior review found no active issues. New findings in latest delta: one blocking regression in DKG message intake, where malformed verified-peer payloads are queued before structural validation and no longer trigger the existing immediate "malformed DKG message" rejection path. Additional cumulative findings: none.

🔴 1 blocking

Prior Reconciliation

Carried-forward prior findings: none. The prior exact-SHA review at ea155abd found no active issues, and the older quota/benchmark findings remained resolved or obsolete.

New Findings In Latest Delta

  • [BLOCKING] src/llmq/net_dkg.cpp:505-525: Malformed DKG payloads are queued before structural validation — The previous code deserialized and structurally checked each DKG payload in ProcessMessage before inserting it into a pending queue, and rejected failures with PeerMisbehaving(..., "malformed DKG message"). The current code moves the raw CDataStream into the pending queue first and only calls DeserializeAndCheckDKGMessage later from ProcessPendingMessageBatch. That regresses the existing intake hardening contract and functional tests: feature_llmq_dkg_intake.py asserts malformed and under-min QCONTRIB payloads are rejected during receipt with the "malformed DKG message" reason, before retention. The later worker path logs a different message and only penalizes when the queued batch is processed, so the current tree no longer satisfies the tested behavior.

Additional Cumulative Findings

None.

🤖 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/llmq/net_dkg.cpp`:
- [BLOCKING] src/llmq/net_dkg.cpp:505-525: Malformed DKG payloads are queued before structural validation
  The previous code deserialized and structurally checked each DKG payload in ProcessMessage before inserting it into a pending queue, and rejected failures with PeerMisbehaving(..., "malformed DKG message"). The current code moves the raw CDataStream into the pending queue first and only calls DeserializeAndCheckDKGMessage later from ProcessPendingMessageBatch. That regresses the existing intake hardening contract and functional tests: feature_llmq_dkg_intake.py asserts malformed and under-min QCONTRIB payloads are rejected during receipt with the "malformed DKG message" reason, before retention. The later worker path logs a different message and only penalizes when the queued batch is processed, so the current tree no longer satisfies the tested behavior.

Note: GitHub does not allow me to submit REQUEST_CHANGES on my own PR, so this is posted as a COMMENT while preserving the verified blocking finding.

@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from d1111fd to e2265ef Compare July 8, 2026 20:53
@thepastaclaw

Copy link
Copy Markdown
Author

Updated perf/dkg-single-deserialize with e2265ef810a03fe80ed0797df1fe35c53cc1a769 to address the current Codex review thread.

What changed:

  • DKG intake now performs a cheap wire-shape check before PeerEraseObjectRequest / PushPendingMessage.
  • Malformed/truncated pushed DKG payloads and under-min QCONTRIB blob counts are rejected immediately through the existing malformed DKG message misbehavior path.
  • Pending queues still retain canonical serialized CDataStream bytes, and typed deserialization remains in the DKG worker before PreVerifyMessage, preserving the CI-friendly serialized-queue design from the prior fix.

Local validation:

  • git diff --check HEAD~1..HEAD
  • make -C src llmq/libbitcoin_node_a-net_dkg.o dashd -j4
  • python3 test/lint/lint-includes.py
  • python3 test/lint/lint-circular-dependencies.py
  • test/functional/feature_llmq_dkg_intake.py --portseed=74011729
  • test/functional/feature_dip3_v19.py --configfile=/Users/claw/Projects/dash/worktrees/tracker-1729/test/config.ini --tmpdir=/private/tmp/dash_pr7401_feature_dip3_v19_wirecheck --randomseed=8319748680785945219 --timeout-factor=2 --portseed=74011730

Replied to and resolved the review thread: #7401 (comment)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

@thepastaclaw Thanks for the update. Adding the cheap wire-shape check ahead of PeerEraseObjectRequest / PushPendingMessage to reject malformed/truncated payloads and under-min QCONTRIB blob counts immediately — while keeping canonical CDataStream bytes in the pending queues and typed deserialization in the worker before PreVerifyMessage — looks like the right approach and preserves the single-deserialize design. I'll trigger a fresh review of the current head (e2265ef810a03fe80ed0797df1fe35c53cc1a769) now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2265ef810

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/llmq/net_dkg.cpp Outdated
CheckDKGMessageStructure previously deserialized a copy of the pushed DKG payload on the network thread to enforce param-derived structural bounds, then the original bytes were retained on the pending queue and deserialized again on the DKG worker thread. For QCONTRIB the BLS point decompression is the dominant cost, so paying it twice per message is meaningful.

Intake now deserializes the payload once, runs the same param-derived bound check on the typed object, and enqueues an already-typed std::shared_ptr<Message>. The DKG worker no longer deserializes on pop. CDKGPendingMessages is turned into a class template parameterised on the message type so each queue stores its typed message directly.

Preserved:

- Oversize rejection with peer misbehavior score 100 before any deserialization or retention.

- Malformed-payload rejection with score 100: a deserialize throw or a bound violation triggers the same ban path as before.

- Inventory hash: still computed over the raw wire bytes at intake, matching what peers compute for the same payload; AlreadyHave / HasSeen / PeerEraseObjectRequest semantics unchanged.

- EnqueueOwn still serializes with SER_NETWORK/PROTOCOL_VERSION and hashes those bytes, matching the old serialize-then-hash form.

Benchmark notes for the removed old-vs-new simulation are preserved at https://gist.github.com/thepastaclaw/b612f49db3d2870d45c6a6ace5c4b0ad.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thepastaclaw thepastaclaw force-pushed the perf/dkg-single-deserialize branch from e2265ef to d291993 Compare July 8, 2026 21:05
@thepastaclaw

Copy link
Copy Markdown
Author

Updated the branch for the latest Codex review.

  • Commit: d291993
  • Change: the pre-queue DKG wire checker now deserializes and validates BLS public keys, signatures, and secret keys from the copied stream before queuing, so malformed non-zero BLS encodings hit the existing malformed DKG message misbehavior path immediately while queues still retain serialized bytes.
  • Tests: git diff --check HEAD~1..HEAD; make -C src llmq/libbitcoin_node_a-net_dkg.o dashd -j4; test/functional/feature_llmq_dkg_intake.py --portseed=74011729; seeded test/functional/feature_dip3_v19.py --randomseed=8319748680785945219 --timeout-factor=2 --portseed=74011731.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

@thepastaclaw Thanks for the update. Extending the pre-queue wire checker to deserialize and validate BLS public keys, signatures, and secret keys from the copied stream — so malformed non-zero BLS encodings are caught immediately via the existing malformed DKG message misbehavior path, while the pending queues still retain serialized bytes for the worker's single deserialization — looks like the right fix. I'll trigger a fresh review of the current head (d291993e8a379b3683c703a28351bb9a8929186f) now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thepastaclaw

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants