zebra-rpc checks its generated protobuf artifacts into proto/__generated__/ so the crate builds without protoc. When protoc is available, build.rs refreshes them β except the refresh can never fire for the binary descriptor.
zebra-rpc/build.rs:45:
if fs::read_to_string(&out_path).ok() != fs::read_to_string(&generated_path).ok() {
fs::copy(out_path, generated_path)?;
}
indexer_descriptor.bin isn't valid UTF-8, so both reads fail, .ok() maps both to None, and None != None is false. The other branch of the same function (line 53) already uses binary-safe fs::read.
So the committed descriptor has been frozen since #9819 created the mechanism (2025-09-12), and went stale when #10776 added rpc GetBlock (2026-06-23). The .proto declares it:
$ curl -s https://raw.githubusercontent.com/ZcashFoundation/zebra/main/zebra-rpc/proto/indexer.proto | grep "rpc GetBlock"
rpc GetBlock(BlockRequest) returns (BlockAndHash);
The shipped descriptor doesn't:
$ curl -sO https://raw.githubusercontent.com/ZcashFoundation/zebra/main/zebra-rpc/proto/__generated__/indexer_descriptor.bin
$ strings indexer_descriptor.bin | grep -E "^(ChainTipChange|MempoolChange|NonFinalizedStateChange|GetBlock)$"
ChainTipChange
NonFinalizedStateChange
MempoolChange
BlockRequest is missing too.
Impact is limited: the descriptor only feeds tonic_reflection, and the service comes from the generated .rs, which updates fine β so GetBlock works, it just isn't discoverable by reflection. Only builds without protoc read the committed file, i.e. crates.io consumers, for whom grpcurl and similar won't list it.
CI can't catch this. CI has protoc, so it regenerates into OUT_DIR and passes regardless of what's committed. Nothing compares the committed artifacts to the .proto. That matters more going forward β #10953 adds a second descriptor and ~2,300 lines of generated Rust under the same unverified path.
Fix
- Use
fs::read in the refresh comparison, matching line 53.
- Commit the regenerated
indexer_descriptor.bin.
- Add a CI step that builds with
protoc then runs git diff --exit-code -- zebra-rpc/proto/__generated__.
zebra-rpcchecks its generated protobuf artifacts intoproto/__generated__/so the crate builds withoutprotoc. Whenprotocis available,build.rsrefreshes them β except the refresh can never fire for the binary descriptor.zebra-rpc/build.rs:45:indexer_descriptor.binisn't valid UTF-8, so both reads fail,.ok()maps both toNone, andNone != Noneisfalse. The other branch of the same function (line 53) already uses binary-safefs::read.So the committed descriptor has been frozen since #9819 created the mechanism (2025-09-12), and went stale when #10776 added
rpc GetBlock(2026-06-23). The.protodeclares it:The shipped descriptor doesn't:
BlockRequestis missing too.Impact is limited: the descriptor only feeds
tonic_reflection, and the service comes from the generated.rs, which updates fine β soGetBlockworks, it just isn't discoverable by reflection. Only builds withoutprotocread the committed file, i.e. crates.io consumers, for whomgrpcurland similar won't list it.CI can't catch this. CI has
protoc, so it regenerates intoOUT_DIRand passes regardless of what's committed. Nothing compares the committed artifacts to the.proto. That matters more going forward β #10953 adds a second descriptor and ~2,300 lines of generated Rust under the same unverified path.Fix
fs::readin the refresh comparison, matching line 53.indexer_descriptor.bin.protocthen runsgit diff --exit-code -- zebra-rpc/proto/__generated__.