Skip to content

p2p: implement BIP434 peer feature negotiation - #1674

Closed
gosunuts wants to merge 5 commits into
mainfrom
feat/bip434-feature-negotiation
Closed

gosunuts wants to merge 5 commits into
mainfrom
feat/bip434-feature-negotiation

Conversation

@gosunuts

@gosunuts gosunuts commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Implements the BIP434 protocol layer from #1634 so peers can advertise independent extensions before verack. Previously outbound startup sent preference messages before learning the remote version and repeated them on VERSION reception. Startup now sends VERSION only; the existing VERSION dispatch generates negotiation responses once. Duplicate VERSION messages are rejected rather than replaying negotiation.

Implemented behavior

  • Advertise protocol 70017; add typed feature codec with 4–80-byte IDs, at most 512 configuration bytes, minimal CompactSize, exact payload consumption, and bounded allocation. Non-UTF8 IDs remain ignorable, as printable ASCII is a recommendation.
  • Accept advertisements only after a remote VERSION >=70017 and before receiving its VERACK. Ignore validated unknown IDs and duplicates without retaining per-feature state or extending the handshake deadline. Ignore unknown commands in the post-VERSION/pre-VERACK window.
  • Guard handshake sends against old/unknown remote versions and our own completed VERACK write. Refuse feature advertisements through the asynchronous post-handshake writer queue, releasing reservations on refusal.
  • Support BIP324 short ID 37 and long-form decoding; keep unimplemented IDs 29–36 ignored.
  • Preserve the existing BIP339/BIP155 preferences and post-verack BIP152 advertisement. Update the command inventory and compatibility policy while retaining the Core 31.1 baseline; the BIP434 reference implementation is pinned separately to Core commit 8598ec220450b05eff03fc1d1c81f51269cca247.

