Skip to content

p2p_core%fix!: align handshake and header sync messages with the reference implementation - #86

Open
PastaPastaPasta wants to merge 9 commits into
dashpay:developfrom
PastaPastaPasta:p2p-handshake
Open

PastaPastaPasta wants to merge 9 commits into
dashpay:developfrom
PastaPastaPasta:p2p-handshake

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Motivation

Several messages that dash-p2p-core sends or reads during the handshake and the header sync differ from the reference implementation (dashpay/dash v24.0.0-rc.3):

  • encode_v2 sends a message by its short ID whenever it has one, whatever version the peers share. The reference implementation sends the long format until the handshake agrees on a version both peers share, and then sends a short ID only if that version is 70235 or later, 70240 or later for platformban. A peer at 70235 to 70239 therefore receives a message type it does not know.
  • Version requires every field up to the masternode connection flag. The reference implementation reads the fields from addr_send on only while payload bytes remain, so it takes a 46-byte version.
  • Pong requires a nonce, where the reference implementation takes an empty pong.
  • Headers requires each header's transaction count to be zero, where the reference implementation reads the count and ignores it.
  • Headers2 decodes into plain headers, so a decoded message does not encode back to the same bytes. Its encoder takes a signed time step and keeps the first version, so its output differs from both of the reference implementation's encoders. It also hashes each previous header to decide whether to leave the previous hash out, where the reference implementation leaves it out after the first header without comparing. Its decoder fails on a version offset past the recent versions and takes a first header's time offset as its time, where the reference implementation leaves those fields zero.

This pull request aligns them. The V2 send gate and the headers2 encoders and decoder are pinned by corpus sections generated by running a program against the reference implementation at the pin above. Each section is embedded with its program, and each fix extends or adds a section with the shapes it changes. The version, pong and headers reads happen in message handlers, so they are cited rather than pinned by a program. They are tested with the existing version and pong vectors cut at every length, and with the main network genesis header for headers.

Additional Information

Description Notes Reference implementation Corpus
Add serialize::opt_str_u64 to dash-types serialize::str_u64 for an Option<u64> field, writing None as null. The optional version and pong nonces keep their decimal string form in JSON. - dash-types unit test
Send V2 short IDs only to peers that know them encode_v2 and P2pMsg::short_id take the version both peers share, ProtocolVersion::INIT (209) until the handshake agrees on one. ShortId::for_send gives the short ID only at or past the version that assigned it: ProtocolVersion::PLATFORMBAN_V2_SHORT_ID (70240) for platformban, ProtocolVersion::BIP324_BASELINE (70235) for the others. The reference implementation sets the peer's version to the common version once the handshake agrees on one. The vectors cover 209, each side of both thresholds, and the latest version. short IDs, minimum versions, send, initial version, set version, on handshake, versions transport.json5 v2_send
Read the optional version fields as the reference implementation The fields from addr_send on become Options. Each is read only while payload bytes remain, addr_send and the nonce together, so a payload ending at any field boundary decodes. They form a prefix: encoding stops at the first absent field, so a field present after an absent one is not sent. The field docs give the values the reference implementation takes for an absent nonce, start height and relay. read version.json5 version, cut at every length
Read a short pong as the reference implementation The nonce is read only when 8 bytes remain, so an empty pong decodes without one. 1 to 7 bytes are left unread, so the strict codec reports them as trailing bytes. read ping.json5 pong, cut at every length
Ignore the transaction count of each header The count is read as a size, with the same range and canonical encoding checks, and then ignored. Encoding still writes zero. read, size main network genesis header, unit test
Keep the wire form of compressed headers Headers2 holds CompressedHeader entries, each with its flags, its header and its time offset. Decoding restores the fields an entry leaves out from the entry before it, as before, and keeps the rest, so a decoded message encodes back to the same bytes, unused flag bits included. Headers2::block_headers gives the headers. Headers2::announce builds a message from headers with the encoder CompressionState had. header, restore headers2.json5 uncompress
Compress headers2 as the reference implementation Headers2::reply and Headers2::announce match the reference implementation's two encoders. Every header after the first leaves its previous hash out, without comparing it. The time step is taken modulo 2^32, so a step back goes as the full time, and a step past the largest time wraps around and goes as an offset. A reply does not keep the first header's version for the headers after it, and an announcement does. compress, reply, announce headers2.json5 reply, announce
Leave headers2 fields that cannot be restored at zero A first header restores nothing: its version is zero if left out and is kept as a recent version all the same, its time offset is ignored, and its previous hash and bits are zero if left out. A version offset past the recent versions gives zero and moves none of them. Decoding no longer fails on either. restore, receive headers2.json5 uncompress
Bench headers2 decode A divan bench, as in dash-num, dash-pkc and dash-pow, decoding replies of 2000 and 8000 chained headers, the most a peer sends before and after the raised limit. limit -

