Skip to content

fix: compare reorg windows by l1 height#355

Open
romanornr wants to merge 1 commit into
mainfrom
fix/sparse-l1-reorg-window
Open

fix: compare reorg windows by l1 height#355
romanornr wants to merge 1 commit into
mainfrom
fix/sparse-l1-reorg-window

Conversation

@romanornr

Copy link
Copy Markdown
Collaborator

Summary

  • Compares reorg detector DB rows to canonical Bitcoin hashes by explicit L1 height instead of zip position.
  • Adds regression tests for sparse via_l1_blocks windows where only the bootstrap/end height is present.
  • Applies the same fix to both the main/external-node reorg detector and the verifier reorg detector.

Closes #354

Why

A fresh external-node database can have a sparse via_l1_blocks table. The previous zip-by-position comparison could compare a DB row for a later height, such as 100891, against the canonical hash for the first height in the reorg window, such as 100792. That can false-detect a reorg and trigger soft-reorg handling even when the DB row is canonical for its actual height.

Validation

git diff --check
cargo fmt --check
cargo test -p via_main_node_reorg_detector
cargo test -p via_verifier_reorg_detector
cargo check -p via_main_node_reorg_detector -p via_verifier_reorg_detector
gitleaks protect --staged --redact --no-banner
gitleaks git --log-opts="main..HEAD" --redact --no-banner

Notes:

  • via_verifier_dal currently emits a pre-existing unused-variable warning in withdrawals_dal.rs; this PR does not touch that file.
  • No live infrastructure actions were run.

Review notes

This PR intentionally keeps the change narrow: sparse DB windows are handled as a safe non-reorg path for rows whose explicit heights still match canonical chain hashes. It does not add historical backfilling; infra-side guarded recovery automation handles future fresh/sparse Hetzner EN DB repair separately.

Copilot AI review requested due to automatic review settings May 15, 2026 20:52

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request refactors the reorganization detection logic in both the main node and the verifier node by introducing a new helper function, find_reorg_start_block_in_window, along with unit tests. The review identified a critical bug where the logic assumes blocks are ordered by height, but the asynchronous fetching mechanism used (buffer_unordered) does not guarantee this order, likely resulting in false-positive detections. It was also recommended to move the duplicated helper function into a shared module to improve maintainability.

Comment on lines +202 to +205
let chain_hashes = chain_blocks
.iter()
.map(|block| block.block_hash().to_string())
.collect::<Vec<_>>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The logic here assumes that chain_blocks (and thus chain_hashes) is ordered by block height. However, the fetch_blocks implementation (lines 79-96) uses buffer_unordered, which returns blocks in the order they are received from the Bitcoin node, not necessarily in the order of the requested heights.

This will cause find_reorg_start_block_in_window to compare hashes for mismatched heights, leading to frequent false-positive reorg detections.

Fix: Change buffer_unordered to buffered (or buffer_ordered) in the fetch_blocks function to ensure the resulting Vec matches the order of the input heights.

Comment on lines +209 to +212
let chain_hashes = chain_blocks
.iter()
.map(|block| block.block_hash().to_string())
.collect::<Vec<_>>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

Similar to the main node reorg detector, this code assumes chain_blocks is ordered by height. Because fetch_blocks uses buffer_unordered, the order is non-deterministic. This will lead to incorrect reorg detection logic as hashes will be compared against the wrong heights.

Fix: Update fetch_blocks to use buffered() instead of buffer_unordered() to preserve the sequence of blocks.

