Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9,072 changes: 7,814 additions & 1,258 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 22 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,33 @@ package.edition = "2024"
http = "1.1.0"
hyper-util = "0.1.20"
hyper = { version = "1" }
hyper-rustls = { version = "0.27", default-features = false, features = ["ring", "http1", "tls12", "logging", "native-tokio"] }
hyper-rustls = { version = "0.27", default-features = false, features = [
"ring",
"http1",
"tls12",
"logging",
"native-tokio",
] }
http-body-util = "0.1.3"
rustls-pemfile = "2"
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "logging", "tls12"] }
tokio-rustls = { version = "0.26", default-features = false, features = [
"ring",
"logging",
"tls12",
] }
tokio = { version = "1" }
tonic = { version = "0.14.4", features = ["tls-webpki-roots"] }

# nym / socks5
nym-sdk = { git = "https://github.com/zingolabs/nym.git", branch = "mods-for-zingolib" }
nym-http-api-client = { git = "https://github.com/zingolabs/nym.git", branch = "mods-for-zingolib", package = "nym-http-api-client" }
nym-validator-client = { git = "https://github.com/zingolabs/nym.git", branch = "mods-for-zingolib", package = "nym-validator-client", default-features = false, features = [
"http-client",
] }
tokio-socks = "0.5"
tower = "0.5"
webpki-roots = "0.26"

# error
thiserror = "1.0.64"
x509-parser = "0.18"
Expand Down
18 changes: 18 additions & 0 deletions zingo-netutils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,29 @@ tonic.workspace = true
lightwallet-protocol.workspace = true
tokio-stream = { workspace = true, optional = true }
zcash_client_backend = { workspace = true, optional = true }
nym-sdk = { workspace = true, optional = true }
nym-http-api-client = { workspace = true, optional = true }
nym-validator-client = { workspace = true, optional = true }
tokio-socks = { workspace = true, optional = true }
tower = { workspace = true, optional = true }
hyper-util = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
webpki-roots = { workspace = true, optional = true }

[features]
globally-public-transparent = ["dep:tokio-stream"]
ping-very-insecure = []
back_compatible = ["dep:zcash_client_backend"]
nym = [
"dep:nym-sdk",
"dep:nym-http-api-client",
"dep:nym-validator-client",
"dep:tokio-socks",
"dep:tower",
"dep:hyper-util",
"dep:tokio",
"dep:webpki-roots",
]

[dev-dependencies]
tokio-stream.workspace = true
Expand Down
50 changes: 50 additions & 0 deletions zingo-netutils/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,45 @@ pub enum GetClientError {

#[error(transparent)]
Transport(#[from] tonic::transport::Error),

#[cfg(feature = "nym")]
#[error("SOCKS5 proxy error: {0}")]
SocksProxy(std::io::Error),

#[cfg(feature = "nym")]
#[error("failed to start Nym proxy: {0}")]
NymStart(Box<NymProxyError>),

#[cfg(feature = "nym")]
#[error(
"proxied request but no proxy configured — call with_nym() or with_socks_proxy() first"
)]
NoProxy,
}

/// Error from [`NymProxy`](super::NymProxy) lifecycle operations.
#[cfg(feature = "nym")]
#[derive(Debug, thiserror::Error)]
pub enum NymProxyError {
/// Failed to build the Nym mixnet client.
#[error("failed to build Nym client: {0}")]
Build(Box<nym_sdk::Error>),

/// Failed to connect to the Nym mixnet.
#[error("failed to connect to Nym mixnet: {0}")]
Connect(Box<nym_sdk::Error>),

/// Failed to query the Nym API for service providers.
#[error("Nym API query failed: {0}")]
DiscoveryApi(String),

/// No public exit gateway could be discovered.
#[error("no public Nym exit gateway found")]
NoProvider,

/// End-to-end connectivity check through the SOCKS5 tunnel failed.
#[error("connectivity check failed: {0}")]
ConnectivityCheck(String),
}

#[cfg(test)]
Expand All @@ -47,6 +86,17 @@ mod get_client_error_tests {
// Verify the From impl exists at compile time.
let _: fn(tonic::transport::Error) -> GetClientError = GetClientError::from;
}

#[cfg(feature = "nym")]
#[test]
fn socks_proxy_error_display() {
let e = GetClientError::SocksProxy(std::io::Error::new(
std::io::ErrorKind::ConnectionRefused,
"connection refused",
));
assert!(e.to_string().contains("SOCKS5 proxy error"));
assert!(e.to_string().contains("connection refused"));
}
}