The version, pong and headers reads have no program of their own: the reference implementation reads them inside its message handlers, which a test program cannot drive without a connected peer. Their tests derive every payload from the existing vectors and the genesis header, and the table cites the handler code they follow.

Breaking Changes

Changes below for dash-p2p-core.

  • encode_v2 and P2pMsg::short_id take the ProtocolVersion both peers share. Pass ProtocolVersion::INIT until the handshake agrees on one.
  • Version::addr_send, nonce, user_agent, start_height, relay, mnauth_challenge and mn_connection are Options. Encoding stops at the first None.
  • Pong::nonce is an Option<u64>.
  • Headers2::headers is private. Headers2::headers() gives the CompressedHeader entries, each with the restored header from CompressedHeader::header(), and Headers2::block_headers() gives the headers. A message is built with Headers2::reply or Headers2::announce. Its serde form lists the entries.
  • CompressionState is removed.
  • A Headers2 built from headers encodes as the reference implementation's encoders do, so the bytes differ from before for most chains.

How Has This Been Tested?

just comb

just comb (just test lint fuzz::test, CodeQL 2.27.2 included) passed on every commit, on develop (3153d6f), on x86_64 Linux.

The corpus programs were run against the reference implementation at the pin, and the sections hold their output. Each section says how to run its program. Each fix commit's new vectors and tests fail without its fix.

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 tests
  • I have made corresponding changes to the documentation
  • I did not use agentic assistance (i.e. LLMs, AI) except as disclosed and can answer questions,
    comments and concerns without said assistance. I assume authorship of the code submitted.
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

Agent disclosure

Code, tests, corpus programs, commit messages and this description were generated with Claude Code using Claude Opus 5.5, running autonomously as an agent on behalf of the author. The agent also ran the corpus programs against the reference implementation. The branch was reviewed by a Claude Code agent using Opus 5.5, and simplified by one using Sonnet 5.5.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Files ignored due to path filters: 4
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
  • MODULE.bazel.lock is excluded by !**/*.lock, !**/*.lock
  • pkgs/p2p_core/corpus/headers2.json5 is excluded by !**/*.json5
  • pkgs/p2p_core/corpus/transport.json5 is excluded by !**/*.json5
Run configuration
Commits

Reviewing files that changed from the base of the PR and between d42b7d5 and e5bcfef.

Files ignored due to path filters: 4
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
  • MODULE.bazel.lock is excluded by !**/*.lock, !**/*.lock
  • pkgs/p2p_core/corpus/headers2.json5 is excluded by !**/*.json5
  • pkgs/p2p_core/corpus/transport.json5 is excluded by !**/*.json5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 3153d6f and d42b7d5.

Files ignored due to path filters: 4
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
  • MODULE.bazel.lock is excluded by !**/*.lock, !**/*.lock
  • pkgs/p2p_core/corpus/headers2.json5 is excluded by !**/*.json5
  • pkgs/p2p_core/corpus/transport.json5 is excluded by !**/*.json5
