Skip to content
Draft
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
ac166f3
feat(network): regulate GetBlocks serving work
czarcas7ic Sep 4, 2026
71cadad
docs(changelog): record GetBlocks regulation
czarcas7ic Sep 4, 2026
8ed38ed
fix(network): isolate test tracking and correct lease fixtures
czarcas7ic Sep 4, 2026
491756d
fix(network): retain serving capacity through state completion
czarcas7ic Sep 4, 2026
85401ce
fix(network): retain GetBlocks terminals through queue pressure
czarcas7ic Sep 4, 2026
8b920aa
Merge remote-tracking branch 'origin/main' into adam/getblocks-regula…
czarcas7ic Sep 5, 2026
7d8d386
fix(release): version dependents of GetBlocks API changes
czarcas7ic Sep 5, 2026
4d953aa
fix(network): serialize serving query claims and cancellation
czarcas7ic Sep 5, 2026
23e951c
test(config): normalize the generated fixture ending
czarcas7ic Sep 5, 2026
774cca4
fix(network): require GetBlocks responses to fit any single block
czarcas7ic Sep 5, 2026
c765f49
Merge remote-tracking branch 'origin/main' into adam/getblocks-regula…
czarcas7ic Sep 6, 2026
122c177
fix: size GetBlocks work rates for native sync
czarcas7ic Sep 6, 2026
e9697eb
Merge remote-tracking branch 'origin/main' into adam/getblocks-regula…
czarcas7ic Sep 7, 2026
0be3f2b
fix(network): remove GetBlocks byte-rate policy
czarcas7ic Sep 7, 2026
a9b37a7
docs: align GetBlocks parameter ledger with capacity admission
czarcas7ic Sep 7, 2026
b9d59ac
fix(network): propagate GetBlocks write backpressure through producer…
czarcas7ic Sep 7, 2026
524c347
test(network): verify producer backpressure over QUIC
czarcas7ic Sep 7, 2026
55b87b6
docs: record bounded GetBlocks transport comparison
czarcas7ic Sep 7, 2026
7704547
Merge main and align publishable dependency versions
czarcas7ic Sep 7, 2026
5f5895f
refactor(network): declare request policy over shared work admission
czarcas7ic Sep 7, 2026
0795f81
fix(network): apply QUIC backpressure at GetBlocks admission
czarcas7ic Sep 7, 2026
a2c395b
test(config): store generated config for version 1.4.0-rc1
czarcas7ic Sep 7, 2026
fb49019
docs: keep Git install example on existing stable release
czarcas7ic Sep 7, 2026
eea1430
chore(network): remove obsolete regulation artifacts
czarcas7ic Sep 7, 2026
9c79b29
test(network): remove measurement-only fixture exports
czarcas7ic Sep 7, 2026
238600d
docs(network): explain GetBlocks policy fields and steps
czarcas7ic Sep 7, 2026
9d7bcae
fix(network): remove unused serving test helper
czarcas7ic Sep 7, 2026
b570724
docs(network): explain peer session checks on reconnect
czarcas7ic Sep 7, 2026
1e1fd1c
docs(network): label reconnect example explicitly
czarcas7ic Sep 7, 2026
44f0e7c
refactor(network): keep slot counters test-only
czarcas7ic Sep 7, 2026
7abcfae
test(config): reuse unchanged config fixture for rc1
czarcas7ic Sep 7, 2026
5a3e7ef
docs(network): describe current GetBlocks design directly
czarcas7ic Sep 7, 2026
2e16d3c
docs(network): explain shared request admission in plain words
czarcas7ic Sep 7, 2026
547b860
docs(network): explain backpressure and ownership in plain words
czarcas7ic Sep 7, 2026
fc2fd44
fix(network): finish ordered frames before cancelling a stream
czarcas7ic Sep 7, 2026
422d993
fix(network): publish download grace before pausing admission
czarcas7ic Sep 7, 2026
9ab5537
fix(network): keep serving streams open during local pressure
czarcas7ic Sep 7, 2026
172b0b9
fix(network): discard redundant rejected-peer bookkeeping
czarcas7ic Sep 7, 2026
25347b4
docs(network): record GetBlocks timeout and receive window defaults
czarcas7ic Sep 7, 2026
e7cabd8
fix(network): retain peer serving capacity across reconnects
czarcas7ic Sep 7, 2026
c624660
refactor(network): simplify GetBlocks admission plumbing
czarcas7ic Sep 7, 2026
43c4860
fix(network): keep test-only bound import out of release builds
czarcas7ic Sep 7, 2026
79ff7c5
fix(network): validate the full GetBlocks request range
czarcas7ic Sep 7, 2026
fbf466b
fix(network): enforce declared payload limits before allocation
czarcas7ic Sep 7, 2026
6e2216e
Merge main into GetBlocks regulation
czarcas7ic Sep 8, 2026
de030de
refactor(network): store one serving request per peer
czarcas7ic Sep 8, 2026
1ebe594
refactor(network): derive received block count from bitmap
czarcas7ic Sep 8, 2026
4b02b80
fix(network): backport QUIC loss recovery datagram bound
czarcas7ic Sep 8, 2026
d9ef1df
docs(changelog): record QUIC recovery fix
czarcas7ic Sep 8, 2026
c35d9ca
merge main and QUIC recovery prerequisite into GetBlocks regulation
czarcas7ic Sep 8, 2026
baa0414
fix(network): keep block responses moving while serving waits
czarcas7ic Sep 8, 2026
88df92f
fix(network): reserve response queue slots before encoding
czarcas7ic Sep 8, 2026
53ccc72
chore(deps): audit the pinned QUIC backport
czarcas7ic Sep 8, 2026
a9692e6
merge QUIC dependency audit into GetBlocks regulation
czarcas7ic Sep 8, 2026
5d5c8ab
ci: refresh GetBlocks checks against transport base
czarcas7ic Sep 8, 2026
d2300ab
test: probe default GetBlocks transport windows
czarcas7ic Sep 8, 2026
0a5ea1b
refactor: retain range-read resources in blocking jobs
czarcas7ic Sep 8, 2026
aa1d90a
refactor: prepare paired block-sync transport and request ownership
czarcas7ic Sep 9, 2026
0a6d564
refactor: serve paired block requests directly through owned storage
czarcas7ic Sep 9, 2026
a289325
fix: give slow block downloads realistic request deadlines
czarcas7ic Sep 9, 2026
54d0732
refactor: bound paired sessions through setup and retirement
czarcas7ic Sep 9, 2026
d916e42
test: adapt regulation ownership coverage to paired serving
czarcas7ic Sep 9, 2026
30752aa
test: allow liveness to retire peers before request expiry
czarcas7ic Sep 9, 2026
dd7f0b9
fix: allow slow block-sync data writes to finish
czarcas7ic Sep 9, 2026
ea9f39a
test: preserve serving prefixes under storage and queue pressure
czarcas7ic Sep 9, 2026
11beaec
refactor: activate paired block sync and remove legacy serving
czarcas7ic Sep 9, 2026
c7481f0
docs: record final paired block-sync validation
czarcas7ic Sep 9, 2026
571ce07
Merge main into GetBlocks refactor
czarcas7ic Sep 9, 2026
7cf0d46
docs: include transport prerequisite in main-targeted GetBlocks PR
czarcas7ic Sep 9, 2026
b80ef5f
refactor(network): remove superseded GetBlocks adapters
czarcas7ic Sep 9, 2026
575abf3
Merge remote-tracking branch 'origin/main' into adam/getblocks-two-st…
czarcas7ic Sep 9, 2026
2fd9cac
test(network): consolidate GetBlocks regressions and separate propert…
czarcas7ic Sep 9, 2026
3b8f8ca
docs: record GetBlocks test consolidation checks
czarcas7ic Sep 9, 2026
745561b
fix(network): reuse incoming pair session reservations
czarcas7ic Sep 9, 2026
98d6147
fix(network): keep paired data timeouts stream-local
czarcas7ic Sep 9, 2026
ea5b4cd
docs(network): explain block-sync session bookkeeping
czarcas7ic Sep 9, 2026
8e27724
fix(network): defer paired session capacity races
czarcas7ic Sep 9, 2026
953b85b
fix(network): retain no-progress policy across pair resets
czarcas7ic Sep 9, 2026
3649cbf
docs: remove withdrawn download test requirement
czarcas7ic Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions crates/zakura-consensus/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zakura-consensus"
version = "7.0.2-rc0"
version = "7.0.2-rc1"
authors.workspace = true
description = "Implementation of Zcash consensus checks for the Zakura node. Internal crate, published to support cargo install zakura"
license.workspace = true
Expand Down Expand Up @@ -67,11 +67,11 @@ zcash_primitives = { workspace = true }
tower-fallback = { package = "zakura-tower-fallback", path = "../tower-fallback/", version = "1.2.0" }
tower-batch-control = { package = "zakura-tower-batch-control", path = "../tower-batch-control/", version = "1.3.0" }

