Skip to content

fix(reorg-detector): compare L1 blocks by explicit height on sparse via_l1_blocks#358

Open
romanornr wants to merge 1 commit into
mainfrom
fix/reorg-detector-sparse-l1-blocks-amp
Open

fix(reorg-detector): compare L1 blocks by explicit height on sparse via_l1_blocks#358
romanornr wants to merge 1 commit into
mainfrom
fix/reorg-detector-sparse-l1-blocks-amp

Conversation

@romanornr

Copy link
Copy Markdown
Collaborator

Why

A Via external-node bootstrap observed during the Hetzner private external-node
rehearsal produced a false soft reorg at the wallet bootstrap block and broke
wallet-resource startup on the next restart. The failure mode was rooted in
the L1 reorg detector and was not specific to that operator run; any fresh or
sparse via_l1_blocks table on a node that already has a via_btc_watch
cursor at the wallet bootstrap height could trigger the same path.

Two source defects combined to produce the false positive:

  1. Both core/node/via_main_node_reorg_detector and
    via_verifier/node/via_reorg_detector fetched the 100-block canonical
    reorg window with futures::buffer_unordered, which yields results in
    completion order, not request order. The fetched Vec<Block> was then
    used as if it were ordered by ascending height.
  2. detect_reorg compared the local DB rows to the canonical chain rows by
    positional zip, not by explicit height. With a freshly bootstrapped
    via_l1_blocks containing only one row at the wallet bootstrap block
    (for example 100891), the detector window started at 100792, so the
    single DB row at 100891 was compared against the canonical chain
    element at index 0 (height 100792). The hashes did not match and the
    detector logged Reorg detected at block 100891.

The main-node soft-reorg handler then reset via_btc_watch.last_processed
below the wallet bootstrap block. On the next restart, the wallet-resource
bootstrap path could not find its rows, because via_btc_watch no longer
considered the wallet bootstrap block processed. This breaks the invariant
that a node booted with VIA_BTC_WATCH_START_L1_BLOCK_NUMBER and
VIA_BTC_WATCH_RESTART_INDEXING=false should not regress its BTC-watch
cursor purely because local via_l1_blocks state is sparse.

The same positional-zip and unordered-fetch pattern existed in the verifier
detector, so the invariant must be enforced on both sides.

What changed

Applied to both core/node/via_main_node_reorg_detector/src/lib.rs and
via_verifier/node/via_reorg_detector/src/lib.rs:

  • fetch_blocks now returns Vec<(i64, Block)>, pairing each block with its
    requested height and re-sorting ascending after buffer_unordered. This
    removes the silent ordering dependency. sync_l1_blocks was also relying
    on positional zip with a height range; it now consumes the explicit
    (height, block) pairs directly.
  • detect_reorg builds a HashMap<i64, String> of canonical block hashes
    keyed by height and delegates to a pure scan_for_reorg helper that walks
    the DB rows in ascending order and compares each row by its explicit
    number against canonical_by_height[number]. Positional zip is gone.
  • New ReorgScan::SparseAt(height) variant signals that the canonical fetch
    is missing a height that the local DB knows about (for example, a stale
    RPC or a partially failed batch fetch). The detector logs the height and
    returns without acting, so a sparse local window cannot cause a false
    reorg event or a BtcWatch demotion.
  • Six unit tests per crate, including
    sparse_db_does_not_falsely_compare_against_window_start, which pins the
    exact 100891-vs-100792 regression.

Performance / Complexity Impact

This change touches reorg detection and L1 sync (a Via-specific area called
out as requiring this section).

Operation Before After Notes
fetch_blocks (per detector iteration) O(W) RPC + O(W) collect O(W) RPC + O(W log W) final sort W = reorg_window = 100. Adds one in-memory sort over up to 100 elements. RPC fan-out (max_concurrent_fetches = 10) is unchanged.
Reorg scan over the window O(min(D, W)) positional zip O(W) HashMap build + O(D) scan D = rows returned by list_l1_blocks (<= W). Same asymptotic cost.
Allocations per iteration Vec<Block> + Vec<height> adds HashMap<i64, String> of size W One small additional allocation; freed at end of iteration.
sync_l1_blocks insert loop O(N) zip over range O(N) direct iteration No change in cost.