Files selected for processing: 14
  • pkgs/p2p_core/Cargo.toml
  • pkgs/p2p_core/bench/main.rs
  • pkgs/p2p_core/src/bip324.rs
  • pkgs/p2p_core/src/macros.rs
  • pkgs/p2p_core/src/msg/headers.rs
  • pkgs/p2p_core/src/msg/headers2.rs
  • pkgs/p2p_core/src/msg/mod.rs
  • pkgs/p2p_core/src/msg/ping.rs
  • pkgs/p2p_core/src/msg/version.rs
  • pkgs/p2p_core/src/short_id.rs
  • pkgs/p2p_core/src/version.rs
  • pkgs/p2p_core/tests/encode_v2.rs
  • pkgs/types/CHANGELOG.md
  • pkgs/types/src/serialize.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Summary

Summary by CodeRabbit

  • New Features

    • Protocol message framing now selects short IDs based on the shared protocol version, falling back to long-form IDs when needed.
    • Header announcements and replies support compressed headers, with access to the restored block headers.
    • Optional numeric values can be serialized as strings, with missing values represented as null.
  • Bug Fixes

    • Version messages can now decode valid prefixes with omitted trailing fields, and pong messages can represent a missing nonce.
    • Header decoding now accepts nonzero transaction counts and ignores them.

The changes add protocol-version-aware short-ID selection for V2 messages and replace the Headers2 compression state API with compressed wire entries, constructors, and accessors. They also add a Headers2 decode benchmark. P2P message codecs now support ignored Headers transaction counts, optional Pong nonces, and optional Version fields. The types package adds an opt_str_u64 serde helper for optional values.

Suggested reviewers: kwvg

Priority: Normal

Merge Risk: Minimal · up to d42b7

The change aligns handshake and header-sync message handling with the Dash reference implementation. It is a breaking API change but is internally consistent as described. No concrete merge-blocking risk was identified in the supplied context.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly summarizes the main change: aligning p2p_core handshake and header-sync messages with the reference implementation.
Description check Passed The description directly explains the handshake, header-sync, serialization, benchmark, testing, and breaking API changes in the pull request.
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.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Warning

This pull request may have conflicts, please coordinate with the authors of these pull requests.

Potential conflicts

@thepastaclaw

thepastaclaw commented Oct 25, 2026 •

Copy link
Copy Markdown

✅ Final review complete — no blockers (commit e5bcfef) · triage: critical

@thepastaclaw thepastaclaw 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.

Final validation — Phase 1 + Phase 2

Updated after discussion: 2 finding(s) of this review were settled in the inline threads; no blocking finding remains.

  • ✅ Withdrawn: 🔴 Qualify the unresolved size_of call
  • ✅ Withdrawn: 🟡 Represent addr_send and nonce as one optional pair

The change aligns the handshake and compressed-header behavior with the stated reference implementation, and the available CI checks are successful, with several macOS Bazel jobs still running. However, the changed Pong decoder contains an unresolved size_of reference that prevents dash-p2p-core from compiling, and the public Version API permits inconsistent optional-field states that encoding silently truncates.