zakura-script = { path = "../zakura-script", version = "3.2.2-rc0" }
zakura-state = { path = "../zakura-state", version = "7.2.0-rc0" }
zakura-node-services = { path = "../zakura-node-services", version = "3.2.3-rc0" }
zakura-script = { path = "../zakura-script", version = "3.2.2-rc1" }
zakura-state = { path = "../zakura-state", version = "8.0.0" }
Comment thread
czarcas7ic marked this conversation as resolved.
Outdated
zakura-node-services = { path = "../zakura-node-services", version = "3.2.3-rc1" }
zakura-chain = { path = "../zakura-chain", version = "7.0.0-rc0" }
zakura-header-chain = { path = "../zakura-header-chain", version = "2.0.1-rc0" }
zakura-header-chain = { path = "../zakura-header-chain", version = "2.0.1-rc1" }

zcash_protocol.workspace = true

Expand All @@ -93,7 +93,7 @@ toml = { workspace = true }

tokio = { workspace = true, features = ["full", "tracing", "test-util"] }

zakura-state = { path = "../zakura-state", version = "7.2.0-rc0", features = ["proptest-impl"] }
zakura-state = { path = "../zakura-state", version = "8.0.0", features = ["proptest-impl"] }
zakura-chain = { path = "../zakura-chain", version = "7.0.0-rc0", features = ["proptest-impl"] }
zakura-test = { path = "../zakura-test/", version = "2.1.0" }

