529 lines
22 KiB
Rust
529 lines
22 KiB
Rust
// file: crates/ksp-onchain-transport-lib/src/grpc_settings.rs
|
|
// version: 1
|
|
|
|
const DEFAULT_GRPC_CLOSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
|
const DEFAULT_GRPC_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
|
const DEFAULT_GRPC_MAX_INBOUND_MESSAGE_SIZE_BYTES: usize = 64 * 1024 * 1024;
|
|
const DEFAULT_GRPC_MAX_OUTBOUND_MESSAGE_SIZE_BYTES: usize = 64 * 1024 * 1024;
|
|
const DEFAULT_GRPC_RECONNECT_INITIAL_BACKOFF: std::time::Duration = std::time::Duration::from_millis(250);
|
|
const DEFAULT_GRPC_RECONNECT_MAX_BACKOFF: std::time::Duration = std::time::Duration::from_secs(5);
|
|
const DEFAULT_GRPC_RECONNECT_MAX_RETRIES: u32 = 5;
|
|
const DEFAULT_GRPC_REQUEST_CHANNEL_CAPACITY: usize = 128;
|
|
const DEFAULT_GRPC_UNARY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
|
const DEFAULT_GRPC_UPDATE_CHANNEL_CAPACITY: usize = 256;
|
|
const MAX_GRPC_CHANNEL_CAPACITY: usize = 65_536;
|
|
const MAX_GRPC_DESCRIPTOR_LENGTH_BYTES: usize = 128;
|
|
const MAX_GRPC_ENDPOINT_COUNT: usize = 128;
|
|
const MAX_GRPC_ENDPOINT_URL_LENGTH_BYTES: usize = 8 * 1024;
|
|
const MAX_GRPC_MESSAGE_SIZE_BYTES: usize = 512 * 1024 * 1024;
|
|
const MAX_GRPC_RECONNECT_RETRIES: u32 = 100;
|
|
const MAX_GRPC_RUNTIME_DURATION: std::time::Duration = std::time::Duration::from_secs(300);
|
|
|
|
/// Runtime Yellowstone gRPC endpoint URL owned by Transport.
|
|
///
|
|
/// The actual URL can contain provider credentials. Its [`std::fmt::Debug`] implementation is intentionally redacted.
|
|
#[derive(Clone, Eq, PartialEq)]
|
|
pub struct YellowstoneGrpcEndpointUrl {
|
|
value: std::string::String,
|
|
}
|
|
|
|
impl YellowstoneGrpcEndpointUrl {
|
|
/// Parses and validates one Yellowstone gRPC endpoint URL.
|
|
pub fn parse(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating Yellowstone gRPC endpoint URL");
|
|
let value = value.into();
|
|
if value.len() > MAX_GRPC_ENDPOINT_URL_LENGTH_BYTES {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC endpoint URL exceeds the KSP length bound")
|
|
.with_context("field", "grpc_endpoints.url"),
|
|
);
|
|
}
|
|
let parsed = match reqwest::Url::parse(value.as_str()) {
|
|
std::result::Result::Ok(parsed) => parsed,
|
|
std::result::Result::Err(error) => {
|
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, field = "grpc_endpoints.url", "rejected invalid Yellowstone gRPC endpoint URL");
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC endpoint URL is invalid")
|
|
.with_context("field", "grpc_endpoints.url")
|
|
.with_source(error),
|
|
);
|
|
},
|
|
};
|
|
if parsed.scheme() != "http" && parsed.scheme() != "https" {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
field = "grpc_endpoints.url",
|
|
scheme = parsed.scheme(),
|
|
"rejected Yellowstone gRPC endpoint URL with unsupported scheme"
|
|
);
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC endpoint URL must use http or https")
|
|
.with_context("field", "grpc_endpoints.url")
|
|
.with_context("scheme", parsed.scheme()),
|
|
);
|
|
}
|
|
if parsed.host_str().is_none() {
|
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, field = "grpc_endpoints.url", "rejected Yellowstone gRPC endpoint URL without host");
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC endpoint URL must contain a host")
|
|
.with_context("field", "grpc_endpoints.url"),
|
|
);
|
|
}
|
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, scheme = parsed.scheme(), "validated Yellowstone gRPC endpoint URL syntax");
|
|
return std::result::Result::Ok(Self { value });
|
|
}
|
|
|
|
/// Returns the sensitive runtime URL text.
|
|
///
|
|
/// Callers must not write this value to logs, generic diagnostics or snapshots.
|
|
#[must_use]
|
|
pub fn as_str(&self) -> &str {
|
|
return self.value.as_str();
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for YellowstoneGrpcEndpointUrl {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
return formatter.write_str("YellowstoneGrpcEndpointUrl(<redacted>)");
|
|
}
|
|
}
|
|
|
|
/// Open provider descriptor used by Yellowstone gRPC endpoint settings.
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
pub struct YellowstoneGrpcProviderName {
|
|
value: std::string::String,
|
|
}
|
|
|
|
impl YellowstoneGrpcProviderName {
|
|
/// Creates an open provider descriptor. Validation is performed by [`YellowstoneGrpcTransportSettings::validate`].
|
|
#[must_use]
|
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> Self {
|
|
return Self { value: value.into() };
|
|
}
|
|
|
|
/// Returns the provider descriptor text.
|
|
#[must_use]
|
|
pub fn as_str(&self) -> &str {
|
|
return self.value.as_str();
|
|
}
|
|
}
|
|
|
|
/// Open cluster or network descriptor used by Yellowstone gRPC endpoint settings.
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
pub struct YellowstoneGrpcClusterName {
|
|
value: std::string::String,
|
|
}
|
|
|
|
impl YellowstoneGrpcClusterName {
|
|
/// Creates an open cluster descriptor. Validation is performed by [`YellowstoneGrpcTransportSettings::validate`].
|
|
#[must_use]
|
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> Self {
|
|
return Self { value: value.into() };
|
|
}
|
|
|
|
/// Returns the cluster descriptor text.
|
|
#[must_use]
|
|
pub fn as_str(&self) -> &str {
|
|
return self.value.as_str();
|
|
}
|
|
}
|
|
|
|
/// Bounded reconnect settings owned by the Yellowstone gRPC runtime.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct YellowstoneGrpcReconnectSettings {
|
|
max_retries: u32,
|
|
initial_backoff: std::time::Duration,
|
|
max_backoff: std::time::Duration,
|
|
}
|
|
|
|
impl YellowstoneGrpcReconnectSettings {
|
|
/// Creates bounded reconnect settings.
|
|
#[must_use]
|
|
pub const fn new(max_retries: u32, initial_backoff: std::time::Duration, max_backoff: std::time::Duration) -> Self {
|
|
return Self { max_retries, initial_backoff, max_backoff };
|
|
}
|
|
|
|
/// Returns the number of reconnect attempts allowed after one live channel or stream is lost.
|
|
#[must_use]
|
|
pub const fn max_retries(&self) -> u32 {
|
|
return self.max_retries;
|
|
}
|
|
|
|
/// Returns the initial reconnect backoff.
|
|
#[must_use]
|
|
pub const fn initial_backoff(&self) -> std::time::Duration {
|
|
return self.initial_backoff;
|
|
}
|
|
|
|
/// Returns the maximum reconnect backoff.
|
|
#[must_use]
|
|
pub const fn max_backoff(&self) -> std::time::Duration {
|
|
return self.max_backoff;
|
|
}
|
|
}
|
|
|
|
impl std::default::Default for YellowstoneGrpcReconnectSettings {
|
|
fn default() -> Self {
|
|
return Self::new(DEFAULT_GRPC_RECONNECT_MAX_RETRIES, DEFAULT_GRPC_RECONNECT_INITIAL_BACKOFF, DEFAULT_GRPC_RECONNECT_MAX_BACKOFF);
|
|
}
|
|
}
|
|
|
|
/// Runtime limits and lifecycle settings for one Yellowstone gRPC physical channel/session path.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct YellowstoneGrpcSessionSettings {
|
|
connect_timeout: std::time::Duration,
|
|
unary_timeout: std::time::Duration,
|
|
close_timeout: std::time::Duration,
|
|
reconnect: crate::YellowstoneGrpcReconnectSettings,
|
|
request_channel_capacity: usize,
|
|
update_channel_capacity: usize,
|
|
max_inbound_message_size_bytes: usize,
|
|
max_outbound_message_size_bytes: usize,
|
|
}
|
|
|
|
impl YellowstoneGrpcSessionSettings {
|
|
/// Creates complete runtime settings for one physical Yellowstone gRPC channel/session path.
|
|
#[must_use]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub const fn new(
|
|
connect_timeout: std::time::Duration,
|
|
unary_timeout: std::time::Duration,
|
|
close_timeout: std::time::Duration,
|
|
reconnect: crate::YellowstoneGrpcReconnectSettings,
|
|
request_channel_capacity: usize,
|
|
update_channel_capacity: usize,
|
|
max_inbound_message_size_bytes: usize,
|
|
max_outbound_message_size_bytes: usize,
|
|
) -> Self {
|
|
return Self {
|
|
connect_timeout,
|
|
unary_timeout,
|
|
close_timeout,
|
|
reconnect,
|
|
request_channel_capacity,
|
|
update_channel_capacity,
|
|
max_inbound_message_size_bytes,
|
|
max_outbound_message_size_bytes,
|
|
};
|
|
}
|
|
|
|
/// Returns the connection establishment timeout.
|
|
#[must_use]
|
|
pub const fn connect_timeout(&self) -> std::time::Duration {
|
|
return self.connect_timeout;
|
|
}
|
|
|
|
/// Returns the default unary request timeout.
|
|
#[must_use]
|
|
pub const fn unary_timeout(&self) -> std::time::Duration {
|
|
return self.unary_timeout;
|
|
}
|
|
|
|
/// Returns the bounded graceful-close timeout.
|
|
#[must_use]
|
|
pub const fn close_timeout(&self) -> std::time::Duration {
|
|
return self.close_timeout;
|
|
}
|
|
|
|
/// Returns the reconnect policy.
|
|
#[must_use]
|
|
pub const fn reconnect(&self) -> &crate::YellowstoneGrpcReconnectSettings {
|
|
return &self.reconnect;
|
|
}
|
|
|
|
/// Returns the bounded outgoing Subscribe request channel capacity.
|
|
#[must_use]
|
|
pub const fn request_channel_capacity(&self) -> usize {
|
|
return self.request_channel_capacity;
|
|
}
|
|
|
|
/// Returns the bounded incoming update delivery channel capacity.
|
|
#[must_use]
|
|
pub const fn update_channel_capacity(&self) -> usize {
|
|
return self.update_channel_capacity;
|
|
}
|
|
|
|
/// Returns the maximum inbound protobuf message size accepted by KSP.
|
|
#[must_use]
|
|
pub const fn max_inbound_message_size_bytes(&self) -> usize {
|
|
return self.max_inbound_message_size_bytes;
|
|
}
|
|
|
|
/// Returns the maximum outbound protobuf message size accepted by KSP.
|
|
#[must_use]
|
|
pub const fn max_outbound_message_size_bytes(&self) -> usize {
|
|
return self.max_outbound_message_size_bytes;
|
|
}
|
|
|
|
/// Validates KSP-owned runtime bounds without reading Config or environment state.
|
|
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
|
if let std::result::Result::Err(error) = validate_grpc_duration(self.connect_timeout, "grpc_session.connect_timeout") {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) = validate_grpc_duration(self.unary_timeout, "grpc_session.unary_timeout") {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) = validate_grpc_duration(self.close_timeout, "grpc_session.close_timeout") {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if self.reconnect.max_retries() > MAX_GRPC_RECONNECT_RETRIES {
|
|
return grpc_invalid_settings("Yellowstone gRPC reconnect retry count exceeds the KSP runtime bound", "grpc_session.reconnect.max_retries");
|
|
}
|
|
if let std::result::Result::Err(error) = validate_grpc_duration(self.reconnect.initial_backoff(), "grpc_session.reconnect.initial_backoff") {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) = validate_grpc_duration(self.reconnect.max_backoff(), "grpc_session.reconnect.max_backoff") {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if self.reconnect.initial_backoff() > self.reconnect.max_backoff() {
|
|
return grpc_invalid_settings("Yellowstone gRPC reconnect initial backoff must not exceed maximum backoff", "grpc_session.reconnect");
|
|
}
|
|
if let std::result::Result::Err(error) = validate_grpc_capacity(self.request_channel_capacity, "grpc_session.request_channel_capacity") {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) = validate_grpc_capacity(self.update_channel_capacity, "grpc_session.update_channel_capacity") {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) = validate_grpc_message_size(self.max_inbound_message_size_bytes, "grpc_session.max_inbound_message_size_bytes")
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) =
|
|
validate_grpc_message_size(self.max_outbound_message_size_bytes, "grpc_session.max_outbound_message_size_bytes")
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
request_channel_capacity = self.request_channel_capacity,
|
|
update_channel_capacity = self.update_channel_capacity,
|
|
max_inbound_message_size_bytes = self.max_inbound_message_size_bytes,
|
|
max_outbound_message_size_bytes = self.max_outbound_message_size_bytes,
|
|
reconnect_max_retries = self.reconnect.max_retries(),
|
|
"validated Yellowstone gRPC session settings"
|
|
);
|
|
return std::result::Result::Ok(());
|
|
}
|
|
}
|
|
|
|
impl std::default::Default for YellowstoneGrpcSessionSettings {
|
|
fn default() -> Self {
|
|
return Self::new(
|
|
DEFAULT_GRPC_CONNECT_TIMEOUT,
|
|
DEFAULT_GRPC_UNARY_TIMEOUT,
|
|
DEFAULT_GRPC_CLOSE_TIMEOUT,
|
|
crate::YellowstoneGrpcReconnectSettings::default(),
|
|
DEFAULT_GRPC_REQUEST_CHANNEL_CAPACITY,
|
|
DEFAULT_GRPC_UPDATE_CHANNEL_CAPACITY,
|
|
DEFAULT_GRPC_MAX_INBOUND_MESSAGE_SIZE_BYTES,
|
|
DEFAULT_GRPC_MAX_OUTBOUND_MESSAGE_SIZE_BYTES,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Runtime settings for one named Yellowstone gRPC endpoint.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct YellowstoneGrpcEndpointSettings {
|
|
name: std::string::String,
|
|
enabled: bool,
|
|
provider: crate::YellowstoneGrpcProviderName,
|
|
cluster: crate::YellowstoneGrpcClusterName,
|
|
url: crate::YellowstoneGrpcEndpointUrl,
|
|
session: crate::YellowstoneGrpcSessionSettings,
|
|
}
|
|
|
|
impl YellowstoneGrpcEndpointSettings {
|
|
/// Creates explicit settings for one logical Yellowstone gRPC endpoint.
|
|
#[must_use]
|
|
pub fn new(
|
|
name: impl std::convert::Into<std::string::String>,
|
|
enabled: bool,
|
|
provider: crate::YellowstoneGrpcProviderName,
|
|
cluster: crate::YellowstoneGrpcClusterName,
|
|
url: crate::YellowstoneGrpcEndpointUrl,
|
|
session: crate::YellowstoneGrpcSessionSettings,
|
|
) -> Self {
|
|
return Self { name: name.into(), enabled, provider, cluster, url, session };
|
|
}
|
|
|
|
/// Returns the logical endpoint name.
|
|
#[must_use]
|
|
pub fn name(&self) -> &str {
|
|
return self.name.as_str();
|
|
}
|
|
|
|
/// Returns whether this endpoint can be used to prepare a physical channel.
|
|
#[must_use]
|
|
pub const fn enabled(&self) -> bool {
|
|
return self.enabled;
|
|
}
|
|
|
|
/// Returns the open provider descriptor.
|
|
#[must_use]
|
|
pub const fn provider(&self) -> &crate::YellowstoneGrpcProviderName {
|
|
return &self.provider;
|
|
}
|
|
|
|
/// Returns the open cluster descriptor.
|
|
#[must_use]
|
|
pub const fn cluster(&self) -> &crate::YellowstoneGrpcClusterName {
|
|
return &self.cluster;
|
|
}
|
|
|
|
/// Returns the sensitive Yellowstone gRPC endpoint URL wrapper.
|
|
#[must_use]
|
|
pub const fn url(&self) -> &crate::YellowstoneGrpcEndpointUrl {
|
|
return &self.url;
|
|
}
|
|
|
|
/// Returns the effective runtime settings for the channel/session path.
|
|
#[must_use]
|
|
pub const fn session(&self) -> &crate::YellowstoneGrpcSessionSettings {
|
|
return &self.session;
|
|
}
|
|
|
|
/// Validates this endpoint without performing network I/O.
|
|
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
|
return validate_grpc_endpoint(self, "grpc_endpoint");
|
|
}
|
|
}
|
|
|
|
/// Complete runtime settings consumed by the KSP Yellowstone gRPC transport engine.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct YellowstoneGrpcTransportSettings {
|
|
endpoints: std::vec::Vec<crate::YellowstoneGrpcEndpointSettings>,
|
|
}
|
|
|
|
impl YellowstoneGrpcTransportSettings {
|
|
/// Creates complete Yellowstone gRPC runtime settings.
|
|
#[must_use]
|
|
pub fn new(endpoints: std::vec::Vec<crate::YellowstoneGrpcEndpointSettings>) -> Self {
|
|
return Self { endpoints };
|
|
}
|
|
|
|
/// Returns configured Yellowstone gRPC endpoints in declaration order.
|
|
#[must_use]
|
|
pub fn endpoints(&self) -> &[crate::YellowstoneGrpcEndpointSettings] {
|
|
return self.endpoints.as_slice();
|
|
}
|
|
|
|
/// Validates structural runtime invariants without reading Config or environment state.
|
|
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, endpoint_count = self.endpoints.len(), "validating Yellowstone gRPC transport settings");
|
|
if self.endpoints.is_empty() {
|
|
return grpc_invalid_settings("at least one Yellowstone gRPC endpoint must be configured", "grpc_endpoints");
|
|
}
|
|
if self.endpoints.len() > MAX_GRPC_ENDPOINT_COUNT {
|
|
return grpc_invalid_settings("Yellowstone gRPC endpoint count exceeds the KSP bound", "grpc_endpoints");
|
|
}
|
|
let mut enabled_endpoint_count = 0_usize;
|
|
for (endpoint_index, endpoint) in self.endpoints.iter().enumerate() {
|
|
let field_prefix = format!("grpc_endpoints[{endpoint_index}]");
|
|
if let std::result::Result::Err(error) = validate_grpc_endpoint(endpoint, field_prefix.as_str()) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if endpoint.enabled() {
|
|
enabled_endpoint_count += 1;
|
|
}
|
|
for previous in &self.endpoints[..endpoint_index] {
|
|
if previous.name() == endpoint.name() {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC endpoint names must be unique")
|
|
.with_context("field", format!("grpc_endpoints[{endpoint_index}].name"))
|
|
.with_context("endpoint_name", endpoint.name()),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if enabled_endpoint_count == 0 {
|
|
return grpc_invalid_settings("at least one Yellowstone gRPC endpoint must be enabled", "grpc_endpoints.enabled");
|
|
}
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
endpoint_count = self.endpoints.len(),
|
|
enabled_endpoint_count,
|
|
"validated Yellowstone gRPC transport settings"
|
|
);
|
|
return std::result::Result::Ok(());
|
|
}
|
|
}
|
|
|
|
fn validate_grpc_endpoint(endpoint: &crate::YellowstoneGrpcEndpointSettings, field_prefix: &str) -> ksp_core_lib::Result<()> {
|
|
let name_field = format!("{field_prefix}.name");
|
|
if let std::result::Result::Err(error) = validate_grpc_descriptor(endpoint.name(), name_field.as_str()) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let provider_field = format!("{field_prefix}.provider");
|
|
if let std::result::Result::Err(error) = validate_grpc_descriptor(endpoint.provider().as_str(), provider_field.as_str()) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let cluster_field = format!("{field_prefix}.cluster");
|
|
if let std::result::Result::Err(error) = validate_grpc_descriptor(endpoint.cluster().as_str(), cluster_field.as_str()) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) = endpoint.session().validate() {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
ksp_logging_lib::trace!(
|
|
target: crate::TRACING_TARGET,
|
|
endpoint_name = endpoint.name(),
|
|
provider = endpoint.provider().as_str(),
|
|
cluster = endpoint.cluster().as_str(),
|
|
enabled = endpoint.enabled(),
|
|
"validated Yellowstone gRPC endpoint settings"
|
|
);
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn validate_grpc_descriptor(value: &str, field: &str) -> ksp_core_lib::Result<()> {
|
|
if value.trim().is_empty() {
|
|
return grpc_invalid_settings("Yellowstone gRPC transport descriptor must not be empty", field);
|
|
}
|
|
if value.trim() != value {
|
|
return grpc_invalid_settings("Yellowstone gRPC transport descriptor must not contain leading or trailing whitespace", field);
|
|
}
|
|
if value.len() > MAX_GRPC_DESCRIPTOR_LENGTH_BYTES {
|
|
return grpc_invalid_settings("Yellowstone gRPC transport descriptor exceeds the KSP length bound", field);
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn validate_grpc_duration(value: std::time::Duration, field: &str) -> ksp_core_lib::Result<()> {
|
|
if value.is_zero() {
|
|
return grpc_invalid_settings("Yellowstone gRPC runtime duration must be greater than zero", field);
|
|
}
|
|
if value > MAX_GRPC_RUNTIME_DURATION {
|
|
return grpc_invalid_settings("Yellowstone gRPC runtime duration exceeds the KSP bound", field);
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn validate_grpc_capacity(value: usize, field: &str) -> ksp_core_lib::Result<()> {
|
|
if value == 0 {
|
|
return grpc_invalid_settings("Yellowstone gRPC channel capacity must be greater than zero", field);
|
|
}
|
|
if value > MAX_GRPC_CHANNEL_CAPACITY {
|
|
return grpc_invalid_settings("Yellowstone gRPC channel capacity exceeds the KSP bound", field);
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn validate_grpc_message_size(value: usize, field: &str) -> ksp_core_lib::Result<()> {
|
|
if value == 0 {
|
|
return grpc_invalid_settings("Yellowstone gRPC message size must be greater than zero", field);
|
|
}
|
|
if value > MAX_GRPC_MESSAGE_SIZE_BYTES {
|
|
return grpc_invalid_settings("Yellowstone gRPC message size exceeds the KSP bound", field);
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn grpc_invalid_settings(message: &str, field: &str) -> ksp_core_lib::Result<()> {
|
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, field = field, reason = message, "rejected Yellowstone gRPC transport settings");
|
|
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, message).with_context("field", field));
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../unit_tests/grpc_settings.rs"]
|
|
mod tests;
|