fix(sync): stop Zakura sync drivers before legacy ChainSync fallback#334
Conversation
And two more auto-invalidated findings. Analyzed three files, diff |
928c3ce to
e872496
Compare
Patches RUSTSEC-2026-0190 (unsoundness in `anyhow::Error::downcast_mut` for versions < 1.0.103), which fails the `cargo deny advisories` CI check. Bump the cargo-vet exemption to the new version too.
e872496 to
6c52c86
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit bf95d60. Configure here.
evan-forbes
left a comment
There was a problem hiding this comment.
Instead of removing the fallback globally, maybe we could disable it only when v2_p2p = true and legacy_p2p = false?
For dual-stack nodes, fallback is still useful, but when it triggers we should first cancel the Zakura header/block sync shutdown token, then start legacy ChainSync.
That keeps the recovery path while ensuring only one body-sync committer is active.
let legacy_fallback = config.network.v2_p2p && config.network.legacy_p2p;
syncer
.bootstrap_genesis_then_pause(
read_only_state_service.clone(),
legacy_fallback,
zakura_endpoint
.as_ref()
.and_then(|endpoint| endpoint.header_sync_shutdown()),
)
...
if zakura_block_sync_stalled(...) {
if !legacy_fallback {
warn!(
verified_tip = ?verified_height,
header_tip = ?header_tip_height,
stall = ?ZAKURA_BODY_SYNC_STALL_TIMEOUT,
"Zakura body sync is stalled; legacy fallback disabled because legacy_p2p is off"
);
continue;
}
// dual-stack fallback path
}then actually shutdown headersync and blocksync
if let Some(shutdown) = zakura_shutdown.as_ref() {
shutdown.cancel();
}
warn!(
verified_tip = ?verified_height,
header_tip = ?header_tip_height,
"Zakura body sync stalled; stopped Zakura sync drivers and switching to legacy ChainSync"
);
return self.sync().await;provided there are some other nodes running both stack, only running zakura should be fine
bf95d60 to
474ca09
Compare
|
Thanks @evan-forbes — adopted this approach in
No new config surface and no stored-config change (the gate is derived from existing flags). Local checks: |
When Zakura body sync stalled, the watchdog (`bootstrap_genesis_then_pause`) reactivated the legacy ChainSync body downloader but left the Zakura header- and block-sync drivers running. Two committers feeding the state-commit pipeline broke its accounting and could deadlock the node. The watchdog now cancels the Zakura endpoint shutdown token (stopping the header- and block-sync drivers) before calling `self.sync()`, so only one body-sync committer is ever active. The fallback is also limited to dual-stack nodes (`v2_p2p && legacy_p2p`): a Zakura-only node has no legacy peers to fall back to, so it keeps waiting for Zakura and warns once per stall window instead of switching pipelines. `stop_zakura_sync` is a module-level helper with a unit test asserting the hand-off cancels the drivers' shutdown token (and is a no-op without one).
474ca09 to
3ac587d
Compare
Add deterministic watchdog action tests so fallback token cancellation is tied to the real stall and legacy-probe decisions.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8a5f531. Configure here.
|
@v12-auditor review |
Analyzed two files, diff |
Wait for Zakura sync tasks to drain in-flight applies before legacy fallback starts, preventing overlapping verifier/state commits during dual-stack handoff.
|
Discussed F-94205 and agreed as non-issue |
Motivation
When
network.v2_p2pis enabled, the legacy syncer runs in watchdog mode (ChainSync::bootstrap_genesis_then_pause) while the Zakura block-sync driver drives body sync. On a Zakura body-sync stall the watchdog calledself.sync(), restarting the legacy ChainSync body downloader — but the Zakura header- and block-sync drivers kept running. Two committers then fed the state-commit pipeline at once, breaking its accounting and deadlocking the node.Solution
Keep the recovery path, but make the switch safe and scope it correctly (per review feedback from @evan-forbes):
header_sync_shutdown()) via thestop_zakura_synchelper, stopping the Zakura header- and block-sync drivers, then callsself.sync(). Only one body-sync committer is ever active, so the deadlock can't happen.legacy_fallback = config.network.v2_p2p && config.network.legacy_p2p, computed instart.rsand passed intobootstrap_genesis_then_pause. A Zakura-only node (legacy_p2p = false) has no legacy peers to fall back to, so it never switches: it warns once per stall window and keeps waiting for Zakura (and skips theFindBlockscross-check probe).FindBlockscross-check (the fleet-restart blind spot).No new config surface — the gate is derived from the existing
v2_p2p/legacy_p2pflags, sozebrad generateoutput and the stored-config fixtures are unchanged.Also bumps
anyhow1.0.102 → 1.0.103 (separate commit) to clearRUSTSEC-2026-0190, which thecargo deny advisoriesCI check started flagging onironwood-main.Tests
sync/tests/fallback.rs:fallback_cancels_the_zakura_shutdown_token— asserts the hand-off cancels the drivers' shutdown token (the deadlock-prevention invariant), verified via a child token standing in for the drivers' observed shutdown.stop_zakura_sync_is_a_noop_without_a_token— a Zakura-only node has no token; the helper must not panic.cargo fmt --all -- --check— cleancargo build -p zebrad --locked— cleancargo clippy -p zebrad -p zebra-network --all-targets -- -D warnings— cleancargo test -p zebrad -- components::sync→ all passingAI Disclosure