Expand Down
2 changes: 1 addition & 1 deletion crates/zakura-header-chain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zakura-header-chain"
version = "2.0.1-rc0"
version = "2.0.1-rc1"
authors.workspace = true
description = "Fork-aware header-chain domain types and transition engine for Zakura"
license.workspace = true
Expand Down
6 changes: 3 additions & 3 deletions crates/zakura-network/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zakura-network"
version = "7.1.0-rc0"
version = "8.0.0"
authors.workspace = true
description = "Networking code for the Zakura node. Internal crate, published to support cargo install zakura"
# # Legal
Expand Down Expand Up @@ -83,9 +83,9 @@ proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }

zakura-chain = { path = "../zakura-chain", version = "7.0.0-rc0", features = ["async-error"] }
zakura-header-chain = { path = "../zakura-header-chain", version = "2.0.1-rc0" }
zakura-header-chain = { path = "../zakura-header-chain", version = "2.0.1-rc1" }
zakura-jsonl-trace = { path = "../zakura-jsonl-trace", version = "1.2.0" }
zakura-node-services = { path = "../zakura-node-services", version = "3.2.3-rc0" }
zakura-node-services = { path = "../zakura-node-services", version = "3.2.3-rc1" }

[dev-dependencies]
proptest = { workspace = true }
Expand Down
38 changes: 37 additions & 1 deletion crates/zakura-network/src/zakura/block_sync/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ pub const DEFAULT_BS_SIZE_DEVIATION_TOLERANCE: u32 = 200;
/// only controls how many bounded body frames a server sends before `BlocksDone`.
pub const MAX_BS_RESPONSE_BYTES: u32 = DEFAULT_BS_MAX_RESPONSE_BYTES;

/// Encoded payload bytes in either terminal GetBlocks response message.
pub const GET_BLOCKS_TERMINAL_PAYLOAD_BYTES: u64 = 9;

const DEFAULT_GET_BLOCKS_NODE_ACTIVE_REQUESTS: usize = 64;
const DEFAULT_GET_BLOCKS_QUERY_TIMEOUT: Duration = Duration::from_secs(8);