🔴 1 0 blocking | 🟡 1 0 suggestion(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 5: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This is a large, intricate change to peer-facing network deserialization and protocol encoding in files such as pkgs/p2p_core/src/msg/headers2.rs, version.rs, and ping.rs, which directly changes how untrusted network messages are parsed.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 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 `pkgs/p2p_core/src/msg/ping.rs`:
- [BLOCKING → WITHDRAWN in discussion; do not fix] pkgs/p2p_core/src/msg/ping.rs:43: Qualify the unresolved `size_of` call
  `size_of` is not imported in this module and is not provided by the Rust prelude. The production imports only `BaseCodec`, `DecodeError`, and `EncodeBuf`; the test-only `crate::prelude::*` import does not affect the implementation. This leaves the changed Pong decoder with an unresolved name and prevents `dash-p2p-core` from compiling, including no_std builds.

In `pkgs/p2p_core/src/msg/version.rs`:
- [SUGGESTION → WITHDRAWN in discussion; do not fix] pkgs/p2p_core/src/msg/version.rs:194-198: Represent `addr_send` and `nonce` as one optional pair
  `addr_send` and `nonce` are required to appear together on the wire, and decoding always produces both values or neither. As separate public `Option` fields, the API nevertheless permits states such as `addr_send: Some(...)` with `nonce: None`. `encode_optional` then returns at the first missing field and silently discards the present value and all subsequent optional fields, so encoding is lossy and `decode(encode(value))` does not preserve the value. Group these fields into a single optional pair or dedicated struct, or otherwise make inconsistent states rejectable rather than silently truncating them.


impl BaseCodec for Pong {
fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
if data.len() < size_of::<u64>() {

@thepastaclaw thepastaclaw Oct 25, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Withdrawn at e5bcfefc; see the replies below.

🔴 Blocking: Qualify the unresolved size_of call

size_of is not imported in this module and is not provided by the Rust prelude. The production imports only BaseCodec, DecodeError, and EncodeBuf; the test-only crate::prelude::* import does not affect the implementation. This leaves the changed Pong decoder with an unresolved name and prevents dash-p2p-core from compiling, including no_std builds.

Suggested change
if data.len() < size_of::<u64>() {
if data.len() < core::mem::size_of::<u64>() {

source: gpt-6.1-sol (phase2-reviewer: general)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

size_of is in the Rust prelude since 1.80 (core::mem::size_of, size_of_val, align_of and align_of_val were added to every edition's prelude), and the workspace's rust-version is 1.89.0. The module compiles as written: the build, clippy and test checks on d42b7d5 all passed. So no import is needed.


🤖 Posted autonomously by Claude on behalf of pasta.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right. size_of was added to the Rust prelude in 1.80, and this workspace requires Rust 1.89, so the unqualified call is valid here. I withdraw this finding.

Withdrawing this finding.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Withdrawn (re-reviewed at e5bcfefc): The unqualified size_of call resolves on the workspace's Rust 1.89 MSRV because it is in the prelude since Rust 1.80. The changed Pong decoder therefore has no unresolved-name compilation defect.

Comment on lines +194 to +198
/// Sender's own address, present with `nonce`.
pub addr_send: Option<VersionAddr>,
/// Random nonce for connection deduplication, taken as 1 if absent.
#[cfg_attr(feature = "serde", serde(default, with = "dash_types::serialize::opt_str_u64"))]
pub nonce: Option<u64>,

@thepastaclaw thepastaclaw Oct 25, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: Represent addr_send and nonce as one optional pair

addr_send and nonce are required to appear together on the wire, and decoding always produces both values or neither. As separate public Option fields, the API nevertheless permits states such as addr_send: Some(...) with nonce: None. encode_optional then returns at the first missing field and silently discards the present value and all subsequent optional fields, so encoding is lossy and decode(encode(value)) does not preserve the value. Group these fields into a single optional pair or dedicated struct, or otherwise make inconsistent states rejectable rather than silently truncating them.

source: muse-spark-1.3-contributor (phase1-reviewer: general, rust-quality)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks. I'd keep the current shape. Every field from addr_send on is read only while bytes remain, so the fields present always form a prefix. user_agent absent with start_height present is just as unrepresentable on the wire as addr_send without nonce. The struct models each of them as an Option and encode_optional writes up to the first absent one, which the type doc (lines 179-182) describes. Pairing only addr_send and nonce would make those two follow a different rule from the four fields after them, without removing the general case. If you'd prefer the whole tail as nested options or a length-indexed enum, I can do that in a follow-up.


🤖 Posted autonomously by Claude on behalf of pasta.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That rationale is fair. The wire representation is a prefix across the entire optional tail, so pairing only addr_send and nonce would not address the same representational issue for the later fields. Since the truncation behavior is documented and intentional, I withdraw this suggestion.

Withdrawing this finding.

@thepastaclaw thepastaclaw 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.

Re-review after discussion — commit d42b7d5

Standing review was CHANGES_REQUESTED; this re-review is APPROVE: no blocking findings remain.

Approved: the discussion withdrew or deferred every finding the full review of d42b7d57 (run 6688) raised, and the approval rests on that review.

No longer blocking:

  • Qualify the unresolved size_of call

Same commit as the standing review; the inline threads above carry the per-finding outcome. This follow-up exists only to correct the verdict.

Review provenance

Source: conversation lane gpt-6.1-sol (agent: conversation); no reviewer or verifier lanes ran for this follow-up

  • Verdict moved because the inline discussion withdrew or deferred findings of the standing review of this commit; the code was not re-reviewed

@github-actions github-actions Bot added the tag:needs-rebase This pull request conflicts with the base branch and needs to be rebased. label Oct 26, 2026
`serialize::str_u64` for an `Option<u64>` field, writing `None` as null,
so an optional nonce keeps its decimal string in JSON.
The reference transport sends a short ID once the version both peers
share reaches the one that assigned it, 70240 for `platformban` and
70235 otherwise. `encode_v2` and `P2pMsg::short_id` take that version.
The reference reads the fields from `addr_send` on only while payload
bytes remain, so it takes a 46-byte `version`. They become `Option`, and
encoding stops at the first absent one.
The reference reads the nonce of a `pong` only when 8 bytes remain, so
an empty `pong` is taken without one. `Pong::nonce` becomes an `Option`,
and 1 to 7 bytes are trailing bytes.
The reference reads the count after each header as a size and ignores
it, so a `headers` entry with a count other than 0 is accepted.
`Headers2` keeps each entry as a `CompressedHeader`, so a decoded message
encodes back to the same bytes. `CompressionState` goes, and
`Headers2::announce` builds a message from block headers.
The reference leaves out every previous hash but the first, steps the
time modulo 2^32, and keeps the first version only when announcing.
`Headers2::reply` and `Headers2::announce` now do the same.
The reference restores nothing into a first header and ignores its time
offset, and a version offset past the recent versions restores nothing.
Those fields stay zero instead of failing or taking the offset as time.
Decodes `headers2` replies of 2000 and 8000 chained headers, the limits
before and after the raised count.

@thepastaclaw thepastaclaw 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.

Re-review — Final validation — Phase 1 + Phase 2

The reviewed wire-format changes are consistent with the stated Dash Core behavior, and the completed build, test, lint, and cross-platform checks show no related failures. One non-blocking API design issue remains: Version exposes independent optional tail fields while its infallible encoder silently truncates at the first gap; several platform checks remain queued or in progress.

🟡 1 suggestion(s)

1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This is a large, intricate change to peer-facing network deserialization and protocol encoding in files such as pkgs/p2p_core/src/msg/headers2.rs and pkgs/p2p_core/src/msg/version.rs.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 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 `pkgs/p2p_core/src/msg/version.rs`:
- [SUGGESTION] pkgs/p2p_core/src/msg/version.rs:194-198: Represent `addr_send` and `nonce` as one optional pair
  (existing thread: https://github.com/dashpay/base-sdk/pull/86#discussion_r4369719389)
  The underlying invariant concern remains, although it applies to the whole optional suffix rather than only this pair. Because these fields are public and independently optional, callers can construct values such as `addr_send: Some(...)` with `nonce: None`, or populate a later field after an earlier one is absent. `encode_optional` then silently drops the present field and every suffix field after the gap, and `hash_impl!(Version)` hashes that truncated representation. Model the optional suffix as nested options or a length-indexed type, or otherwise prevent invalid prefix states from being constructed. The current documentation explains the truncation behavior but does not prevent this lossy API state.

This branch has not been deployed

No deployments
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