Skip to content

fix(rpc): use consistent chain-state snapshots in getblock, getblockheader, gettxout#10606

Open
zmanian wants to merge 3 commits into
ZcashFoundation:mainfrom
zmanian:fix/rpc-consistent-snapshots
Open

fix(rpc): use consistent chain-state snapshots in getblock, getblockheader, gettxout#10606
zmanian wants to merge 3 commits into
ZcashFoundation:mainfrom
zmanian:fix/rpc-consistent-snapshots

Conversation

@zmanian
Copy link
Copy Markdown
Contributor

@zmanian zmanian commented May 20, 2026

Motivation

Closes #10550.

Several RPC methods issue multiple sequential ReadStateService queries and combine the results into a single response. Each oneshot call independently samples the latest best chain, so a reorg or tip advance between queries can produce internally inconsistent responses. PR #10523 fixed this for getrawtransaction; this PR addresses the three remaining sibling sites called out in #10550 (getblock, getblockheader, gettxout), and also folds in the verbosity-2 panic reported in the issue by @sangsoo-osec (via @mpguerra).

Solution

  • getblock (verbosity 1 and 2): construct the transaction-fetch ReadRequest after shadowing hash_or_height with the resolved hash, so the TransactionIdsForBlock / BlockAndSize lookup is taken from the same chain view as the header. Header/orchard/block-info already used the resolved hash; only the transaction read was racy.
  • getblockheader (verbose): pass the resolved hash to the SaplingTree lookup. The Depth read was already pinned to hash; SaplingTree was still using the caller-supplied hash_or_height.
  • gettxout: extend MinedTx with a best_chain_tip_hash field captured in the same tip(chain, db) call used to compute confirmations. get_tx_out now drops the separate Tip query entirely and uses that hash as the response's bestblock.
  • Defense in depth for the verbosity-2 panic: in the getblock verbosity-2 transaction-serialization loop, replace the confirmations.try_into().expect(...) (which panicked when confirmations = -1) with u32::try_from(...).ok() and pass Some(confirmations >= 0) for in_active_chain. The atomic-read fix prevents this state from being produced in normal flow, but a graceful RPC error is the right floor.

Tests

  • New mock-based regression test rpc_getblock_verbosity_2_side_chain_no_panic drives the verbosity-2 path with Depth = None (i.e. confirmations = -1) and asserts that:
    • get_block returns a normal response instead of panicking,
    • each transaction object is labeled with height = -1 and confirmations = 0 (the existing zcashd-compatible "side chain" shape).
  • Existing rpc_getblock, rpc_getblockheader, rpc_gettxout, and zebra-state populated-state tests still pass (MinedTx's new field is set from the same chain-snapshot tip(...) lookup that already computed confirmations).
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test -p zebra-rpc --lib
cargo test -p zebra-state --lib

All clean.

Linked issue

AI disclosure

Drafted with Claude Code (RPC method edits, MinedTx field addition, regression test scaffolding). The author reviewed each change and is the responsible author.

…eader, gettxout

Several RPC methods issue multiple sequential ReadStateService queries and
combine the results into a single response. Each oneshot call independently
samples the latest best chain, so a reorg or tip advance between queries can
produce internally inconsistent responses. PR ZcashFoundation#10523 fixed this for
getrawtransaction; this commit applies the same pattern to the three remaining
sibling sites.

- getblock (verbosity 1 and 2): build the transaction-fetch ReadRequest after
  shadowing hash_or_height with the resolved hash, so the transactions are
  read from the same chain view as the header.
- getblockheader (verbose): pass the resolved hash to the SaplingTree query so
  it is taken from the same chain view as the BlockHeader/Depth reads.
- gettxout: drop the separate Tip query and reuse the best-chain tip hash
  captured in the Transaction read's snapshot (new MinedTx.best_chain_tip_hash
  field), so the response's bestblock anchor matches the chain view used to
  compute confirmations.

Also hardens the getblock verbosity-2 path so the confirmations = -1 case
(block no longer on the best chain) returns a normal RPC response labeled
as not in the active chain, instead of panicking on a u32 try_into during
transaction serialization.

Closes ZcashFoundation#10550

AI disclosure: drafted with Claude Code; author reviewed and is responsible
for the change.
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@zmanian
Copy link
Copy Markdown
Contributor Author

zmanian commented May 20, 2026

Codex code review verdict: APPROVE. One recommendation surfaced — added a zebra-state/CHANGELOG.md entry for the public MinedTx::best_chain_tip_hash addition (commit e965fd2).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use consistent chain-state snapshots for multi-query RPC methods

2 participants