/// Default steady-state cwnd gain, percent of the bandwidth-delay product. 300% ramps a
/// proven peer up as `1 → 3 → 9 …`; the reliability discount and delay-gradient ceiling
/// pull it back if the extra concurrency costs drops or standing queue.
Expand Down Expand Up @@ -233,7 +239,9 @@ pub struct ZakuraBlockSyncConfig {
/// Initial per-peer BBR cwnd (cold-start point), in blocks; converges to the
/// BDP-derived target once the first delivery is measured.
pub initial_inflight_requests: u32,
/// Maximum total response bytes this node advertises per `GetBlocks` response.
/// Maximum serialized block bytes this node advertises per `GetBlocks` response.
/// Must be at least [`block::MAX_BLOCK_BYTES`] so any single valid block fits.
/// Message discriminators and the response terminator are reserved separately.
pub max_response_bytes: u32,
/// Maximum estimated bytes reserved for outstanding block-body requests: a
/// DoS/pacing bound on in-flight wire data, released at receipt. Received
Expand Down Expand Up @@ -316,6 +324,32 @@ pub struct ZakuraBlockSyncConfig {
pub floor_bypass_slots: u32,
/// Block-sync peer caps and queue limits owned by this service.
pub peer_limits: ServicePeerLimits,
/// Resource policy for serving inbound `GetBlocks` requests.
pub get_blocks_regulation: GetBlocksRegulationConfig,
}

/// Node and peer bounds applied before GetBlocks state work starts.
///
/// Each session has one response producer, held through state work and transport
/// writes. Node capacity returns when the last query, result, or frame owner drops.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct GetBlocksRegulationConfig {
/// State queries and responses that may remain active across all peers.
pub node_active_requests: usize,
/// Response deadline for a state query. Timed-out reads keep their resource
/// charges until the underlying state work finishes.
#[serde(with = "humantime_serde")]
pub query_timeout: Duration,
}

@evan-forbes evan-forbes Sep 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

one of the ideas in 747 was to reuse a lot of these logic instead of making one per message

not sure if we can do this for everything but many of these seem like they could be generalized

I'm biased, and not sure how feasible it is irl, but the idea of defining the message then adding and configuring the functions then and there seems nice

@czarcas7ic czarcas7ic Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is now reduced to the following config:

pub struct GetBlocksRegulationConfig {
    pub node_active_requests: usize,
    pub query_timeout: Duration,
}

Because we might want these to differ per message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I left this unresolved because I could easily be convinced that maybe we just keep these global as well.


impl Default for GetBlocksRegulationConfig {
fn default() -> Self {
Self {
node_active_requests: DEFAULT_GET_BLOCKS_NODE_ACTIVE_REQUESTS,
query_timeout: DEFAULT_GET_BLOCKS_QUERY_TIMEOUT,
}
}
}

fn deserialize_ignored_replace_legacy_syncer<'de, D>(deserializer: D) -> Result<bool, D::Error>
Expand Down Expand Up @@ -359,6 +393,7 @@ impl Default for ZakuraBlockSyncConfig {
bbr_cwnd_unit: CwndUnit::Bytes,
floor_bypass_slots: DEFAULT_BS_FLOOR_BYPASS_SLOTS,
peer_limits: ServicePeerLimits::default(),
get_blocks_regulation: GetBlocksRegulationConfig::default(),
}
}
}
Expand Down Expand Up @@ -465,6 +500,7 @@ impl ZakuraBlockSyncConfig {
if self.bbr_probe_rtt_interval <= self.bbr_probe_rtt_duration {
return Err("bbr_probe_rtt_interval must exceed bbr_probe_rtt_duration");
}
super::serving_regulation::validate_config(self)?;
Ok(())
}

Expand Down
84 changes: 71 additions & 13 deletions crates/zakura-network/src/zakura/block_sync/events.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(any(test, feature = "proptest-impl"))]
use super::state::BlockSyncFrontiers;
use super::{request::*, *};
use std::num::NonZeroU64;
use std::{fmt, num::NonZeroU64};