No BIP434 extension is enabled yet, so production generates no feature advertisements. In particular, this does not advertise or implement BIP332 (#1637), retain unknown feature registries, or introduce another handshake/state owner. Recognized extension configuration belongs to its eventual implementation in the existing connection state.

Validation

Executed under WSL with Rust 1.95.0 using --ignore-rust-version; the repository declares Rust 1.99.0, which is not installed. No toolchain, manifest, or lockfile requirements were changed.

  • cargo fmt --all -- --check and git diff --check: pass.
  • cargo test --ignore-rust-version -p bitcoin-rs-p2p --test wire_codec --test handshake_roundtrip --test core_compat: 36 tests pass, including independent codec bytes, malformed/nonminimal/trailing payloads and existing TCP/serving compatibility regressions.
  • Targeted library runs for bip434, fsm::tests, handshake::tests, bip324::msg_type::tests, and listener::v2_transport_tests: pass. The new real-socket test exercises 70015/70016/70017 clients over v1/v2, duplicate/non-UTF8 unknown features before the remote verack, exact server handshake order, and subsequent ping responses. Existing outbound v2, legacy fallback, and deadline tests also pass.
  • Existing sync proofs tick_fetches_new_tip_headers_from_at_tip_peers and cold_start_stall_hedges_front_without_reassigning_owner: pass.
  • Scoped lint: cargo clippy --ignore-rust-version --locked -p bitcoin-rs-p2p --all-targets --no-deps -- -D warnings -A clippy::duration_suboptimal_units: pass. The unmodified strict command fails on existing duration_suboptimal_units warnings in UTXO; --no-deps alone also encounters existing P2P service/tx-policy instances. No unrelated lint cleanup is included.

Synchronization impact

This changes the peer readiness/handshake path and the new message's outbound queue classification. It does not change authoritative header admission, validation, chain selection, or durable commit. Relevant clauses are SYNC-02 (connection ownership/frontier recovery), SYNC-04 (protocol failure classification), and SYNC-06 (eligible peers reach the request driver). The two existing sync proofs above are the smallest relevant driver/headroom evidence from the synchronization contract's map; the real-socket v2/fallback tests cover the affected connection boundary. No new locks, persistence schema, chain frontier, or retained operator-data representation are introduced.

Remaining evidence / why draft

Live interoperability with a BIP434-capable Core and validation on the declared Rust 1.99 toolchain remain outstanding. The socket tests use this implementation's listener, not a Core daemon; existing Core 31.1 evidence is not new BIP434 evidence. Deep Core interoperability stays in its existing evidence lane. Unknown-feature duplicates are covered; supported-feature configuration/conflict semantics will be defined when an actual extension is enabled. This PR deliberately leaves #1634 open pending its live-reference acceptance evidence.

RetriggerView in GreptileConfidence Score: 5/5

No code defect found in this re-review blocks merging.

Summary

Adds BIP434 feature negotiation while keeping the Core 31.1 baseline explicit.

  • Peers wait for the remote version before sending handshake preferences.
  • Peer messages can carry bounded BIP434 feature data.
  • Peers accept feature messages only during the version handshake.
  • Feature advertisements stay on the handshake-owned send path.
  • The lockfiles now pin ctutils 0.4.5.

Live Core interoperability and verification on Rust 1.99 remain explicitly outstanding; this review did not run tests.

Reviews (3) · Last reviewed commit: "fix(p2p): reject duplicate VERSION befor..." · Reviewed by Greptile

@amazon-q-developer amazon-q-developer 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.

Review Summary

This PR implements BIP434 peer feature negotiation: protocol version bump to 70017, a new Feature message variant with codec, FSM-level receive-window enforcement (post-version, pre-remote-verack), and a matching send-window guard (sent_verack flag in Peer). The architectural changes are coherent — the outbound handshake now sends only VERSION before learning the remote version, feature messages are blocked from the async post-handshake writer queue, and duplicate/unknown feature IDs are ignored without state retention.

One confirmed defect requiring a fix:

A truncated feature payload in decode_feature returns PeerError::Protocol (a local/non-punitive error class) for a remotely-sourced structural violation. Every other comparable decode error in this file uses PeerError::Misbehavior, which is the correct type for remote violations — it flows through PeerError::inbound() → Incoming(...) → is_misbehavior() == true, affecting reconnect policy appropriately. Using Protocol here means a peer that sends a truncated feature payload is never penalized.

The connection.rs comment I initially added has been reviewed more carefully — the credit is correctly dropped (released) when enqueue returns early with OutboundRefusal::Closed, as confirmed by the test at line 1567-1568. That comment should be disregarded as a false positive.

Draft state is noted: Live interoperability with a BIP434-capable Core node and validation on the declared Rust 1.99 toolchain remain outstanding per the PR description.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread crates/p2p/src/wire.rs Outdated
Comment thread crates/p2p/src/connection.rs Outdated
Comment thread crates/p2p/src/wire.rs
lidarbtc and others added 2 commits October 25, 2026 00:08
…e sends pre-charge

- fsm already rejects a repeated VERSION as misbehavior; update the
  discouragement test to assert it (protected leases ignore it and stall
  to the read deadline) and keep the first version's stored services.
- decode_feature: a truncated feature identifier is malformed wire data,
  so classify it as Misbehavior, not a recoverable Protocol error.
- PeerLease::send/send_with: refuse Feature messages before charging the
  outbound budget so the credit never round-trips for a refused send.
- getnetworkinfo: report the negotiated protocol version from
  wire::PROTOCOL_VERSION (70017) instead of the stale 70016 literal; the
  pinned-Core corpus declares the divergence as a gap key, and the e2e
  introspection assertion follows the advertised version.
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Pushed 731cea571 addressing the open findings:

  • decode_feature: a truncated feature identifier is now PeerError::Misbehavior — it is malformed wire data, not a recoverable protocol condition.
  • PeerLease::send/send_with: Feature messages are refused before charging the outbound budget, so the credit never round-trips for a refused send.
  • accepted_version_updates_addr_before_discouraging_repeated_version: now asserts the real FSM behavior — a repeated version is Misbehavior, which a protected lease ignores and stalls to the read deadline; the stored services stay from the first version.
  • getnetworkinfo now reports the negotiated wire::PROTOCOL_VERSION (70017) instead of the stale 70016 literal; handler_smoke, core_compat, the e2e introspection assertion, and the pinned-Core corpus follow, with the divergence declared as a gap key.

This should also fix the test-crates/ci-gate failures on the previous head.

Written by Devin

@metaphoric-dev

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T02:08:16.053205Z 731cea5 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 731cea5719

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

@metaphoric-dev
metaphoric-dev marked this pull request as ready for review October 28, 2026 18:24
metaphorics added 2 commits October 29, 2026 00:13
# Conflicts:
#	Cargo.lock
#	crates/p2p/src/bip324/msg_type.rs
#	crates/p2p/src/compat.rs
#	crates/p2p/src/fsm.rs
#	crates/p2p/src/handshake.rs
#	crates/p2p/src/wire.rs
#	crates/p2p/tests/core_compat.rs
#	crates/rpc/tests/core_compat.rs
#	crates/rpc/tests/corpus/core-31.1/20_batch_two_requests.json
#	crates/rpc/tests/handler_smoke.rs
#	docs/contracts/p2p-wire.md
#	docs/policies/p2p-compatibility.md
#	e2e/tests/p2p_sync.rs
#	fuzz/Cargo.lock
Adapt the PR's remaining send-window gating and tests to the
crate::feature::{FeatureAnnouncement, FeatureId} API that main landed in
#1694 (Message::Feature tuple variant, FEATURE_VERSION in crate::feature),
and keep the PR's duplicate-VERSION rejection ahead of the service gate:
main's receive_version overwrites remote_version mid-handshake, which
routes a repeated VERSION into 'outbound peer lacks desirable services'
instead of the duplicate-version misbehavior this PR asserts.
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Merge-conflict sweep: I resolved all 14 conflicted files against current main (commit 701f96e).

Resolution notes:

  • Conflicts were largely overlapping implementations of the same BIP434 work — this PR's version-vs-main's Land verified overlay stack #1694 overlay stack. I resolved toward main's canonical implementation (crate::feature::{FeatureAnnouncement, FeatureId, FEATURE_VERSION}, Message::Feature tuple variant, receive_feature).
  • The PR's residual unique delta survives: send-window gating in peer.rs/connection.rs, the listener/roundtrip/codec tests, and the duplicate-VERSION-before-service-gate fix, which I ported onto main's receive_version (early remote_version.is_some() → Misbehavior("duplicate version message")), since main's overwrite path would route a repeated VERSION into the service gate first.
  • cargo check -p bitcoin-rs-p2p --all-targets and the targeted fsm/listener/bip434 tests pass. 7 lib tests fail identically on main itself on this box (environment-dependent dials) — pre-existing, not from this merge.

Remaining red is inherited from main: the stable-toolchain roll (rustfmt 1.10.0, new clippy lints) broke fmt/clippy/drift/build repo-wide. A repair branch with the mechanical fixes is pushed at devin/sweep-toolchain-drift (couldn't open the PR — the linked account can't open PRs right now); a maintainer can open it, then a main merge here goes green for those lanes.

Copy link
Copy Markdown
Member Author

Closing as superseded. The base BIP434 implementation is already on main through #1697; the remaining duplicate-VERSION and BIP434 send-window/queue protections were adapted to current main in #1714.

#1714 preserves current BIP332/BIP331 negotiation and includes scoped unit, loopback v1/v2, integration and synchronization evidence (149 tests passed). It does not claim new live BIP434 Core interoperability or close the outstanding reference-evidence requirement in #1634.

The source branch is preserved; this PR is closed without merging.

@gosunuts gosunuts closed this Oct 29, 2026
gosunuts added a commit that referenced this pull request Oct 29, 2026
…1714)

