Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 3 additions & 6 deletions Cargo.lock

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

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ keywords = ["zakura", "zcash"]
rust-version = "1.97"
edition = "2021"

# Backport the loss-probe datagram limit fix to Iroh 0.92's exact QUIC versions.
# Keep the related crates on one revision. Workspace patches are not inherited
# by downstream library users; they must apply the same patch in their workspace.
[patch.crates-io]
iroh-quinn = { git = "https://github.com/zakura-core/iroh-quinn", rev = "1dcc7a43488fecd199d343d47e93e9ed8319fcaa" }
iroh-quinn-proto = { git = "https://github.com/zakura-core/iroh-quinn", rev = "1dcc7a43488fecd199d343d47e93e9ed8319fcaa" }
iroh-quinn-udp = { git = "https://github.com/zakura-core/iroh-quinn", rev = "1dcc7a43488fecd199d343d47e93e9ed8319fcaa" }

[workspace.dependencies]
# Zakura's published binary assets: the reviewed Mainnet historical frontier grid, which is too
# large to carry in git history at the weekly cadence the release state advances.
Expand Down
1 change: 1 addition & 0 deletions crates/zakura-network/src/zakura/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5465,6 +5465,7 @@ impl ZakuraHandlerError {

#[cfg(test)]
mod tests {
mod quic_progress;
use super::*;
use crate::{
protocol::internal::{InventoryResponse, Response},
Expand Down
83 changes: 83 additions & 0 deletions crates/zakura-network/src/zakura/handler/tests/quic_progress.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! Regression coverage for the pinned QUIC loss-recovery backport.

use super::*;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn bidirectional_transfers_exceed_flow_control_windows() -> Result<(), BoxError> {
let _guard = zakura_test::init();
const ALPN: &[u8] = b"/zakura/testkit/quic-progress/0";
// Larger than the default send, stream, and connection windows. Both sides
// must receive acknowledgements and new flow-control credit to finish.
const BYTES: usize = 64 * 1024 * 1024;
let limits = ZakuraLocalLimits::from_config(&Config::default());
let server = LocalEndpointFactory::with_transport_config(limits.transport_config())
.endpoint(92341)
.await?;
let client = LocalEndpointFactory::with_transport_config(limits.transport_config())
.endpoint(92342)
.await?;
let (connection_tx, mut connection_rx) = mpsc::channel(1);
let (stream_tx, mut stream_rx) = mpsc::channel(1);
let router = Router::builder(server)
.accept(
ALPN,
CaptureConnection {
connection_tx,
stream_tx,
},
)
.spawn();
let address = LocalEndpointFactory::node_addr(router.endpoint()).await;
let connection = timeout(Duration::from_secs(10), client.connect(address, ALPN)).await??;
let remote = timeout(Duration::from_secs(5), connection_rx.recv())
.await?
.unwrap();
let (mut send_a, recv_a) = connection.open_bi().await?;
// Make the stream visible to accept_bi before starting the bulk transfer.
send_a.write_all(&[42]).await?;
let (mut send_b, recv_b) = timeout(Duration::from_secs(5), stream_rx.recv())
.await?
.unwrap();

let transfer = async {
tokio::try_join!(
async {
send_a.write_all(&vec![42; BYTES]).await?;
send_a.finish()?;
Ok::<_, BoxError>(())
},
async {
send_b.write_all(&vec![43; BYTES]).await?;
send_b.finish()?;
Ok::<_, BoxError>(())
},
drain_stream(recv_a, BYTES, 43),
drain_stream(recv_b, BYTES + 1, 42),
)?;
Ok::<_, BoxError>(())
};
timeout(Duration::from_secs(30), transfer)
.await
.unwrap_or_else(|_| {
panic!(
"QUIC transfer stalled: local={:?}; remote={:?}",
connection.stats(),
remote.stats()
)
})?;
connection.close(0u32.into(), b"done");
client.close().await;
router.shutdown().await?;
Ok(())
}

async fn drain_stream(mut recv: RecvStream, expected: usize, byte: u8) -> Result<(), BoxError> {
let mut buffer = vec![0; 64 * 1024];
let mut received = 0;
while let Some(count) = recv.read(&mut buffer).await? {
assert!(buffer[..count].iter().all(|value| *value == byte));
received += count;
assert!(received <= expected);
}
assert_eq!(received, expected);
Ok(())
}
2 changes: 1 addition & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]

# List of URLs for allowed Git repositories
allow-git = []
allow-git = ["https://github.com/zakura-core/iroh-quinn"]

[sources.allow-org]
github = [
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog/unreleased/941.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Fixed

- Fix QUIC connections stalling during loss recovery when the socket cannot
send a batch of datagrams
([#941](https://github.com/zakura-core/zakura/pull/941)).
21 changes: 21 additions & 0 deletions qa/supply-chain/audits.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4381,3 +4381,24 @@ criteria = "safe-to-deploy"
user-id = 169181 # Kris Nuttycombe (nuttycom)
start = "2024-12-17"
end = "2027-07-10"

[[audits.iroh-quinn]]
who = "OpenAI Codex <noreply@openai.com>"
criteria = "safe-to-deploy"
delta = "0.14.0 -> 0.14.0@git:1dcc7a43488fecd199d343d47e93e9ed8319fcaa"
importable = false
notes = "Compared the 25 packaged files, including the original manifest, with the pinned Git source. They are byte-identical to the already audited registry release. The related protocol crate carries the separately audited loss-recovery fix."

[[audits.iroh-quinn-proto]]
who = "OpenAI Codex <noreply@openai.com>"
criteria = "safe-to-deploy"
delta = "0.13.0 -> 0.13.0@git:1dcc7a43488fecd199d343d47e93e9ed8319fcaa"
importable = false
notes = "Compared all 53 packaged files with the pinned source. Only the transmit-bound condition and its regression test differ. The condition counts datagrams directly instead of inferring their count from bytes, so a short loss probe cannot admit a second datagram when the socket supports only one. The integer conversion is bounded by the existing ten-segment clamp. The regression failed on the original condition; all 254 default-feature protocol tests pass with the fix. No parsing, cryptography, dependency, or unsafe-code changes."

[[audits.iroh-quinn-udp]]
who = "OpenAI Codex <noreply@openai.com>"
criteria = "safe-to-deploy"
delta = "0.5.7 -> 0.5.7@git:1dcc7a43488fecd199d343d47e93e9ed8319fcaa"
importable = false
notes = "Compared the 13 packaged files, including the original manifest and build script, with the pinned Git source. They are byte-identical to the already audited registry release. Socket and platform-specific code is unchanged."
10 changes: 10 additions & 0 deletions qa/supply-chain/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ url = "https://raw.githubusercontent.com/zcash/rust-ecosystem/main/supply-chain/
[imports.zcashd]
url = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"

# Keep the existing registry audits and require a reviewed delta for the backport.
[policy.iroh-quinn]
audit-as-crates-io = true

[policy.iroh-quinn-proto]
audit-as-crates-io = true

[policy.iroh-quinn-udp]
audit-as-crates-io = true

[policy.zakura]
audit-as-crates-io = false

Expand Down
Loading