W is fixed at 100 by ViaReorgDetectorConfig::reorg_window, so the extra
sort and the HashMap are bounded and run at most once per poll interval. No
hot DAL path or BTC-client path changed.

Boundaries and non-goals

  • This PR updates source code and unit tests only. No live deployment was
    run from this branch.
  • No DAL signatures, SQL, schema, migrations, config keys, env vars, metric
    names, or chain-state were changed. init() still seeds exactly one
    bootstrap row when via_l1_blocks is empty; the height-keyed comparison
    makes that safe rather than requiring a coherent full-window backfill.
  • No changes to kube-state, helm-charts, or any Hetzner / GCP
    infrastructure repo. Rollout of the new behavior to via-main-testnet,
    Hetzner external nodes, or verifier nodes remains a separate operator
    step after review and approval.
  • No secrets, wallet material, or cookies were read, printed, or moved.

How to review

Suggested review focus:

  • core/node/via_main_node_reorg_detector/src/lib.rs and
    via_verifier/node/via_reorg_detector/src/lib.rs: confirm both detectors
    now route through scan_for_reorg and that the SparseAt branch returns
    without inserting reorg metadata or touching via_btc_watch.
  • fetch_blocks: confirm the height pairing + sort is correct and that the
    signature change is propagated to sync_l1_blocks and is_canonical_chain
    in both crates.
  • core/node/via_main_node_reorg_detector/src/tests.rs and
    via_verifier/node/via_reorg_detector/src/tests.rs: the
    sparse_db_does_not_falsely_compare_against_window_start test is the
    named regression for the Hetzner observation; the
    missing_canonical_height_for_present_db_row_returns_sparse test pins the
    safe non-reorg path when the canonical fetch is stale or incomplete.
  • Sibling check requested by repo guidance: both the main-node/external-node
    and verifier detectors received the same fix in the same PR; there are no
    remaining positional-zip comparisons against canonical chain blocks in
    these two files.

No upstream ZKsync reference is needed; the affected code is Via-specific.

Checks run

cargo build -p via_main_node_reorg_detector -p via_verifier_reorg_detector
cargo test  -p via_main_node_reorg_detector -p via_verifier_reorg_detector
cargo fmt   -p via_main_node_reorg_detector -p via_verifier_reorg_detector --check
cargo clippy -p via_main_node_reorg_detector -p via_verifier_reorg_detector --tests

All 12 new unit tests (6 per crate) passed. Clippy emitted no new warnings in
the touched crates (pre-existing warnings in core/lib/dal are unrelated).

zkstack dev fmt / zkstack dev lint were not run in this environment; the
narrower cargo fmt --check and cargo clippy above were used in their
place for the two affected crates.

Author checklist

  • PR title follows Conventional Commits.
  • Tests, docs, formatting, and linting were updated or marked not applicable.

Live infrastructure and deployment impact

  • No live infrastructure actions were run.
  • Merging this PR changes source and unit tests only and is not expected to
    deploy live services by itself.
  • Merging can affect future images built from main; rollout to a live
    environment (Hetzner external nodes, via-main-testnet, verifier nodes)
    remains a separate deployment approval and verification step. Operators
    rolling this out on a node that has already been reset by the previous
    buggy soft-reorg path may also need to restore the wallet bootstrap
    cursor; that recovery is out of scope for this PR.

…ia_l1_blocks

Both the main-node/external-node reorg detector and the verifier reorg detector
fetched the canonical reorg window with futures::buffer_unordered and then
compared local DB rows to canonical rows by positional zip. With a freshly
bootstrapped via_l1_blocks containing only the wallet/bootstrap block (for
example 100891), the 100-block detector window started at 100792, so the single
DB row at 100891 was compared against the canonical block at index 0 (height
100792). The hashes did not match and a false reorg at the wallet bootstrap
block was reported. On the main node, soft-reorg handling then reset the
via_btc_watch cursor below the wallet bootstrap block and broke wallet-resource
startup on the next restart.

Changes (apply to both detectors):
- fetch_blocks now returns Vec<(i64, Block)> with explicit heights and
  ascending sort, so callers no longer depend on buffer_unordered preserving
  request order. This also removes the latent ordering bug in sync_l1_blocks
  where new blocks were inserted under heights derived from the request range
  rather than the actually-fetched block.