BIP434 feature advertisements could pass through `Peer::send` without a
negotiated remote version or after our VERACK, and the post-handshake
writer queue could accept them. A second VERSION before readiness could
also replace the accepted version and replay negotiation responses.

Extract the remaining boundary fixes from #1674 onto current main:
- Keep the local VERACK write fact in `Peer`. Permit `feature` only with
a known remote version >=70017 and before a successful local VERACK
write. The remote VERACK closes only the receive window.
- Reject `feature` at both PeerLease queue entrypoints, before ordinary
budget charging and with reserved credits released. This remains a local
caller error even under aggregate pressure.
- Reject duplicate VERSION before mutating peer state, preserve the
original receive timestamp, and keep protected peers under the existing
ignore/deadline rules. Update the existing address-book regression to
retain the first accepted service flags.
- Update the owning handshake policy/evidence references. Existing
BIP332/BIP331 offers, message codecs, startup preference ordering and
protocol version remain unchanged.


[BIP434](https://github.com/bitcoin/bips/blob/master/bip-0434.md#specification)
requires the remote version threshold and prohibits sending feature
after the sender's VERACK. The send fact is independent of receiving the
other side's VERACK.

Validation on Windows, Rust 1.99:
- Before the fix, the new direct-send test accepted feature with no
remote version; the queue test classified feature as Shared backpressure
instead of refusing it.
- **113 selected library tests passed**: FSM, handshake/BIP324, peer
sends, connection budgets, duplicate VERSION dispatch/metadata, real
v1/v2 listener/fallback/deadline tests, and both synchronization proofs
below.
- **36 integration tests passed**: `cargo test -p bitcoin-rs-p2p --test
handshake_roundtrip --test wire_codec --test core_compat`.
- The two direct-send tests passed again after the final assertion-only
lint edits.
- `cargo clippy -p bitcoin-rs-p2p --lib --tests --no-deps -- -D warnings
-A dead_code`, workspace formatting and diff checks passed. The command
without `-A dead_code` remains blocked by existing Windows
`sam::tests::FakeSam` fields `strangers`/`accepted`; the existing
storage checkpoint dead-code warning is also unchanged.
- New loopback evidence enables BIP332 and BIP331 together, checks exact
handshake command order for 70016/70017 over v1/v2, sends features after
receiving the server VERACK but before sending the client VERACK,
refuses local post-VERACK feature writes, then verifies ping/pong.

Synchronization impact: this touches peer handshake/readiness and
outbound queue admission, not authoritative header admission, chain
validation, chain selection or durable commit. Relevant clauses are
SYNC-02 (connection ownership/frontier recovery), SYNC-04 (typed
protocol failure), and SYNC-06 (eligible peer/request drivers). The
smallest existing evidence-map proofs
`tick_fetches_new_tip_headers_from_at_tip_peers` and
`cold_start_stall_hedges_front_without_reassigning_owner` both passed.
Existing connection reservation/release and socket fallback/deadline
tests cover the changed local boundary. No locks, persisted
representations, schemas or generation counters are added.

Limits: loopback peers use this implementation. No new live
BIP434-capable Core interoperability campaign was run; this does not
close the outstanding reference-evidence requirement in #1634.
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.

3 participants