From 8b674871c1b6b35a0b76c9cca0039a6c02156450 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 12 Mar 2026 18:50:03 -0300 Subject: [PATCH] docs(p2p): restructure README into architecture overview with localized sub-READMEs The monolithic p2p README mixed architecture, validation rules, and protocol details. Split into focused sub-READMEs next to the code they document, with the main README serving as an architecture overview and navigation index. Co-Authored-By: Claude Opus 4.6 --- yarn-project/p2p/README.md | 132 +++++++++- .../attestation_validator/README.md | 49 ++++ .../proposal_validator/README.md | 123 ++++++++++ .../p2p/src/services/reqresp/README.md | 229 ++++++++++++++++++ 4 files changed, 530 insertions(+), 3 deletions(-) create mode 100644 yarn-project/p2p/src/msg_validators/attestation_validator/README.md create mode 100644 yarn-project/p2p/src/msg_validators/proposal_validator/README.md create mode 100644 yarn-project/p2p/src/services/reqresp/README.md diff --git a/yarn-project/p2p/README.md b/yarn-project/p2p/README.md index 375662609f73..1ed930c351de 100644 --- a/yarn-project/p2p/README.md +++ b/yarn-project/p2p/README.md @@ -1,7 +1,133 @@ # P2P -This package includes the functionality of the P2P networking required by the aztec node. The `P2PClient` provides an interface for the introduction of transactions to the Tx Pool and propagation of those transactions through the network. The `P2PService` offers an abstraction of the P2P networking. +This package implements the P2P networking layer for Aztec nodes using libp2p. It handles transaction propagation, block and checkpoint proposal dissemination, attestation collection for consensus, and peer management. The `P2PClient` provides the top-level interface used by `aztec-node`; the `BootstrapNode` class runs a lightweight discovery-only node that introduces peers to the network without participating in gossip. -The package depends on a block source in order to reconcile the transaction pool and remove settled transactions. +## Architecture -Additionally, the `BootstrapNode` class provides the functionality for running a P2P 'bootnode', one that serves the purpose of introducing new peers to the network. It does not participate in transaction exchange. +- **P2PClient** wraps everything below. Manages lifecycle, bridges L2 block events to pool state transitions, exposes `ITxProvider` for RPC. +- **LibP2PService** is the core networking layer. Subscribes to gossipsub topics, registers req/resp handlers, runs message validation pipelines. It composes: + - **PeerManager** — peer scoring (gossipsub + application-level), authentication (STATUS/AUTH handshakes), connection gating. + - **DiscV5Service** — UDP-based peer discovery using Ethereum's discv5 protocol and ENR records. + - **ReqResp** — request-response protocols: BLOCK, BLOCK_TXS, TX, STATUS, AUTH, PING, GOODBYE. + - **TxCollection** — coordinates transaction fetching: fast collection for proposals/proving (deadline-driven, falls back to `BatchTxRequester`) and slow background collection for unproven blocks. +- **Mempools** sit below the service layer: + - **TxPoolV2** — transaction mempool with explicit state machine (pending, protected, mined, soft-deleted, hard-deleted) and pluggable eviction rules. + - **AttestationPool** — stores block/checkpoint proposals and attestations per slot. Handles equivocation detection and slash callbacks. + +### Key Components + +| Component | Responsibility | +|-----------|---------------| +| **P2PClient** | Top-level orchestrator. Manages lifecycle, bridges L2 block events to pool state transitions, exposes `ITxProvider` for RPC. | +| **LibP2PService** | Core networking. Subscribes to gossipsub topics, registers req/resp handlers, runs message validation pipelines. | +| **PeerManager** | Peer scoring (gossipsub + application-level), authentication (STATUS/AUTH handshakes), connection gating. | +| **DiscV5Service** | UDP-based peer discovery using Ethereum's discv5 protocol and ENR records. | +| **TxCollection** | Coordinates transaction fetching: fast collection for proposals/proving (deadline-driven, falls back to `BatchTxRequester`) and slow background collection for unproven blocks. | +| **BatchTxRequester** | Aggressive parallel fetching of missing txs from peers via BLOCK_TXS protocol. Classifies peers as pinned/dumb/smart for efficient batching. | +| **TxPoolV2** | Transaction mempool with explicit state machine (pending, protected, mined, soft-deleted, hard-deleted) and pluggable eviction rules. | +| **AttestationPool** | Stores block/checkpoint proposals and attestations per slot. Handles equivocation detection and slash callbacks. | + +### Peer Lifecycle + +``` +Unknown → [DiscV5 discovery] → Discovered → [TCP connect] → Connected + → [STATUS or AUTH handshake] → Authenticated → [gossip participation] → Active +``` + +Handshake type depends on config: +- `p2pAllowOnlyValidators` = true and peer is not protected: **AUTH** handshake (signature challenge proving validator identity). Unauthenticated peers get `appSpecificScore = -Infinity`, excluding them from all gossip. +- Otherwise: **STATUS** handshake (version compatibility check only). +- Protected peers (trusted/private/preferred): STATUS only, always considered authenticated. + +Connection gating: peers with too many failed AUTH attempts (`p2pMaxFailedAuthAttemptsAllowed`, default 3) are denied inbound connections for 1 hour. + +--- + +## Sub-module Documentation + +| README | Covers | +|--------|--------| +| [Gossipsub Scoring](src/services/gossipsub/README.md) | P1-P4 parameter calculation, decay mechanics, convergence math, global thresholds, application-level penalties, tuning guidelines | +| [Transaction Validation](src/msg_validators/tx_validator/README.md) | Validator factories per entry point, individual validator descriptions with benchmarks, coverage table | +| [Proposal Validation](src/msg_validators/proposal_validator/README.md) | BlockProposal and CheckpointProposal gossipsub validation, pool admission, validator-client processing, slashing | +| [Attestation Validation](src/msg_validators/attestation_validator/README.md) | CheckpointAttestation gossipsub validation, pool admission, equivocation detection, L1 submission validation | +| [ReqResp Protocols](src/services/reqresp/README.md) | Handshake protocols (STATUS, AUTH, PING, GOODBYE), block data protocols (BLOCK, BLOCK_TXS, TX), rate limits, transport validation | +| [BatchTxRequester](src/services/reqresp/batch-tx-requester/README.md) | Peer classification (pinned/dumb/smart), worker architecture, BLOCK_TXS wire protocol | +| [TxPool Interface](src/mem_pools/tx_pool/README.md) | TxPool contract, storage structure, priority system, nullifier deduplication, eviction rules | +| [TxPoolV2](src/mem_pools/tx_pool_v2/README.md) | State machine, soft deletion (slot-based vs prune-based), pre-add vs post-event rules | + +--- + +## Gossipsub Objects + +All gossipsub messages pass through a shared pre-validation pipeline before topic-specific logic: + +| Stage | Rule | Consequence | File | +|-------|------|-------------|------| +| 0 | Snappy decompressed size <= per-topic limit (see per-object sections) | Message dropped | `p2p/src/services/encoding.ts` | +| 1 | P2PMessage envelope deserializes | REJECT + LowToleranceError | `p2p/src/services/libp2p/libp2p_service.ts` | +| 2 | Gossipsub-level message cache dedup (configurable `seenTTL`) | Silently dropped by gossipsub | gossipsub internals | +| 3 | Application-level dedup via `MessageSeenValidator` (fixed-size circular buffer + Set) | IGNORE | `p2p/src/msg_validators/msg_seen_validator/` | + +A REJECT result from any validation stage increments the gossipsub P4 (invalidMessageDeliveries) counter for the peer on that topic. P4 weight is -20, decaying over 4 slots. This is in addition to any application-level peer penalty. + +### Peer Penalty Severity Reference + +| Severity | Approx. strikes to ban | Used for | +|----------|------------------------|----------| +| `LowToleranceError` | ~2 | Invalid proof, deserialization failure, old double-spend | +| `MidToleranceError` | ~10 | Most validation failures (metadata, data, gas, phases, size) | +| `HighToleranceError` | ~50 | Timestamp expiry, block header, recent double-spend, rate limits | + +See [Gossipsub Scoring](src/services/gossipsub/README.md) for full score calculation, decay mechanics, and threshold alignment. + +### Object Summary + +| Topic | Snappy Limit | Description | Detailed Docs | +|-------|-------------|-------------|---------------| +| `tx` | 512 KB | Transactions. Two-stage validation: fast validators (parallel) then proof verification. Pool pre-check between stages avoids wasting CPU on proof for txs the pool would reject. | [Tx Validation](src/msg_validators/tx_validator/README.md) | +| `block_proposal` | 10 MB | Block proposals from proposers. Validated for slot timing, signature, proposer identity, tx hash integrity. Validator nodes may re-execute and slash on state mismatch. | [Proposal Validation](src/msg_validators/proposal_validator/README.md) | +| `checkpoint_proposal` | 10 MB | Checkpoint proposals containing the final block. Same proposal validation as blocks, plus embedded block extraction and separate validation. | [Proposal Validation](src/msg_validators/proposal_validator/README.md) | +| `checkpoint_attestation` | 5 KB | Validator attestations for checkpoints. Validated for slot timing, attester/proposer signatures, committee membership. Equivocation at count=2 triggers slash callback. | [Attestation Validation](src/msg_validators/attestation_validator/README.md) | + +--- + +## ReqResp Protocols + +See [ReqResp Protocols](src/services/reqresp/README.md) for full protocol details. + +### Rate Limits (Responder Side) + +| Protocol | Peer Limit | Global Limit | +|----------|-----------|-------------| +| PING | 5/s | 10/s | +| STATUS | 5/s | 10/s | +| AUTH | 5/s | 10/s | +| GOODBYE | 5/s | 10/s | +| BLOCK | 2/s | 5/s | +| BLOCK_TXS | 10/s | 200/s | +| TX | (see config) | (see config) | + +Per-peer limit exceeded: `HighToleranceError` + `RATE_LIMIT_EXCEEDED` status. Global limit exceeded: `RATE_LIMIT_EXCEEDED` status only (no peer penalty). + +### Peer Score Thresholds + +| Score | State | Action | +|-------|-------|--------| +| > -50 | Healthy | Normal | +| -100 < score <= -50 | Disconnect | GOODBYE sent + disconnect on next heartbeat | +| <= -100 | Banned | GOODBYE sent + disconnect on next heartbeat | + +### Protocol Summary + +| Protocol | Request | Response | Purpose | +|----------|---------|----------|---------| +| STATUS | Own blockchain state | Peer's blockchain state | Version compatibility check on connect | +| AUTH | Random challenge (`Fr`) | Signed challenge response | Validator identity verification on connect | +| PING | (empty) | `pong` | Liveness check | +| GOODBYE | Reason byte | (none meaningful) | Graceful disconnect | +| BLOCK | Block number (`Fr`) | Block data (3 MB snappy) | Block sync | +| BLOCK_TXS | Archive root + tx hashes + BitVector | Txs + BitVector of availability | Batch tx fetching for proposals/proving | +| TX | `TxHashArray` | Matching txs | Individual tx fetching | + +Request payloads are NOT snappy-compressed (asymmetric: only responses use snappy). diff --git a/yarn-project/p2p/src/msg_validators/attestation_validator/README.md b/yarn-project/p2p/src/msg_validators/attestation_validator/README.md new file mode 100644 index 000000000000..18e8b1f0aa06 --- /dev/null +++ b/yarn-project/p2p/src/msg_validators/attestation_validator/README.md @@ -0,0 +1,49 @@ +# Attestation Validation + +This module validates `CheckpointAttestation` gossipsub messages. Attestations are signatures from committee members endorsing a checkpoint proposal. + +**Topic**: `checkpoint_attestation` | **Snappy size limit**: 5 KB + +## Stage 1: AttestationValidator (Gossipsub Validation) + +| # | Rule | Consequence | Severity | File | +|---|------|-------------|----------|------| +| 1 | **Slot timeliness**: `currentSlot` or `nextSlot`. Previous slot within 500ms: IGNORE. Older: REJECT. | REJECT or IGNORE | HighToleranceError | `attestation_validator.ts` | +| 2 | **Attester signature**: `getSender()` must recover valid address | REJECT | LowToleranceError | same | +| 3 | **Attester in committee**: recovered address in committee for slot | REJECT | HighToleranceError | same | +| 4 | **Proposer exists**: `getProposerAttesterAddressInSlot` must return defined | REJECT | HighToleranceError | same | +| 5 | **Proposer signature**: `getProposer()` must recover valid address | REJECT | LowToleranceError | same | +| 6 | **Proposer matches expected**: recovered proposer = expected for slot | REJECT | HighToleranceError | same | +| 7 | **NoCommitteeError**: committee unavailable | REJECT | LowToleranceError | same | + +**Fisherman mode extension** (`FishermanAttestationValidator`): if a checkpoint proposal for the same archive exists in pool, the attestation's `ConsensusPayload` must `.equals()` the stored proposal's payload. On mismatch: REJECT + LowToleranceError. + +## Stage 2: Pool Admission + +| # | Rule | Consequence | +|---|------|-------------| +| 8 | Sender recoverable (pool-side) | Silent drop | +| 9 | Not a duplicate (same slot + proposalId + signer) | IGNORE | +| 10 | Per-signer cap: `MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER` = 3 | IGNORE | + +Own attestations added via `addOwnCheckpointAttestations` bypass the per-signer cap. + +## Stage 3: Equivocation Detection + +When a signer's attestation count for a slot reaches exactly 2 (different proposals): `duplicateAttestationCallback` fires -> `WANT_TO_SLASH_EVENT` with `OffenseType.DUPLICATE_ATTESTATION`. Attestation still ACCEPTED and rebroadcast. Callback fires once (not again at count 3+). + +## Validation at L1 Checkpoint Submission (Archiver) + +| Rule | Consequence | File | +|------|-------------|------| +| Each attestation must have recoverable signature (or address-only is allowed but does not count toward quorum) | Checkpoint rejected as invalid | `archiver/src/modules/validation.ts` | +| Attestation at index `i` must correspond to committee member at index `i` | Checkpoint rejected as invalid | same | +| Valid attestation count >= floor(committee * 2/3) + 1 | Checkpoint rejected as invalid | same | +| No committee / escape hatch open | Accepted unconditionally | same | + +Note: `skipValidateCheckpointAttestations` config flag bypasses all archiver attestation validation. + +## Gossipsub Topic Scoring + +P3 enabled with expected messages per slot = `targetCommitteeSize`. Conservative threshold (30% of convergence value). Max P3 penalty = -34 per topic. + diff --git a/yarn-project/p2p/src/msg_validators/proposal_validator/README.md b/yarn-project/p2p/src/msg_validators/proposal_validator/README.md new file mode 100644 index 000000000000..fa7a9694dd63 --- /dev/null +++ b/yarn-project/p2p/src/msg_validators/proposal_validator/README.md @@ -0,0 +1,123 @@ +# Proposal Validation + +This module validates `BlockProposal` and `CheckpointProposal` gossipsub messages. Both share the same base `ProposalValidator` (neither subclass overrides `validate()`), with checkpoint-specific logic layered on top in the gossipsub handler. + +## BlockProposal + +**Topic**: `block_proposal` | **Snappy size limit**: 10 MB + +### Stage 1: Gossipsub Validation (ProposalValidator) + +File: `proposal_validator.ts` + +| # | Rule | Consequence | Severity | +|---|------|-------------|----------| +| 1 | **Slot check**: must be `currentSlot` or `nextSlot`. Previous slot within 500ms tolerance: IGNORE. | REJECT | HighToleranceError | +| 2 | **Signature**: `getSender()` must recover a valid address. If `signedTxs` present, its recovered sender must match. | REJECT | MidToleranceError | +| 3 | **Txs permitted**: if `disableTransactions`, must have 0 txHashes and 0 embedded txs | REJECT | MidToleranceError | +| 4 | **Max txs**: `txHashes.length <= maxTxsPerBlock` | REJECT | MidToleranceError | +| 5 | **Embedded txs in txHashes**: every embedded tx's hash must appear in `txHashes` | REJECT | MidToleranceError | +| 6 | **Proposer check**: signer must match expected proposer for slot (skipped if committee size = 0) | REJECT | MidToleranceError | +| 7 | **Tx hash integrity**: each embedded tx's recomputed hash must match declared hash | REJECT | LowToleranceError | +| 8 | **NoCommitteeError**: epoch cache cannot determine committee | REJECT | LowToleranceError | + +Deserialization guards: `BlockProposal.fromBuffer` and `SignedTxs.fromBuffer` both enforce `txCount <= MAX_TXS_PER_BLOCK` (65536). Violation -> REJECT + LowToleranceError. + +### Stage 2: Mempool (Attestation Pool) + +| # | Rule | Consequence | +|---|------|-------------| +| 9 | **Duplicate**: same archive root already stored | IGNORE (no penalty) | +| 10 | **Per-position cap**: max 3 proposals per (slot, indexWithinCheckpoint) | REJECT + HighToleranceError | +| 11 | **Equivocation**: >1 distinct proposal for same (slot, index) | ACCEPT (rebroadcast for detection). At count=2: `duplicateProposalCallback` fires -> slash event (`OffenseType.DUPLICATE_PROPOSAL`, configured via `slashDuplicateProposalPenalty`) | + +### Stage 3: Validator-Client Processing (BlockProposalHandler) + +Only runs on validator nodes. Non-validator nodes use a default handler that triggers tx collection without deep validation. + +| # | Rule | Failure Reason | +|---|------|----------------| +| 12 | Signature re-check | `invalid_proposal` | +| 13 | ProposalValidator re-run | `invalid_proposal` | +| 14 | Self-proposal filter | Ignored silently | +| 15 | Parent block exists (`lastArchive.root` matches known block or genesis) | `parent_block_not_found` | +| 16 | Parent block slot <= proposal slot | `parent_block_wrong_slot` | +| 17 | Block number not already in archiver | `block_number_already_exists` | +| 18 | Checkpoint number consistency (multiple sub-rules for first/non-first blocks) | `invalid_proposal` | +| 19 | Global variables consistency (non-first block: chainId, version, slot, timestamp, coinbase, feeRecipient, gasFees match parent) | `global_variables_mismatch` | +| 20 | L1-to-L2 message hash matches `proposal.inHash` | `in_hash_mismatch` | +| 21 | All txs referenced by `txHashes` obtainable | `txs_not_available` | +| 22 | **Re-execution**: processed tx count matches `txHashes.length` | `timeout` (ReExTimeoutError) | +| 23 | **Re-execution**: no failed txs | `failed_txs` (ReExFailedTxsError) -- **SLASHABLE** | +| 24 | **Re-execution**: archive root and header match proposal | `state_mismatch` (ReExStateMismatchError) -- **SLASHABLE** | + +**Escape hatch**: during escape hatch periods (`isEscapeHatchOpenAtSlot`), re-execution and slashing are both disabled, and the proposal is rejected locally. + +**Conditional re-execution**: rules 22-24 only run when at least one condition is true: `fishermanMode` enabled, `slashBroadcastedInvalidBlockPenalty > 0` with `validatorReexecute`, committee membership with `validatorReexecute`, `alwaysReexecuteBlockProposals`, or `blobClient.canUpload()`. + +**Slashing**: only `state_mismatch` and `failed_txs` trigger on-chain slashing (`OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL`, gated by `slashBroadcastedInvalidBlockPenalty > 0`). Unknown errors during re-execution do NOT slash. + +**Embedded tx validation**: txs in `signedTxs` are validated via `createTxValidatorForBlockProposalReceivedTxs` (well-formedness only) when stored in the tx pool. Invalid embedded txs are rejected from the pool but do not cause the block proposal itself to be rejected at gossipsub level. + +### Gossipsub Topic Scoring + +| Parameter | Effect | +|-----------|--------| +| P4 (invalidMessageDeliveries) | weight = -20, decay over 4 slots | +| P3 (meshMessageDeliveries) | Enabled only when `expectedBlockProposalsPerSlot > 0` (MBPS mode) | +| P1/P2 | Only active when P3 is enabled | + +--- + +## CheckpointProposal + +**Topic**: `checkpoint_proposal` | **Snappy size limit**: 10 MB + +### Stage 1: Gossipsub Validation (ProposalValidator) + +Same `ProposalValidator.validate()` as BlockProposal (shared implementation, neither subclass overrides it). See BlockProposal Stage 1 rules 1-8. + +### Stage 2: Embedded Block Proposal Validation (if `lastBlock` present) + +The checkpoint's embedded `lastBlock` is extracted via `getBlockProposal()` and validated through `BlockProposalValidator.validate()` plus block mempool checks. + +| Rule | Consequence | File | +|------|-------------|------| +| Block proposal must pass `BlockProposalValidator.validate()` | If REJECT: entire checkpoint REJECTED | `libp2p_service.ts` | +| Block proposal must not exceed per-position cap (3) | Checkpoint REJECTED + HighToleranceError | same | +| Block equivocation detected (>1 proposals for same slot+index) | Checkpoint REJECTED (block itself is ACCEPT for re-broadcast) | same | + +### Stage 3: Mempool (Attestation Pool) + +| Rule | Consequence | File | +|------|-------------|------| +| Duplicate (same archive ID) | IGNORE (no penalty). Embedded block still processed if valid. | `attestation_pool.ts` | +| Per-slot cap: `MAX_CHECKPOINT_PROPOSALS_PER_SLOT` = 5 | REJECT + HighToleranceError. Embedded block still processed. | same | + +### Stage 4: Equivocation Detection + +When >1 checkpoint proposals exist for same slot (count > 1): ACCEPT (re-broadcast). At count == 2 (exactly): `duplicateProposalCallback` fires. Proposal NOT further processed. Callback fires only once per equivocation pair. + +### Stage 5: Validator-Client Consensus Validation + +Determines whether the validator signs an attestation. + +| Rule | Consequence | File | +|------|-------------|------| +| Escape hatch open | No attestation | `validator-client/src/validator.ts` | +| Signature invalid (re-check) | No attestation | same | +| Self-proposal | No attestation (ignored) | same | +| `feeAssetPriceModifier` outside [-100, +100] bps | No attestation | same | +| Not in committee (unless fisherman mode) | No attestation | same | +| Checkpoint header mismatch (computed vs proposal) | No attestation | same | +| Archive root mismatch | No attestation | same | +| Epoch out hash mismatch | No attestation | same | +| Last block not found / not matching | No attestation | same | +| Already attested to this or earlier slot | No attestation (unless `attestToEquivocatedProposals`) | same | + +**`skipCheckpointProposalValidation` config**: when true, the re-execution checks (header/archive/epoch hash) are all skipped. Signature, fee modifier, committee, escape hatch, and equivocation checks still apply. + +### Gossipsub Topic Scoring + +P3 enabled with expected rate of 1 message per slot. P4 weight = -20, max P3 penalty = -34 per topic. + diff --git a/yarn-project/p2p/src/services/reqresp/README.md b/yarn-project/p2p/src/services/reqresp/README.md new file mode 100644 index 000000000000..982e00a28e74 --- /dev/null +++ b/yarn-project/p2p/src/services/reqresp/README.md @@ -0,0 +1,229 @@ +# ReqResp Protocols + +This module implements libp2p request-response protocols for the Aztec P2P network. All protocols share common transport-level validation (rate limiting, timeouts, Snappy decompression, error penalties) with protocol-specific logic layered on top. + +## Common Transport Validation + +### Rate Limiting (Responder Side) + +Applied before the protocol handler runs. + +| Protocol | Peer Limit | Global Limit | File | +|----------|-----------|-------------|------| +| PING | 5/s | 10/s | `rate-limiter/rate_limits.ts` | +| STATUS | 5/s | 10/s | same | +| AUTH | 5/s | 10/s | same | +| GOODBYE | 5/s | 10/s | same | +| BLOCK | 2/s | 5/s | same | +| BLOCK_TXS | 10/s | 200/s | same | +| TX | (see rate limits file) | (see rate limits file) | same | + +- Per-peer limit exceeded: `HighToleranceError` penalty + `RATE_LIMIT_EXCEEDED` status. Penalty fires inside `RequestResponseRateLimiter.allow()`, not the stream handler. +- Global limit exceeded: `RATE_LIMIT_EXCEEDED` status only (no peer penalty). + +### Response Status Byte (Requester Side) + +| Rule | Consequence | File | +|------|-------------|------| +| First chunk must be exactly 1 byte | `ReqRespStatusError(UNKNOWN)` | `status.ts` | +| Byte must be valid `ReqRespStatus` enum (0-4, 126, 127) | `ReqRespStatusError(UNKNOWN)` | same | + +Note: `prettyPrintReqRespStatus` is missing a `NOT_FOUND` case (minor logging bug). + +### Snappy Decompression (Requester Side) + +Per-protocol size limits checked via preamble before decompression. + +### Timeouts (Requester Side) + +| Timeout | Default | Penalty | +|---------|---------|---------| +| Individual request | 10s | HighToleranceError | +| Dial | 5s | HighToleranceError | + +### Error Penalty Categorization (Requester Side) + +| Error Type | Severity | +|------------|----------| +| GOODBYE subprotocol errors | None | +| `CollectiveReqRespTimeoutError` / `InvalidResponseError` | None | +| `AbortError` / connection close / muxer closed | None | +| `ECONNRESET` / `EPIPE` / `ECONNREFUSED` / `ERR_UNEXPECTED_EOF` | HighToleranceError | +| `ERR_UNSUPPORTED_PROTOCOL` | HighToleranceError | +| `IndividualReqRespTimeoutError` / `TimeoutError` | HighToleranceError | +| Catch-all | HighToleranceError | + +### Request Error Penalty (Responder Side) + +| Error Type | Severity | +|------------|----------| +| `BADLY_FORMED_REQUEST` | LowToleranceError | +| All others | None | + +### Notes + +- Request payloads are NOT snappy-compressed (asymmetric: only responses use snappy). + +--- + +## Handshake Protocols + +### Connection-Level Gating (Before Any Handshake) + +| Rule | Consequence | File | +|------|-------------|------| +| Deny inbound connection from IP/peerId with too many failed auth handshakes | Connection denied | `libp2p_service.ts` | +| Threshold: `p2pMaxFailedAuthAttemptsAllowed` (default 3) | Tracked per peerId AND per IP | `peer_manager.ts` | +| Failed auth entries expire after 1 hour | Peer can reconnect; no escalating penalty for repeat offenders | same | + +### Handshake Trigger Logic (`peer:connect`) + +1. `p2pDisableStatusHandshake` = true: no handshake +2. `p2pAllowOnlyValidators` = false: STATUS handshake +3. Peer is protected (trusted/private/preferred): STATUS handshake +4. Otherwise: AUTH handshake (superset of STATUS) + +Config constraint: `p2pDisableStatusHandshake && p2pAllowOnlyValidators` is disallowed. + +### STATUS Protocol (`/aztec/req/status/1.0.0`) + +**Requester side** (`peer_manager.ts`): + +| Rule | Consequence | +|------|-------------| +| Response status must be SUCCESS | Peer scheduled for disconnect | +| `compressedComponentsVersion` must match | Peer scheduled for disconnect | +| Any exception | Peer scheduled for disconnect | + +`StatusMessage.validate()` currently only checks `compressedComponentsVersion`. Fields `latestBlockNumber`, `latestBlockHash`, `finalizedBlockNumber` are NOT validated (TODO in code). + +**Responder side**: no validation of incoming request content (always responds with own status). This means the requester leaks its blockchain state to any peer before validation. + +**Deserialization bounds**: `MAX_VERSION_STRING_LENGTH` = 64 bytes, `MAX_BLOCK_HASH_STRING_LENGTH` = 128 bytes. Expected response size: 1 KB. + +### AUTH Protocol (`/aztec/req/auth/1.0.0`) + +**Requester side** (`peer_manager.ts`): + +| # | Rule | Consequence | +|---|------|-------------| +| 1 | Response status is SUCCESS | `markAuthHandshakeFailed` + disconnect | +| 2 | `compressedComponentsVersion` match | `markAuthHandshakeFailed` + disconnect | +| 3 | Valid ECDSA signature recovery from challenge response | `markAuthHandshakeFailed` + disconnect | +| 4 | Recovered address is a registered validator | `markAuthHandshakeFailed` + disconnect | +| 5 | Validator address not already authenticated to different peerId | Silent return (no disconnect, no failure marking -- peer stays connected but unauthenticated) | +| 6 | Any exception | `markAuthHandshakeFailed` + disconnect | + +Challenge: random `Fr`, payload = `keccak256("Aztec Validator Challenge:" + challenge)`, signed with `eth_sign` style. Challenge is NOT bound to peer identity (transport encryption via Noise is the binding layer). + +On success: peer added to authenticated maps, prior failures cleared (including IP-based ones -- shared-IP peers benefit from a legitimate validator's success). + +**Responder side** (`validator-client/src/validator.ts` + `peer_manager.ts`): + +| # | Rule | Consequence | +|---|------|-------------| +| 1 | Peer must be protected (`shouldTrustWithIdentity` in `peer_manager.ts`) | Returns empty buffer (SUCCESS status + empty payload -> requester gets parse error -> `markAuthHandshakeFailed`) | +| 2 | Node must have registered validator address | Returns empty buffer (same consequence) | + +**Unauthenticated peer gossip**: when `p2pAllowOnlyValidators` is true, unauthenticated peers get `appSpecificScore = -Infinity`, completely excluding them from all gossip. + +### PING Protocol (`/aztec/req/ping/1.0.0`) + +No validation on either side. Responder returns `Buffer.from('pong')`. Expected response: 1 KB. + +### GOODBYE Protocol (`/aztec/req/goodbye/1.0.0`) + +**Responder**: buffer must be 1 byte (defaults to `UNKNOWN` on invalid length). Goodbye reason byte is NOT validated against the enum -- any byte 0-255 accepted. Peer scheduled for disconnect regardless of reason. + +**Requester**: response errors are never penalized (GOODBYE subprotocol exempt from error categorization). + +### Periodic Re-validation + +| Rule | Interval | File | +|------|----------|------| +| Authenticated validators re-checked against current validator set | Every heartbeat (`peerCheckIntervalMS`) | `peer_manager.ts` | +| If validator address no longer registered, auth entry removed | Same | same | + +Protected peers (private/trusted/preferred) are always considered "authenticated" without AUTH handshake. + +--- + +## Block Data Protocols + +### BLOCK Protocol (`/aztec/req/block/1.0.0`) + +**Server side**: + +| Rule | Consequence | File | +|------|-------------|------| +| Request must parse as `Fr` | `BADLY_FORMED_REQUEST` + LowToleranceError | `protocols/block.ts` | +| Block lookup throws | `INTERNAL_ERROR` status | same | +| Block not found | SUCCESS + empty buffer (design choice; no `NOT_FOUND` status used) | same | + +**Requester side** (Snappy limit: 3 MB): + +| Rule | Consequence | File | +|------|-------------|------| +| Response block number must match requested | LowToleranceError; rejected | `libp2p_service.ts` (`validateRequestedBlock`) | +| Local block must exist for hash verification | Rejected (no penalty) | same | +| Response block hash must equal local block hash | MidToleranceError; rejected | same | + +**Limitation**: the local-block requirement means BLOCK req/resp is unusable for initial P2P-only sync (before L1 sync provides local copies for verification). A TODO in the code acknowledges this. + +### BLOCK_TXS Protocol (`/aztec/req/block_txs/1.0.0`) + +**Server side**: + +| Rule | Consequence | File | +|------|-------------|------| +| Request must parse as `BlockTxsRequest` (Fr + TxHashArray + BitVector) | `BADLY_FORMED_REQUEST` + LowToleranceError | `protocols/block_txs/block_txs_handler.ts` | +| BitVector length: non-negative and <= `MAX_TXS_PER_BLOCK` (65536) | Deserialization throws -> `BADLY_FORMED_REQUEST` | `protocols/block_txs/bitvector.ts` | +| Archive root not found and no explicit txHashes | `NOT_FOUND` status | handler | +| Internal error during lookup | Unhandled exception -> stream abort (no `INTERNAL_ERROR` status, unlike BLOCK) | handler | + +Conditional registration: BLOCK_TXS handler only registered when `config.disableTransactions` is false. Otherwise peers get `ERR_UNSUPPORTED_PROTOCOL`. + +**Requester side via `sendBatchRequest`** (Snappy limit: `max(N, 1) * 512 + 1` KB): + +| Rule | Consequence | File | +|------|-------------|------| +| Archive root must match request | MidToleranceError | `libp2p_service.ts` (`validateRequestedBlockTxs`) | +| BitVector length must match request | MidToleranceError | same | +| No duplicate tx hashes | MidToleranceError | same | +| Tx count within bounds | MidToleranceError | same | +| Local block proposal must exist for archive root | Rejected (no penalty) | same | +| All tx hashes must be in proposal's tx list at allowed indices | LowToleranceError | same | +| Txs in strictly increasing index order | LowToleranceError | same | +| Each tx passes well-formedness (Metadata [4 fields], Size, Data, Proof) | LowToleranceError | same | + +**Requester side via `BatchTxRequester`** (separate validation path): + +| Rule | Consequence | File | +|------|-------------|------| +| Non-SUCCESS status: `FAILURE`/`UNKNOWN` | HighToleranceError + "bad peer" tracking | `batch-tx-requester/batch_tx_requester.ts` | +| `RATE_LIMIT_EXCEEDED` | Peer marked rate-limited (cooldown) | same | +| `NOT_FOUND` / `BADLY_FORMED_REQUEST` / `INTERNAL_ERROR` | Falls through silently (no penalty) | same | +| Each tx validated (Metadata + Size + Data + Proof) | LowToleranceError per invalid tx; valid txs from same response still accepted | same | +| Archive root match + non-empty txIndices | No penalty on mismatch; peer not promoted to "smart" | same | + +**Double penalty on transport errors**: when `BatchTxRequester` encounters a transport error (e.g., ECONNRESET), both `sendRequestToPeer`'s internal handler and the `BatchTxRequester`'s catch block penalize the peer, resulting in double HighToleranceError. + +See [BatchTxRequester README](batch-tx-requester/README.md) for the full architecture (peer classification, worker model, wire protocol). + +### TX Protocol (`/aztec/req/tx/1.0.0`) + +**Server side**: + +| Rule | Consequence | File | +|------|-------------|------| +| Request must parse as `TxHashArray` | `BADLY_FORMED_REQUEST` + LowToleranceError | `protocols/tx.ts` | + +**Requester side** (validator registered at startup, not the default noop): + +| Rule | Consequence | File | +|------|-------------|------| +| Each returned tx hash must be in the requested set | MidToleranceError | `libp2p_service.ts` (`validateRequestedTxs`) | +| Each tx passes well-formedness (Metadata + Size + Data + Proof) | LowToleranceError | same | + +Snappy limit: `max(N, 1) * 512 + 1` KB. +