Comment on lines +320 to +339
fn find_reorg_start_block_in_window(
start_height: i64,
db_blocks: &[(i64, String)],
chain_hashes: &[String],
) -> Option<i64> {
for (db_number, db_hash) in db_blocks {
let Ok(chain_index) = usize::try_from(*db_number - start_height) else {
continue;
};
let Some(chain_hash) = chain_hashes.get(chain_index) else {
continue;
};

if chain_hash != db_hash {
return Some(*db_number);
}
}

None
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The function find_reorg_start_block_in_window is identical to the one implemented in via_verifier/node/via_reorg_detector/src/lib.rs. To improve maintainability and avoid logic drift, consider moving this utility function to a shared internal crate or a common module accessible by both components.

Copilot AI left a comment

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.

Pull request overview

This PR fixes false-positive reorg detection when via_l1_blocks is sparse by comparing DB rows to canonical Bitcoin blocks using explicit L1 block height (rather than zip position), and adds regression tests covering sparse-window scenarios in both the main-node/external-node detector and the verifier detector.

Changes:

  • Replace zip-by-position DB-vs-chain comparisons with height-indexed comparisons.
  • Add unit tests to cover sparse via_l1_blocks windows (e.g., only end-height present).
  • Apply the same comparison logic to both reorg detector implementations.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
via_verifier/node/via_reorg_detector/src/lib.rs Switches reorg window comparison to be height-based and adds sparse-window regression tests.
core/node/via_main_node_reorg_detector/src/lib.rs Mirrors the verifier change: height-based comparison plus sparse-window regression tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +209 to +215
let chain_hashes = chain_blocks
.iter()
.map(|block| block.block_hash().to_string())
.collect::<Vec<_>>();

let reorg_start_block_height_opt =
find_reorg_start_block_in_window(start_height, &db_blocks, &chain_hashes);
Comment on lines +202 to +208
let chain_hashes = chain_blocks
.iter()
.map(|block| block.block_hash().to_string())
.collect::<Vec<_>>();

for ((db_number, db_hash), chain_block) in db_blocks.iter().zip(chain_blocks.iter()) {
if chain_block.block_hash().to_string() != *db_hash {
tracing::warn!("Reorg detected at block {}", db_number);
reorg_start_block_height_opt = Some(*db_number);
break;
}
let reorg_start_block_height_opt =
find_reorg_start_block_in_window(start_height, &db_blocks, &chain_hashes);
@romanornr

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

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

romanornr added a commit that referenced this pull request May 16, 2026
## Why

`via-core` already had a small root `AGENTS.md` and a compact pull
request template, but both were much thinner than the guidance now
present in `kube-state` and `via-infra-hetzner`. That made
protocol/runtime reviews easier to frame as generic code changes instead
of reviews with explicit ownership boundaries, live-vs-source
distinctions, validation expectations, and deployment impact notes.

This repository owns runtime behavior that deployment repos consume
through images, binaries, config keys, and docs. Reviewers and future
operators benefit from a repo-local routing file and a PR template that
clearly separate source behavior from live deployment proof, especially
for external-node bootstrap, Bitcoin/L1 handling, verifier/prover paths,
CLI-driven setup paths, and incident-response follow-up.

The guidance was refined with GitNexus group context from the Via
repository set (`vianetwork`, `via-platform-core`, and
`via-infra-foundation`), direct GitNexus inspection of `via-core`
component surfaces, and a full pass over the current upstream ZKsync
`AGENTS.md`. Upstream ZKsync guidance was not copied wholesale; only the
parts that match this Via checkout were adapted.

## What changed

- Expanded the root `AGENTS.md` into a via-core-specific routing and
safety guide.
- Added explicit ownership boundaries between `via-core`, `kube-state`,
`helm-charts`, `via-infra-hetzner`, and GCP infrastructure repos.
- Added more specific via-core path guidance for:
  - BTC client / BTC watch / BTC sender paths
  - main-node and verifier reorg detectors
  - verifier-side BTC/DA sibling implementations
  - DA clients
- prover, verifier, indexer, contracts, configs, Docker examples, and
`zkstack_cli`
- Added source-of-truth, safety, change-discipline, validation,
GitNexus, and PR-description guidance for runtime/protocol work.
- Adapted the upstream ZKsync `zkstack_cli` rebuild rule for Via: Rust
changes under `zkstack_cli/` require rebuilding the installed CLI with
`zkstackup --local` before CLI validation reflects source changes.
- Adapted the upstream Forge-script lesson narrowly for this checkout:
`zkstack_cli` commands that construct Forge script parameters or output
paths should verify generated paths and fail clearly instead of masking
missing deployments, wrong ordering, or bad paths with broad fallbacks.
- Added a scoped fail-fast rule to `AGENTS.md` for deployment/upgrade
orchestration scripts: avoid `try/catch` and low-level `staticcall`
fallback probes when they would hide missing deployments,
initialization-order bugs, or timing problems; explicit state checks are
preferred. Static-call tests and precompile/EVM-behavior tests are
explicitly excluded from that rule. The PR template keeps this as
broader `zkstack_cli`/review-focus guidance rather than a mandatory
checklist item.
- Reworked `.github/pull_request_template.md` into
reviewer/operator-focused sections:
  - `Why`
  - `What changed`
  - `Boundaries and non-goals`
  - `How to review`
  - `Checks run`
  - `Live infrastructure and deployment impact`

## Boundaries and non-goals

- No runtime code, contracts, configs, Docker files, or workflows
changed.
- No live infrastructure actions were run.
- No database rows, wallet state, Kubernetes resources, Helm releases,
DNS records, cloud resources, or host services changed.
- No secret values were added, moved, renamed, printed, or required by
this PR.
- The existing PR-title workflow and conventional-commit requirement
remain unchanged.
- This PR intentionally updates repo guidance only; it does not fix the
open sparse reorg-window runtime bug in PR #355.
- Upstream ZKsync's v31 ecosystem upgrade-script architecture was
reviewed but not copied because this Via checkout does not contain the
same `deploy-scripts/upgrade/...` files.
- The upstream `try/catch` / `staticcall` rule was included only as a
scoped orchestration-script rule, not as a blanket ban across all
Solidity, because this checkout legitimately uses `staticcall` in
test/precompile semantics code.

## How to review

Focus on whether the guidance is useful for future `via-core`
protocol/runtime reviews without overclaiming live deployment state.

Review points:
- `AGENTS.md` should route agents/reviewers to the right repo and avoid
conflating source behavior with live desired state.
- The listed paths should be specific enough to help with Via runtime
review without becoming a brittle full source map.
- The upstream ZKsync-derived guidance should be limited to parts that
exist or clearly apply in this Via checkout.
- `.github/pull_request_template.md` should preserve the useful
checklist intent while asking for stronger context, validation, and
rollout boundaries.
- The template should remain broadly usable for code, docs, tests,
configs, runtime safety PRs, and tooling changes.
- The text should not imply that a source PR performs a deployment or
live verification by itself.

## Checks run

```bash
git diff --check
git diff --cached --check
gitleaks protect --staged --redact --no-banner
curl -fsSL https://raw.githubusercontent.com/matter-labs/zksync-era/refs/heads/main/AGENTS.md -o /tmp/zksync-era-AGENTS.md
NODE_NO_WARNINGS=1 gitnexus analyze --skip-agents-md "$PWD"
NODE_NO_WARNINGS=1 gitnexus query -r via-core -c 'Improving via-core AGENTS.md and PR template with repo-specific runtime review guidance' -g 'Identify Via runtime components and review boundaries' -l 10 'via btc sender reorg detector external node verifier prover indexer config'
NODE_NO_WARNINGS=1 gitnexus cypher -r via-core "MATCH (f:File) WHERE f.filePath CONTAINS 'via_btc_sender' OR f.filePath CONTAINS 'via_btc_client' OR f.filePath CONTAINS 'via_da_client' OR f.filePath CONTAINS 'via_btc_watch' OR f.filePath CONTAINS 'via_main_node_reorg_detector' RETURN f.name, f.filePath LIMIT 80"
```

## Live infrastructure and deployment impact

No live infrastructure actions were run.

Merging this PR changes repository guidance and the default pull request
body only. It is not expected to deploy live services by itself.
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.

Reorg detector should handle sparse via_l1_blocks windows safely

2 participants