- detect_reorg builds a HashMap<i64, String> of canonical hashes keyed by
  height and delegates to a pure scan_for_reorg helper that compares each DB
  row by its explicit number, never positionally.
- New ReorgScan::SparseAt(height) variant: when the canonical fetch lacks a
  height the local DB knows about, the detector logs and returns without
  acting instead of falsely demoting the BtcWatch cursor.

Regression coverage: six unit tests per crate, including
sparse_db_does_not_falsely_compare_against_window_start, which reproduces the
exact 100891-vs-100792 scenario observed during the Hetzner private
external-node rehearsal.

No DAL signatures, schema, config keys, metric names, or deployment desired
state were changed. init() still seeds a single bootstrap row; the
height-keyed comparison makes that safe rather than requiring a coherent
full-window backfill.

Amp-Thread-ID: https://ampcode.com/threads/T-019e3766-dbfa-71c9-865a-bd24243ae5e6
Co-authored-by: Amp <amp@ampcode.com>
Copilot AI review requested due to automatic review settings May 17, 2026 19:37

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 Via L1 reorg detection so sparse via_l1_blocks state is compared against canonical BTC blocks by explicit height rather than positional order, preventing false reorg detection during bootstrap-like states.

Changes:

  • Pairs fetched BTC blocks with their requested heights and sorts after buffer_unordered.
  • Replaces positional zip reorg comparison with a height-keyed HashMap scan helper.
  • Adds mirrored unit coverage for main-node and verifier sparse-window regression cases.

Reviewed changes

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

File Description
core/node/via_main_node_reorg_detector/src/lib.rs Updates main-node fetch, sync, and reorg detection logic to use explicit block heights.
core/node/via_main_node_reorg_detector/src/tests.rs Adds unit tests for sparse DB and height-keyed reorg scanning behavior.
via_verifier/node/via_reorg_detector/src/lib.rs Applies the same height-keyed fetch and reorg scan behavior to the verifier detector.
via_verifier/node/via_reorg_detector/src/tests.rs Adds verifier-side unit tests mirroring main-node reorg scan coverage.

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

@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 enhances reorg detection for both the main node and verifier by implementing height-keyed block comparison, replacing the previous positional approach. It introduces a ReorgScan enum and a scan_for_reorg utility to explicitly handle cases where the local database or canonical chain data is sparse, preventing false reorg reports. Review feedback highlights that the fetch_blocks implementation is currently too strict because it fails the entire operation on any single RPC error. This prevents the newly added SparseAt logic from handling transient failures gracefully, so it is recommended to allow partial results in the fetch logic.

Comment on lines +133 to +136
let mut blocks: Vec<(i64, Block)> = results
.into_iter()
.collect::<Result<_, _>>()
.context("Failed to fetch blocks")?;

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 current implementation of fetch_blocks is strict and will fail the entire iteration if a single block fetch fails due to the use of collect::<Result<Vec<_>, _>>(). This effectively makes the newly introduced ReorgScan::SparseAt logic in detect_reorg unreachable, as scan_for_reorg will only ever receive a complete set of blocks or not be called at all if an error occurs.

Consider making fetch_blocks more lenient by allowing partial results. This would allow the reorg detector to gracefully skip the check (via the SparseAt variant) when the RPC is temporarily missing some blocks, rather than logging a high-level error and failing the whole iteration.

        let mut blocks: Vec<(i64, Block)> = results
            .into_iter()
            .filter_map(|res| match res {
                Ok(b) => Some(b),
                Err(err) => {
                    tracing::warn!("Failed to fetch block from canonical chain: {err}");
                    None
                }
            })
            .collect();

        if blocks.is_empty() && from_block_height <= to_block_height {
            anyhow::bail!("Failed to fetch any blocks from the canonical chain");
        }

Comment on lines +170 to +173
let mut blocks: Vec<(i64, Block)> = results
.into_iter()
.collect::<Result<_, _>>()
.context("Failed to fetch blocks")?;

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