/// Error from the `get_info` (`GetLightdInfo`) RPC.
Expand Down
139 changes: 113 additions & 26 deletions zingo-netutils/src/globally_public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,61 +33,80 @@ pub trait TransparentIndexer: Indexer {
type GetAddressUtxosError: std::error::Error;
type GetAddressUtxosStreamError: std::error::Error;

/// Return a stream of transactions for a transparent address in a block range.
///
/// Same behavior as
/// [`get_taddress_transactions`](TransparentIndexer::get_taddress_transactions).
/// This method is a legacy alias; callers should migrate.
#[cfg(not(feature = "nym"))]
#[deprecated(note = "use get_taddress_transactions instead")]
fn get_taddress_txids(
&self,
filter: TransparentAddressBlockFilter,
) -> impl Future<Output = Result<tonic::Streaming<RawTransaction>, Self::GetTaddressTxidsError>>;
#[cfg(feature = "nym")]
#[deprecated(note = "use get_taddress_transactions instead")]
fn get_taddress_txids(
&self,
filter: TransparentAddressBlockFilter,
proxied: bool,
) -> impl Future<Output = Result<tonic::Streaming<RawTransaction>, Self::GetTaddressTxidsError>>;

/// Return a stream of transactions for a transparent address in a block range.
///
/// Results are sorted by block height. Mempool transactions are not included.
#[cfg(not(feature = "nym"))]
fn get_taddress_transactions(
&self,
filter: TransparentAddressBlockFilter,
) -> impl Future<Output = Result<tonic::Streaming<RawTransaction>, Self::GetTaddressTransactionsError>>;
#[cfg(feature = "nym")]
fn get_taddress_transactions(
&self,
filter: TransparentAddressBlockFilter,
proxied: bool,
) -> impl Future<Output = Result<tonic::Streaming<RawTransaction>, Self::GetTaddressTransactionsError>>;

/// Return the total confirmed balance for the given transparent addresses.
///
/// The returned [`Balance`] contains the sum in zatoshis. Only confirmed
/// (mined) outputs are included; mempool UTXOs are not counted.
#[cfg(not(feature = "nym"))]
fn get_taddress_balance(
&self,
addresses: AddressList,
) -> impl Future<Output = Result<Balance, Self::GetTaddressBalanceError>>;
#[cfg(feature = "nym")]
fn get_taddress_balance(
&self,
addresses: AddressList,
proxied: bool,
) -> impl Future<Output = Result<Balance, Self::GetTaddressBalanceError>>;

/// Return the total confirmed balance by streaming addresses to the server.
///
/// Client-streaming variant of
/// [`get_taddress_balance`](TransparentIndexer::get_taddress_balance).
/// The addresses are streamed individually, avoiding message size limits
/// for large address sets. Returns the same [`Balance`] sum.
#[cfg(not(feature = "nym"))]
fn get_taddress_balance_stream(
&self,
addresses: Vec<Address>,
) -> impl Future<Output = Result<Balance, Self::GetTaddressBalanceStreamError>>;
#[cfg(feature = "nym")]
fn get_taddress_balance_stream(
&self,
addresses: Vec<Address>,
proxied: bool,
) -> impl Future<Output = Result<Balance, Self::GetTaddressBalanceStreamError>>;

/// Return UTXOs for the given addresses as a single response.
///
/// Results are sorted by block height. Pass `max_entries = 0` for
/// unlimited results.
#[cfg(not(feature = "nym"))]
fn get_address_utxos(
&self,
arg: GetAddressUtxosArg,
) -> impl Future<Output = Result<GetAddressUtxosReplyList, Self::GetAddressUtxosError>>;
#[cfg(feature = "nym")]
fn get_address_utxos(
&self,
arg: GetAddressUtxosArg,
proxied: bool,
) -> impl Future<Output = Result<GetAddressUtxosReplyList, Self::GetAddressUtxosError>>;

/// Return a stream of UTXOs for the given addresses.
///
/// Prefer this over [`get_address_utxos`](TransparentIndexer::get_address_utxos)
/// when the result set may be large.
#[cfg(not(feature = "nym"))]
fn get_address_utxos_stream(
&self,
arg: GetAddressUtxosArg,
) -> impl Future<
Output = Result<tonic::Streaming<GetAddressUtxosReply>, Self::GetAddressUtxosStreamError>,
>;
#[cfg(feature = "nym")]
fn get_address_utxos_stream(
&self,
arg: GetAddressUtxosArg,
proxied: bool,
) -> impl Future<
Output = Result<tonic::Streaming<GetAddressUtxosReply>, Self::GetAddressUtxosStreamError>,
>;
Expand All @@ -102,14 +121,26 @@ impl TransparentIndexer for GrpcIndexer {
type GetAddressUtxosStreamError = GetAddressUtxosStreamError;

#[allow(deprecated)]
#[cfg(not(feature = "nym"))]
async fn get_taddress_txids(
&self,
filter: TransparentAddressBlockFilter,
) -> Result<tonic::Streaming<RawTransaction>, GetTaddressTxidsError> {
let (mut client, request) = self.stream_call(filter).await?;
Ok(client.get_taddress_txids(request).await?.into_inner())
}
#[allow(deprecated)]
#[cfg(feature = "nym")]
async fn get_taddress_txids(
&self,
filter: TransparentAddressBlockFilter,
proxied: bool,
) -> Result<tonic::Streaming<RawTransaction>, GetTaddressTxidsError> {
let (mut client, request) = self.stream_call_routed(filter, proxied).await?;
Ok(client.get_taddress_txids(request).await?.into_inner())
}

#[cfg(not(feature = "nym"))]
async fn get_taddress_transactions(
&self,
filter: TransparentAddressBlockFilter,
Expand All @@ -120,15 +151,38 @@ impl TransparentIndexer for GrpcIndexer {
.await?
.into_inner())
}
#[cfg(feature = "nym")]
async fn get_taddress_transactions(
&self,
filter: TransparentAddressBlockFilter,
proxied: bool,
) -> Result<tonic::Streaming<RawTransaction>, GetTaddressTransactionsError> {
let (mut client, request) = self.stream_call_routed(filter, proxied).await?;
Ok(client
.get_taddress_transactions(request)
.await?
.into_inner())
}

#[cfg(not(feature = "nym"))]
async fn get_taddress_balance(
&self,
addresses: AddressList,
) -> Result<Balance, GetTaddressBalanceError> {
let (mut client, request) = self.time_boxed_call(addresses).await?;
Ok(client.get_taddress_balance(request).await?.into_inner())
}
#[cfg(feature = "nym")]
async fn get_taddress_balance(
&self,
addresses: AddressList,
proxied: bool,
) -> Result<Balance, GetTaddressBalanceError> {
let (mut client, request) = self.time_boxed_call_routed(addresses, proxied).await?;
Ok(client.get_taddress_balance(request).await?.into_inner())
}

#[cfg(not(feature = "nym"))]
async fn get_taddress_balance_stream(
&self,
addresses: Vec<Address>,
Expand All @@ -140,20 +194,53 @@ impl TransparentIndexer for GrpcIndexer {
.await?
.into_inner())
}
#[cfg(feature = "nym")]
async fn get_taddress_balance_stream(
&self,
addresses: Vec<Address>,
proxied: bool,
) -> Result<Balance, GetTaddressBalanceStreamError> {
let mut client = self.get_client_routed(proxied).await?;
let stream = tokio_stream::iter(addresses);
Ok(client
.get_taddress_balance_stream(stream)
.await?
.into_inner())
}

#[cfg(not(feature = "nym"))]
async fn get_address_utxos(
&self,
arg: GetAddressUtxosArg,
) -> Result<GetAddressUtxosReplyList, GetAddressUtxosError> {
let (mut client, request) = self.time_boxed_call(arg).await?;
Ok(client.get_address_utxos(request).await?.into_inner())
}
#[cfg(feature = "nym")]
async fn get_address_utxos(
&self,
arg: GetAddressUtxosArg,
proxied: bool,
) -> Result<GetAddressUtxosReplyList, GetAddressUtxosError> {
let (mut client, request) = self.time_boxed_call_routed(arg, proxied).await?;
Ok(client.get_address_utxos(request).await?.into_inner())
}

#[cfg(not(feature = "nym"))]
async fn get_address_utxos_stream(
&self,
arg: GetAddressUtxosArg,
) -> Result<tonic::Streaming<GetAddressUtxosReply>, GetAddressUtxosStreamError> {
let (mut client, request) = self.stream_call(arg).await?;
Ok(client.get_address_utxos_stream(request).await?.into_inner())
}
#[cfg(feature = "nym")]
async fn get_address_utxos_stream(
&self,
arg: GetAddressUtxosArg,
proxied: bool,
) -> Result<tonic::Streaming<GetAddressUtxosReply>, GetAddressUtxosStreamError> {
let (mut client, request) = self.stream_call_routed(arg, proxied).await?;
Ok(client.get_address_utxos_stream(request).await?.into_inner())
}
}
Loading