# zingo-netutils
*Test counts reflect the `dev` branch and update on each push.*



A complete `Indexer` abstraction for communicating with Zcash chain indexers
(`lightwalletd` / `zainod`).
## Organizing principle
This crate defines the **`Indexer` trait** -- the sole interface a Zcash
wallet or tool needs to query, sync, and broadcast against a chain indexer.
The trait is implementation-agnostic: production code uses the provided
`GrpcIndexer` (gRPC over tonic), while tests can supply a mock implementor
with no network dependency.
All proto types are provided by
[`lightwallet-protocol`](https://crates.io/crates/lightwallet-protocol)
and re-exported via `pub use lightwallet_protocol` so consumers do not need
an additional dependency.
## Usage
```rust
use zingo_netutils::{GrpcIndexer, Indexer};
let uri: http::Uri = "https://zec.rocks:443".parse().unwrap();
let indexer = GrpcIndexer::new(uri)?;
let info = indexer.get_info().await?;
let tip = indexer.get_latest_block().await?;
```
`GrpcIndexer::new` validates the URI (scheme must be `http` or `https`,
authority must be present) and pre-builds the TLS endpoint. It returns
`Result<Self, GetClientError>` -- there is no invalid-state representation.
## Backwards compatibility
Code that needs a raw `CompactTxStreamerClient<Channel>` (e.g. pepper-sync)
can still obtain one:
```rust
// Using lightwallet-protocol types (preferred)
let client = indexer.get_client().await?;
// Using zcash_client_backend types (migration aid)
// Requires: zingo-netutils = { features = ["back_compatible"] }
let client = indexer.get_zcb_client().await?;
```
The `back_compatible` feature brings in `zcash_client_backend` as an
optional dependency and exposes `get_zcb_client()`, which returns
`zcash_client_backend`'s `CompactTxStreamerClient<Channel>`. This is a
bridge for consumers that have not yet migrated to `lightwallet-protocol`
types.
## Feature gates
All features are **off by default**.
| `globally-public-transparent` | `TransparentIndexer` sub-trait: t-address balance, transaction history, and UTXO queries. Requires `tokio-stream`. |
| `ping-very-insecure` | `Indexer::ping()` method. Name mirrors the lightwalletd `--ping-very-insecure` CLI flag required server-side. Testing only. |
| `back_compatible` | `GrpcIndexer::get_zcb_client()` returning `zcash_client_backend`'s client type for pepper-sync compatibility. |
## Error handling
Every trait method has a dedicated error enum (defined in `src/error.rs`).
Each enum has two variants:
- `GetClientError(GetClientError)` — the connection was never established.
`InvalidScheme` and `InvalidAuthority` are deterministic; `Transport`
may be transient and retryable.
- A method-specific gRPC variant wrapping `tonic::Status` — the server
received the request but returned an error.
`SendTransactionError` adds a third variant, `SendRejected(String)`, for
transactions the server evaluated and rejected (not retryable with the
same bytes).
All error types are bounded by `std::error::Error`. Transparent method
errors live in `error::transparent` (gated by `globally-public-transparent`).
Every error enum has a doc-test proving the contract: `From<GetClientError>`
produces the connection variant, `From<tonic::Status>` produces the
method-specific variant. Feature-gated doc-tests use `#[cfg]` so
`cargo test --doc` passes with or without features, and assertions
execute when the feature is enabled.
| `GetClientError` | `GrpcIndexer::new`, `get_client`, embedded in every method error |
| `GetInfoError` | `get_info` |
| `GetLatestBlockError` | `get_latest_block` |
| `SendTransactionError` | `send_transaction` |
| `GetTreeStateError` | `get_tree_state` |
| `GetBlockError` | `get_block` |
| `GetBlockNullifiersError` | `get_block_nullifiers` |
| `GetBlockRangeError` | `get_block_range` |
| `GetBlockRangeNullifiersError` | `get_block_range_nullifiers` |
| `GetTransactionError` | `get_transaction` |
| `GetMempoolTxError` | `get_mempool_tx` |
| `GetMempoolStreamError` | `get_mempool_stream` |
| `GetLatestTreeStateError` | `get_latest_tree_state` |
| `GetSubtreeRootsError` | `get_subtree_roots` |
| `PingError` | `ping` |
| `GetTaddressTxidsError` | `get_taddress_txids` |
| `GetTaddressTransactionsError` | `get_taddress_transactions` |
| `GetTaddressBalanceError` | `get_taddress_balance` |
| `GetTaddressBalanceStreamError` | `get_taddress_balance_stream` |
| `GetAddressUtxosError` | `get_address_utxos` |
| `GetAddressUtxosStreamError` | `get_address_utxos_stream` |
## Building docs
Docs must be built with `--all-features` so intra-doc links to
feature-gated items resolve:
```sh
RUSTDOCFLAGS="-D warnings" cargo doc --all-features --document-private-items
```
## TLS
HTTPS connections use rustls with webpki root certificates (via tonic's
`tls-webpki-roots` feature). No system OpenSSL installation is required.
## gRPC endpoint audit
Every `CompactTxStreamer` RPC from `walletrpc/service.proto` is covered.
The table below shows the mapping between proto RPCs and trait methods.
### Exact matches (13)
| `GetTreeState(BlockID)` | `get_tree_state(BlockId)` | `BlockId` | `TreeState` |
| `GetBlock(BlockID)` | `get_block(BlockId)` | `BlockId` | `CompactBlock` |
| `GetBlockNullifiers(BlockID)` | `get_block_nullifiers(BlockId)` | `BlockId` | `CompactBlock` |
| `GetBlockRange(BlockRange)` | `get_block_range(BlockRange)` | `BlockRange` | `Streaming<CompactBlock>` |
| `GetBlockRangeNullifiers(BlockRange)` | `get_block_range_nullifiers(BlockRange)` | `BlockRange` | `Streaming<CompactBlock>` |
| `GetTransaction(TxFilter)` | `get_transaction(TxFilter)` | `TxFilter` | `RawTransaction` |
| `GetTaddressTxids(TABF)` | `get_taddress_txids(TABF)` | `TABF` | `Streaming<RawTransaction>` |
| `GetTaddressTransactions(TABF)` | `get_taddress_transactions(TABF)` | `TABF` | `Streaming<RawTransaction>` |
| `GetTaddressBalance(AddressList)` | `get_taddress_balance(AddressList)` | `AddressList` | `Balance` |
| `GetMempoolTx(GetMempoolTxRequest)` | `get_mempool_tx(GetMempoolTxRequest)` | `GetMempoolTxRequest` | `Streaming<CompactTx>` |
| `GetSubtreeRoots(GetSubtreeRootsArg)` | `get_subtree_roots(GetSubtreeRootsArg)` | `GetSubtreeRootsArg` | `Streaming<SubtreeRoot>` |
| `GetAddressUtxos(GetAddressUtxosArg)` | `get_address_utxos(GetAddressUtxosArg)` | `GetAddressUtxosArg` | `GetAddressUtxosReplyList` |
| `GetAddressUtxosStream(GetAddressUtxosArg)` | `get_address_utxos_stream(GetAddressUtxosArg)` | `GetAddressUtxosArg` | `Streaming<GetAddressUtxosReply>` |
| `Ping(Duration)` | `ping(ProtoDuration)` | `ProtoDuration` | `PingResponse` |
### Intentional divergences (7)
| `GetLightdInfo(Empty)` | `get_info()` | Hides `Empty` arg | Ergonomic -- unit request type adds no information |
| `GetLatestBlock(ChainSpec)` | `get_latest_block()` | Hides `ChainSpec` arg | Same reason |
| `GetMempoolStream(Empty)` | `get_mempool_stream()` | Hides `Empty` arg | Same reason |
| `GetLatestTreeState(Empty)` | `get_latest_tree_state()` | Hides `Empty` arg | Same reason |
| `SendTransaction(RawTransaction)` | `send_transaction(Box<[u8]>)` | Abstracts input and output | Takes raw bytes, returns txid string -- decouples callers from proto |
| `GetTaddressBalanceStream(stream Address)` | `get_taddress_balance_stream(Vec<Address>)` | `Vec` instead of client stream | Impl converts to stream internally; simpler call-site API |
| `Ping(Duration)` | `ping(ProtoDuration)` | Type alias | Avoids collision with `std::time::Duration` |
### Compile-time enforcement
The `proto_agreement` test module (`src/proto_agreement.rs`) contains 20
dead-code async functions that reference both the generated
`CompactTxStreamerClient` method and the corresponding trait method with
explicit type annotations. If either side drifts from the proto, the
module fails to compile.
### Full RPC / method / error audit
Every proto RPC has a 1:1 trait method and a 1:1 error enum.
| 1 | `GetLightdInfo(Empty)` | `get_info` | `GetInfoError` |
| 2 | `GetLatestBlock(ChainSpec)` | `get_latest_block` | `GetLatestBlockError` |
| 3 | `SendTransaction(RawTransaction)` | `send_transaction` | `SendTransactionError` |
| 4 | `GetTreeState(BlockID)` | `get_tree_state` | `GetTreeStateError` |
| 5 | `GetBlock(BlockID)` | `get_block` | `GetBlockError` |
| 6 | `GetBlockNullifiers(BlockID)` | `get_block_nullifiers` | `GetBlockNullifiersError` |
| 7 | `GetBlockRange(BlockRange)` | `get_block_range` | `GetBlockRangeError` |
| 8 | `GetBlockRangeNullifiers(BlockRange)` | `get_block_range_nullifiers` | `GetBlockRangeNullifiersError` |
| 9 | `GetTransaction(TxFilter)` | `get_transaction` | `GetTransactionError` |
| 10 | `GetMempoolTx(GetMempoolTxRequest)` | `get_mempool_tx` | `GetMempoolTxError` |
| 11 | `GetMempoolStream(Empty)` | `get_mempool_stream` | `GetMempoolStreamError` |
| 12 | `GetLatestTreeState(Empty)` | `get_latest_tree_state` | `GetLatestTreeStateError` |
| 13 | `GetSubtreeRoots(GetSubtreeRootsArg)` | `get_subtree_roots` | `GetSubtreeRootsError` |
| 14 | `Ping(Duration)` | `ping` | `PingError` |
| 15 | `GetTaddressTxids(TABF)` | `get_taddress_txids` | `GetTaddressTxidsError` |
| 16 | `GetTaddressTransactions(TABF)` | `get_taddress_transactions` | `GetTaddressTransactionsError` |
| 17 | `GetTaddressBalance(AddressList)` | `get_taddress_balance` | `GetTaddressBalanceError` |
| 18 | `GetTaddressBalanceStream(stream Address)` | `get_taddress_balance_stream` | `GetTaddressBalanceStreamError` |
| 19 | `GetAddressUtxos(GetAddressUtxosArg)` | `get_address_utxos` | `GetAddressUtxosError` |
| 20 | `GetAddressUtxosStream(GetAddressUtxosArg)` | `get_address_utxos_stream` | `GetAddressUtxosStreamError` |
All error enums share `GetClientError` (connection failure) as their first
variant. Each has a method-specific second variant wrapping `tonic::Status`.
`SendTransactionError` adds a third variant, `SendRejected(String)`.
## License
[MIT](LICENSE-MIT)