Similar to the main node detector, fetch_blocks here is strict, which prevents the ReorgScan::SparseAt logic from being utilized. Making it lenient by allowing partial results would enable the intended behavior of skipping the reorg check during transient RPC issues instead of failing the iteration with an error.

        let mut blocks: Vec<(i64, Block)> = results
            .into_iter()
            .filter_map(|res| match res {
                Ok(b) => Some(b),
                Err(err) => {
                    tracing::warn!("Failed to fetch block from canonical chain: {err}");
                    None
                }
            })
            .collect();

        if blocks.is_empty() && from_block_height <= to_block_height {
            anyhow::bail!("Failed to fetch any blocks from the canonical chain");
        }

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5133cad6-4596-444f-9fe1-07a1b75b63ed

📥 Commits

Reviewing files that changed from the base of the PR and between e744659 and 6e02d79.

📒 Files selected for processing (4)
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
  • via_verifier/node/via_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (10)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Preserve rich error context in production/library paths; prefer propagated errors and anyhow::Context / with_context over flattening failures into generic strings
Prefer one-line expressions and short helper calls when they remain readable and rustfmt accepts them; avoid manual wrapping churn and do not reformat unrelated existing code just to change line shape
When changing async fetch/compare code, preserve or explicitly encode ordering assumptions; do not compare height-indexed data by vector position unless the ordering contract is proven and tested

Run cargo test -p <crate-or-package> before submitting a pull request to verify tests pass for affected crates

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs

⚙️ CodeRabbit configuration file

**/*.rs: Focus on correctness, security, error handling, race conditions, async ordering,
DB semantics, height/hash/txid identity, and maintainability.
Do not request docstrings or unit tests unless tied to a concrete failure mode or invariant.
When changing a Via-specific path, consider the corresponding main-node or verifier sibling.

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
!(src/**/*.rs|tests/**/*.rs)

📄 CodeRabbit inference engine (AGENTS.md)

Avoid unwrap() / expect() in non-test runtime paths

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
**/via_*/**/*.{rs,toml}

📄 CodeRabbit inference engine (.github/pull_request_template.md)

For Via-specific code in reorg detection/L1 sync, BTC sender/watch/client, DA/Celestia, and hot database paths (via_*_dal), include a 'Performance / Complexity Impact' section describing performance characteristics, time complexity before vs. after, allocation behavior, and cache/memory access implications

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
**/{via_btc_watch,via_btc_client,*reorg_detector}/**/*.rs

📄 CodeRabbit inference engine (.github/ISSUE_TEMPLATE/l1_btc_reorg_sync_bug.yml)

For sparse DB rows, async fetches, batch RPC, zip, Vec index, heights, hashes, txids, batches, or timestamps: verify that the chain fetch preserves requested height order and document where.

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
**/{via_btc_watch,via_btc_client,via_btc_sender,*reorg_detector,*via_l1_block_dal}/**/*.rs

📄 CodeRabbit inference engine (.github/ISSUE_TEMPLATE/l1_btc_reorg_sync_bug.yml)

**/{via_btc_watch,via_btc_client,via_btc_sender,*reorg_detector,*via_l1_block_dal}/**/*.rs: Do not use Vec/index position or zip to bind height/hash/tx identity without explicit verification that order is preserved.
Bitcoin block hash, txid, batch number, or DB row must remain bound to the exact height, transaction, batch, or row identity it was fetched or stored for.

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
via_verifier/node/via_reorg_detector/**

⚙️ CodeRabbit configuration file

via_verifier/node/via_reorg_detector/**: Verifier reorg detector. Treat as sibling of the main-node version; check both when either changes.
Preserve explicit height/hash identity and DB ordering assumptions.

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
**/via_*reorg_detector*/**

⚙️ CodeRabbit configuration file

**/via_*reorg_detector*/**: This is high-risk Via-specific code. Apply strong scrutiny across these review lenses:

  • Identity and ordering assumptions (especially around heights, hashes, and async results)
  • Main-node vs verifier sibling consistency
  • Performance impact (time complexity, allocations, and cache behavior) when replacing algorithms or introducing new functions/data structures
  • Clear distinction between DB state, canonical data, and live system behavior

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
**/via_*/**

⚙️ CodeRabbit configuration file

**/via_*/**: This is Via-specific code. When the change involves replacing an existing algorithm or introducing new functions or data structures, consider and describe the performance impact (time complexity, allocations, and cache behavior where relevant), in addition to correctness.

Files:

  • via_verifier/node/via_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/lib.rs
  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
core/node/via_main_node_reorg_detector/**

⚙️ CodeRabbit configuration file

core/node/via_main_node_reorg_detector/**: Main-node reorg detector. Review together with via_verifier/node/via_reorg_detector when behavior overlaps.
Enforce explicit height/hash identity. Audit sparse rows, async fetch ordering, and Vec/zip usage.

Files:

  • core/node/via_main_node_reorg_detector/src/lib.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
**/*{test,spec}*.rs

