Skip to content

feat(rpc): add lightwalletd-compatible gRPC server implementing CompactTxStreamer#10953

Draft
arya2 wants to merge 3 commits into
mainfrom
lightwalletd-grpc
Draft

feat(rpc): add lightwalletd-compatible gRPC server implementing CompactTxStreamer#10953
arya2 wants to merge 3 commits into
mainfrom
lightwalletd-grpc

Conversation

@arya2

@arya2 arya2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Motivation

Zcash light clients currently need a separate lightwalletd instance sitting between them and a full node. This PR lets Zebra serve light clients directly by implementing the lightwalletd CompactTxStreamer gRPC interface in zebra-rpc.

Solution

Adds a new zebra_rpc::lightwalletd module: a tonic gRPC server implementing all 18 CompactTxStreamer methods, enabled with a new rpc.lightwalletd_listen_addr config field (disabled by default).

  • The service.proto / compact_formats.proto files from zcash/lightwalletd are vendored into zebra-rpc/proto/ and compiled by the existing build-script mechanism, with generated code checked into proto/__generated__/ so crates.io builds don't need protoc.
  • Wallet-shaped methods (GetTreeState, GetSubtreeRoots, GetTaddressBalance, GetTaddressTxids, GetAddressUtxos, SendTransaction, GetLightdInfo) call the existing JSON-RPC RpcImpl methods in-process — the same methods lightwalletd calls on zcashd/Zebra over HTTP.
  • Compact blocks (GetBlock, GetBlockRange, and the nullifier variants) and mempool streams (GetMempoolTx, GetMempoolStream) are built directly from the read-state and mempool services, following the existing indexer gRPC server's patterns. Compact-block tree sizes are fetched by the block's own hash so a concurrent reorg can't make ChainMetadata inconsistent with the block.
  • Wire-format details were verified against the lightwalletd Go implementation, including two places where lightwalletd's behavior contradicts the proto comments: Exclude entries in GetMempoolTx are little-endian txid suffixes, and GetMempoolStream sends height: 0 for mempool transactions (Figure out what to do in librustzcash about the broken GetTransaction method of lightwalletd zcash/librustzcash#1484).
  • Ping is disabled, matching production lightwalletd instances.

Tests

  • New unit tests for BlockID conversion and Exclude list matching (byte order, ambiguous and empty entries).
  • All existing zebra-rpc tests pass (cargo nextest run -p zebra-rpc --release: 113 passed).
  • Manual end-to-end test on Regtest: started zebrad with lightwalletd_listen_addr set, mined 5 blocks via the generate RPC, and exercised 12 methods with a tonic CompactTxStreamerClient (GetLightdInfo, GetLatestBlock, GetBlock by height and by hash, GetBlockRange, GetTreeState, GetLatestTreeState, GetMempoolTx, GetTaddressBalance, GetTaddressTxids, GetAddressUtxos), verifying hashes, heights, coinbase balances, and tree states.
  • cargo fmt --check and cargo clippy --workspace --all-targets are clean for the touched crates.

Specifications & References

Follow-up Work

  • GetBlockRange issues 3 sequential state reads per block; pipelining them would matter for full-chain light-client sync throughput.
  • pub use wire::* re-exports all generated proto types from zebra-rpc; a curated re-export would shrink the semver-checked public API surface.
  • The indexer and lightwalletd gRPC servers share near-identical spawn code that could be deduplicated.
  • Note: fixing the build script's descriptor-copy comparison (binary-safe fs::read) means checked-in *_descriptor.bin files now refresh from local protoc output, which regenerated indexer_descriptor.bin in this PR and may churn across contributor protoc versions.

AI Disclosure

  • AI tools were used: Claude Code wrote the implementation, tests, and this PR description, with multi-agent review; the author directed and verified the work.

PR Checklist

  • The PR title follows conventional commits format: type(scope): description
  • The PR follows the contribution guidelines.
  • This change was discussed in an issue or with the team beforehand.
  • The solution is tested.
  • The documentation and changelogs are up to date.

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

Adds an opt-in CompactTxStreamer gRPC server so Zebra can directly serve light clients.

Changes:

  • Implements gRPC handlers using Zebra RPC, state, tip, and mempool services.
  • Adds configuration, startup wiring, vendored protobufs, and generated bindings.
  • Updates user and crate changelogs.

Assessment: The implementation targets an outdated lightwalletd schema and has unresolved correctness, DoS, backpressure, and test-coverage issues. The supplied metadata also lacks a link to the pre-discussed issue or maintainer acknowledgment.

Reviewed changes

Copilot reviewed 12 out of 15 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
CHANGELOG.md Documents the new server.
zebra-rpc/CHANGELOG.md Records the crate API addition.
zebra-rpc/build.rs Builds or copies protobuf artifacts.
zebra-rpc/proto/compact_formats.proto Defines compact block messages.
zebra-rpc/proto/service.proto Defines CompactTxStreamer.
zebra-rpc/proto/__generated__/cash.z.wallet.sdk.rpc.rs Generated tonic bindings.
zebra-rpc/src/config/rpc.rs Adds the listen address setting.
zebra-rpc/src/lib.rs Exposes the module.
zebra-rpc/src/lightwalletd.rs Converts chain data to compact messages.
zebra-rpc/src/lightwalletd/methods.rs Implements the gRPC methods.
zebra-rpc/src/lightwalletd/server.rs Initializes the tonic server.
zebra-rpc/src/server/tests/vectors.rs Updates configuration fixtures.
zebrad/src/commands/start.rs Starts and monitors the server.

Comment on lines +41 to +45
message CompactTx {
// Index and hash will allow the receiver to call out to chain
// explorers or other data structures to retrieve more information
// about this transaction.
uint64 index = 1; // the index within the full block
continue;
}

let mut matches = txids.iter().filter(|txid| txid.0.ends_with(suffix));
let (sapling_response, orchard_response) = futures::join!(sapling_request, orchard_request);

let sapling_tree_size = match sapling_response {
Ok(ReadResponse::SaplingTree(tree)) => tree.map(|tree| tree.count()).unwrap_or_default(),
};

let orchard_tree_size = match orchard_response {
Ok(ReadResponse::OrchardTree(tree)) => tree.map(|tree| tree.count()).unwrap_or_default(),

let subtree_root = SubtreeRoot {
root_hash,
completing_block_hash: completing_block_hash.0.to_vec(),
Comment on lines +739 to +741
if response_sender.send(response).await.is_err() || is_err {
return;
}
Comment on lines +481 to +483
if response_sender.send(Ok(subtree_root)).await.is_err() {
return;
}
&self,
request: Request<AddressList>,
) -> Result<Response<Balance>, Status> {
let addresses = request.into_inner().addresses;
Comment on lines +798 to +800
let response = rpc
.get_address_utxos(GetAddressUtxosRequest::new(args.addresses, false))
.await
Comment on lines +48 to +50
#[tonic::async_trait]
impl<Rpc, ReadStateService, Mempool, Tip> CompactTxStreamer
for LightwalletdRPC<Rpc, ReadStateService, Mempool, Tip>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-feature Category: New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants