// file: crates/ksp-onchain-transport-lib/src/grpc_channel.rs // version: 1 /// Prepared Yellowstone gRPC channel owned by KSP Transport. /// /// `pre.002` intentionally prepares a lazy HTTP/2 channel without performing network I/O. TLS configuration, provider-neutral metadata injection and live /// connection/error semantics are added by `pre.003`. The underlying Tonic channel and upstream Yellowstone protobuf types are never exposed publicly. pub struct YellowstoneGrpcChannel { endpoint_name: std::string::String, provider: crate::YellowstoneGrpcProviderName, cluster: crate::YellowstoneGrpcClusterName, channel: tonic::transport::Channel, } impl YellowstoneGrpcChannel { /// Prepares one lazy Yellowstone gRPC channel from validated Transport-owned endpoint settings. /// /// This operation performs no network connection. HTTPS endpoints remain syntactically accepted here; TLS is deliberately completed in `pre.003` /// before any live call is allowed. pub fn prepare(endpoint: &crate::YellowstoneGrpcEndpointSettings) -> ksp_core_lib::Result { if let std::result::Result::Err(error) = endpoint.validate() { return std::result::Result::Err(error); } if !endpoint.enabled() { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "disabled Yellowstone gRPC endpoint cannot prepare a channel") .with_context("field", "grpc_endpoint.enabled") .with_context("endpoint_name", endpoint.name()), ); } let tonic_endpoint = match tonic::transport::Endpoint::from_shared(endpoint.url().as_str().to_owned()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { ksp_logging_lib::warn!( target: crate::TRACING_TARGET, endpoint_name = endpoint.name(), provider = endpoint.provider().as_str(), cluster = endpoint.cluster().as_str(), "failed to prepare Yellowstone gRPC channel URI" ); return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "Yellowstone gRPC channel could not be prepared") .with_context("endpoint_name", endpoint.name()), ); }, }; let tonic_endpoint = tonic_endpoint .connect_timeout(endpoint.session().connect_timeout()) .timeout(endpoint.session().unary_timeout()) .buffer_size(endpoint.session().request_channel_capacity()); let channel = tonic_endpoint.connect_lazy(); ksp_logging_lib::debug!( target: crate::TRACING_TARGET, endpoint_name = endpoint.name(), provider = endpoint.provider().as_str(), cluster = endpoint.cluster().as_str(), "prepared lazy Yellowstone gRPC channel" ); return std::result::Result::Ok(Self { endpoint_name: endpoint.name().to_owned(), provider: endpoint.provider().clone(), cluster: endpoint.cluster().clone(), channel, }); } /// Returns the safe logical endpoint name. #[must_use] pub fn endpoint_name(&self) -> &str { return self.endpoint_name.as_str(); } /// Returns the safe open provider descriptor. #[must_use] pub const fn provider(&self) -> &crate::YellowstoneGrpcProviderName { return &self.provider; } /// Returns the safe open cluster descriptor. #[must_use] pub const fn cluster(&self) -> &crate::YellowstoneGrpcClusterName { return &self.cluster; } } impl std::fmt::Debug for YellowstoneGrpcChannel { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneGrpcChannel") .field("endpoint_name", &self.endpoint_name) .field("provider", &self.provider) .field("cluster", &self.cluster) .finish(); } } #[cfg(test)] #[path = "../unit_tests/grpc_channel.rs"] mod tests;