📄 CodeRabbit inference engine (.github/ISSUE_TEMPLATE/l1_btc_reorg_sync_bug.yml)

Regression tests for L1/BTC/reorg fixes must prove that sparse via_l1_blocks rows cannot cause height/hash miscomparison.

Files:

  • via_verifier/node/via_reorg_detector/src/tests.rs
  • core/node/via_main_node_reorg_detector/src/tests.rs
🔇 Additional comments (4)
core/node/via_main_node_reorg_detector/src/lib.rs (1)

1-1: LGTM!

Also applies to: 14-45, 111-141, 176-176, 213-223, 244-269

core/node/via_main_node_reorg_detector/src/tests.rs (1)

1-104: LGTM!

via_verifier/node/via_reorg_detector/src/lib.rs (1)

1-1: LGTM!

Also applies to: 15-46, 148-178, 183-183, 220-229, 251-275

via_verifier/node/via_reorg_detector/src/tests.rs (1)

1-89: LGTM!


Walkthrough

Both reorg detectors refactor block fetching and comparison logic to use explicit height-keyed matching instead of positional iteration. A new pure scan_for_reorg helper classifies reorg status; fetch_blocks returns ordered (height, Block) pairs; and detect_reorg builds a height→hash map and delegates comparison to the new helper, treating sparse (missing canonical) heights as inconclusive.

Changes

Reorg Detector Height-Keyed Refactoring

Layer / File(s) Summary
Height-keyed scan helper and comprehensive test coverage
core/node/via_main_node_reorg_detector/src/lib.rs, core/node/via_main_node_reorg_detector/src/tests.rs, via_verifier/node/via_reorg_detector/src/lib.rs, via_verifier/node/via_reorg_detector/src/tests.rs
New ReorgScan enum and pure scan_for_reorg function introduced in both modules for height-keyed classification of no-reorg, hash divergence, and sparse-canonical cases. Both test modules comprehensively validate the helper across regression, sparse-data short-circuit, divergence-detection, and edge cases.
Block fetch refactoring to explicit height-keyed pairs
core/node/via_main_node_reorg_detector/src/lib.rs, via_verifier/node/via_reorg_detector/src/lib.rs
Both fetch_blocks implementations now return (i64, Block) tuples and explicitly restore ascending height order after concurrent fetching. is_canonical_chain in both modules is updated to destructure and validate using the explicit height field.
Reorg detection refactored to height-keyed HashMap comparison
core/node/via_main_node_reorg_detector/src/lib.rs, via_verifier/node/via_reorg_detector/src/lib.rs
detect_reorg in both modules builds a HashMap<i64, String> from fetched canonical blocks, delegates comparison to scan_for_reorg, and returns early (inconclusive) when canonical data is sparse. L1 sync block insertion is updated to iterate over (height, block) tuples directly instead of positional zipping.

Possibly Related Issues

  • Reorg detector should handle sparse via_l1_blocks windows safely #354: The changes directly address the same positional-comparison bug documented in this issue; both detectors were refactored using the same strategy: height-keyed block fetching, HashMap-based canonical matching, and the new scan_for_reorg helper to handle sparse-data short-circuits.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing reorg detection by comparing L1 blocks using explicit height instead of positional zip on sparse via_l1_blocks tables.
Description check ✅ Passed The description comprehensively covers all template sections: Why (root cause analysis of false reorg), What changed (fetch_blocks refactor, HashMap-based comparison, SparseAt handling), Performance/Complexity (detailed O() analysis with table), Boundaries (scope limited to source/tests), How to review (specific file/function focus areas), Checks run, and Live infrastructure impact.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

2 participants