/// Committed header metadata used by block sync to schedule and validate a body.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -80,6 +80,8 @@ pub enum BlockSyncEvent {
},
/// Node wiring finished or abandoned a `Block` response to an inbound `GetBlocks`.
BlockRangeResponseFinished {
/// Exact inbound request being completed.
request_id: BlockRangeRequestId,
/// Peer whose served-response slot can be released.
peer: ZakuraPeerId,
/// First requested height.
Expand All @@ -91,6 +93,10 @@ pub enum BlockSyncEvent {
},
/// State returned committed bodies requested by a peer and the reactor should send them.
BlockRangeResponseReady {
/// Keep the returned blocks charged until the reactor consumes or drops them.
lease: BlockRangeQueryLease,
/// Exact inbound request being completed.
request_id: BlockRangeRequestId,
/// Peer whose inbound request is being served.
peer: ZakuraPeerId,
/// First requested height.
Expand All @@ -102,6 +108,24 @@ pub enum BlockSyncEvent {
},
}

/// Session lifecycle facts sent from [`BlockSyncService`] to the reactor.
///
/// This internal channel carries the session generation needed to reject stale
/// connect and disconnect events without exposing transport bookkeeping in the
/// public driver API.
#[derive(Clone, Debug)]
pub(super) enum BlockSyncPeerLifecycleEvent {
/// A newly admitted stream session is ready for reactor bookkeeping.
Connected(BlockSyncPeerSession),
/// One exact stream session has ended.
Disconnected {
/// Peer whose stream session ended.
peer: ZakuraPeerId,
/// Generation assigned when the session was admitted.
session_id: u64,
},
}

/// Result of applying a block-sync body through the verifier driver.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum BlockApplyResult {
Expand Down Expand Up @@ -253,6 +277,34 @@ impl BlockApplyOutcome {
/// ignore those stale completions instead of releasing a newer in-flight body.
pub type BlockApplyToken = u64;

/// Monotonic identity assigned to each inbound `GetBlocks` request.
///
/// The state driver echoes this identity so a delayed completion cannot settle
/// a newer request or send blocks through a replacement peer session.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockRangeRequestId(NonZeroU64);

impl BlockRangeRequestId {
/// Construct an ID, returning `None` for the reserved zero value.
pub const fn new(value: u64) -> Option<Self> {
match NonZeroU64::new(value) {
Some(value) => Some(Self(value)),
None => None,
}
}

/// Return the nonzero integer carried by this request identity.
pub const fn get(self) -> u64 {
self.0.get()
}
}

impl fmt::Display for BlockRangeRequestId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}

/// Actions emitted by the future block-sync reactor for the service seam.
#[derive(Clone, Debug)]
pub enum BlockSyncAction {
Expand All @@ -271,12 +323,21 @@ pub enum BlockSyncAction {
},
/// Ask node wiring to read committed bodies for an inbound `GetBlocks`.
QueryBlocksByHeightRange {
/// Resource ownership to retain through the actual state read and response.
lease: BlockRangeQueryLease,
/// Exact inbound request identity to echo in the response event.
request_id: BlockRangeRequestId,
/// Peer that requested the range.
peer: ZakuraPeerId,
/// First height.
start: block::Height,
/// Maximum count.
count: u32,
/// Maximum total encoded block-body bytes the state result may contain.
max_response_bytes: u32,
/// Response deadline. The driver retains capacity until the underlying
/// state future completes, including after this timeout.
timeout: Duration,
},
/// Parent-first body ready for B3's verifier/commit driver.
SubmitBlock {
Expand Down Expand Up @@ -349,10 +410,8 @@ pub enum BlockSyncMisbehavior {
MalformedMessage,
/// A peer sent blocks that were not requested.
UnsolicitedBlock,
/// A peer requested more blocks than this node advertised it can serve.
GetBlocksTooLong,
/// A peer exceeded this node's inbound `GetBlocks` serving budget.
GetBlocksSpam,
/// A peer requested blocks before sending its required `Status`.
GetBlocksBeforeStatus,
/// A peer supplied a body whose payload does not match its requested header.
BodyPayloadMismatch(zakura_header_chain::BodyPayloadMismatch),
/// A commitment-matching body deterministically failed consensus.
Expand All @@ -376,10 +435,9 @@ pub enum BlockSyncMisbehavior {
/// Each per-peer pipe-routine ([`PeerRoutine`](super::peer_routine)) decodes its
/// own frames and runs the download logic locally; it forwards only the concerns
/// that need reactor-global state (serving, status advertisement, the producer,
/// misbehavior aggregation) over this channel. The sender is `try_send`/bounded
/// so a busy reactor never backpressures a routine's decode loop into stalling
/// its transport (the only blocking routine send is the Sequencer `AcceptBody`).
#[derive(Clone, Debug)]
/// misbehavior aggregation) over this channel. Serving waits for channel capacity
/// with stream reads paused; control notifications use bounded `try_send`.
#[derive(Debug)]
pub(super) enum RoutineToReactor {
/// A routine received a `Status` and updated its own servable/caps + the
/// registry. The reactor advertises our `Status` reply and republishes the
Expand All @@ -396,10 +454,10 @@ pub(super) enum RoutineToReactor {
ServeGetBlocks {
/// Peer that requested the range.
peer: ZakuraPeerId,
/// First requested height.
start_height: block::Height,
/// Requested block count.
count: u32,
/// Decoded fields of the admitted request.
request: super::serving_regulation::GetBlocksRequest,
/// Provisional resource ownership from the originating peer session.
attempt: super::serving_regulation::AdmissionAttempt,
},
/// A routine drained its pending work; the producer should re-query (it
/// self-gates on low-water, so the ping is idempotent/cheap).
Expand Down
Loading
Loading