v0.2.9-pre.003
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/error.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
/// Error code used when no logical endpoint can satisfy a request.
|
||||
pub const ERROR_CODE_ENDPOINT_SELECTION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("onchain_transport", "endpoint_selection_failed");
|
||||
/// Error code used when a Yellowstone gRPC channel cannot be prepared safely.
|
||||
pub const ERROR_CODE_GRPC_CHANNEL_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("onchain_transport", "grpc_channel_failed");
|
||||
/// Error code used when a Yellowstone gRPC endpoint returns a remote gRPC status.
|
||||
pub const ERROR_CODE_GRPC_STATUS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("onchain_transport", "grpc_status");
|
||||
/// Error code used when an HTTP connection cannot be established.
|
||||
pub const ERROR_CODE_HTTP_CONNECTION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("onchain_transport", "http_connection_failed");
|
||||
/// Error code used when an HTTP request fails after a connection exists.
|
||||
|
||||
@@ -1,80 +1,96 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/grpc_channel.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
/// Prepared Yellowstone gRPC channel owned by KSP Transport.
|
||||
/// Prepared or connected 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.
|
||||
/// The underlying Tonic channel, endpoint URL and request metadata remain private. Callers use the KSP-owned typed Yellowstone surfaces layered on this
|
||||
/// physical channel instead of receiving a raw Tonic escape hatch.
|
||||
#[derive(Clone)]
|
||||
pub struct YellowstoneGrpcChannel {
|
||||
endpoint_name: std::string::String,
|
||||
provider: crate::YellowstoneGrpcProviderName,
|
||||
cluster: crate::YellowstoneGrpcClusterName,
|
||||
_channel: tonic::transport::Channel,
|
||||
channel: tonic::transport::Channel,
|
||||
metadata: std::vec::Vec<crate::YellowstoneGrpcMetadataEntry>,
|
||||
session: crate::YellowstoneGrpcSessionSettings,
|
||||
}
|
||||
|
||||
impl YellowstoneGrpcChannel {
|
||||
/// Prepares one lazy Yellowstone gRPC channel from validated Transport-owned endpoint settings.
|
||||
/// Prepares one lazy HTTP/2 channel without establishing a network connection.
|
||||
///
|
||||
/// 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<Self> {
|
||||
if let std::result::Result::Err(error) = endpoint.validate() {
|
||||
/// HTTPS endpoints receive the KSP TLS configuration immediately, so invalid local TLS setup is rejected before a typed client is created. Tonic lazy
|
||||
/// channels require an active Tokio runtime even though no socket is opened yet.
|
||||
pub fn prepare(settings: &crate::YellowstoneGrpcEndpointSettings) -> ksp_core_lib::Result<Self> {
|
||||
if let std::result::Result::Err(error) = settings.validate() {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if !endpoint.enabled() {
|
||||
if !settings.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()),
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "disabled Yellowstone gRPC endpoint cannot prepare a channel")
|
||||
.with_context("endpoint_name", settings.name()),
|
||||
);
|
||||
}
|
||||
if tokio::runtime::Handle::try_current().is_err() {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
endpoint_name = endpoint.name(),
|
||||
provider = endpoint.provider().as_str(),
|
||||
cluster = endpoint.cluster().as_str(),
|
||||
"Yellowstone gRPC channel preparation requires an active Tokio runtime"
|
||||
);
|
||||
return std::result::Result::Err(grpc_channel_error(settings, "Yellowstone gRPC channel preparation requires an active Tokio runtime"));
|
||||
}
|
||||
let endpoint = match build_tonic_endpoint(settings) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let channel = endpoint.connect_lazy();
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
endpoint_name = settings.name(),
|
||||
provider = settings.provider().as_str(),
|
||||
cluster = settings.cluster().as_str(),
|
||||
tls = settings.url().uses_tls(),
|
||||
metadata_count = settings.metadata().len(),
|
||||
"prepared lazy Yellowstone gRPC channel"
|
||||
);
|
||||
return std::result::Result::Ok(Self::from_parts(settings, channel));
|
||||
}
|
||||
|
||||
/// Establishes one Yellowstone gRPC HTTP/2 channel with bounded connect timeout and configured TLS roots.
|
||||
pub async fn connect(settings: &crate::YellowstoneGrpcEndpointSettings) -> ksp_core_lib::Result<Self> {
|
||||
if let std::result::Result::Err(error) = settings.validate() {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if !settings.enabled() {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "Yellowstone gRPC channel requires an active Tokio runtime")
|
||||
.with_context("endpoint_name", endpoint.name()),
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "disabled Yellowstone gRPC endpoint cannot connect")
|
||||
.with_context("endpoint_name", settings.name()),
|
||||
);
|
||||
}
|
||||
let tonic_endpoint = match tonic::transport::Endpoint::from_shared(endpoint.url().as_str().to_owned()) {
|
||||
if tokio::runtime::Handle::try_current().is_err() {
|
||||
return std::result::Result::Err(grpc_channel_error(settings, "Yellowstone gRPC connection requires an active Tokio runtime"));
|
||||
}
|
||||
let endpoint = match build_tonic_endpoint(settings) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let channel = match endpoint.connect().await {
|
||||
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()),
|
||||
endpoint_name = settings.name(),
|
||||
provider = settings.provider().as_str(),
|
||||
cluster = settings.cluster().as_str(),
|
||||
tls = settings.url().uses_tls(),
|
||||
"Yellowstone gRPC channel connection failed"
|
||||
);
|
||||
return std::result::Result::Err(grpc_channel_error(settings, "Yellowstone gRPC channel connection failed"));
|
||||
},
|
||||
};
|
||||
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"
|
||||
endpoint_name = settings.name(),
|
||||
provider = settings.provider().as_str(),
|
||||
cluster = settings.cluster().as_str(),
|
||||
tls = settings.url().uses_tls(),
|
||||
"connected Yellowstone gRPC channel"
|
||||
);
|
||||
return std::result::Result::Ok(Self {
|
||||
endpoint_name: endpoint.name().to_owned(),
|
||||
provider: endpoint.provider().clone(),
|
||||
cluster: endpoint.cluster().clone(),
|
||||
_channel: channel,
|
||||
});
|
||||
return std::result::Result::Ok(Self::from_parts(settings, channel));
|
||||
}
|
||||
|
||||
/// Returns the safe logical endpoint name.
|
||||
@@ -83,17 +99,34 @@ impl YellowstoneGrpcChannel {
|
||||
return self.endpoint_name.as_str();
|
||||
}
|
||||
|
||||
/// Returns the safe open provider descriptor.
|
||||
/// Returns the open provider descriptor.
|
||||
#[must_use]
|
||||
pub const fn provider(&self) -> &crate::YellowstoneGrpcProviderName {
|
||||
return &self.provider;
|
||||
}
|
||||
|
||||
/// Returns the safe open cluster descriptor.
|
||||
/// Returns the open cluster descriptor.
|
||||
#[must_use]
|
||||
pub const fn cluster(&self) -> &crate::YellowstoneGrpcClusterName {
|
||||
return &self.cluster;
|
||||
}
|
||||
|
||||
/// Creates the standard Solana Yellowstone unary facade over this physical channel.
|
||||
#[must_use]
|
||||
pub fn standard_unary_client(&self) -> crate::SolanaYellowstoneGrpcUnaryClient {
|
||||
return crate::SolanaYellowstoneGrpcUnaryClient::new(self.channel.clone(), self.metadata.clone(), self.session.clone());
|
||||
}
|
||||
|
||||
fn from_parts(settings: &crate::YellowstoneGrpcEndpointSettings, channel: tonic::transport::Channel) -> Self {
|
||||
return Self {
|
||||
endpoint_name: settings.name().to_owned(),
|
||||
provider: settings.provider().clone(),
|
||||
cluster: settings.cluster().clone(),
|
||||
channel,
|
||||
metadata: settings.metadata().to_vec(),
|
||||
session: settings.session().clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneGrpcChannel {
|
||||
@@ -103,10 +136,39 @@ impl std::fmt::Debug for YellowstoneGrpcChannel {
|
||||
.field("endpoint_name", &self.endpoint_name)
|
||||
.field("provider", &self.provider)
|
||||
.field("cluster", &self.cluster)
|
||||
.field("metadata_count", &self.metadata.len())
|
||||
.field("channel", &"<private>")
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tonic_endpoint(settings: &crate::YellowstoneGrpcEndpointSettings) -> ksp_core_lib::Result<tonic::transport::Endpoint> {
|
||||
let endpoint = match tonic::transport::Endpoint::from_shared(settings.url().as_str().to_owned()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(grpc_channel_error(settings, "Yellowstone gRPC endpoint URI cannot be prepared by the HTTP/2 transport"));
|
||||
},
|
||||
};
|
||||
let endpoint = endpoint.connect_timeout(settings.session().connect_timeout()).buffer_size(settings.session().request_channel_capacity());
|
||||
if settings.url().uses_tls() {
|
||||
let tls = tonic::transport::ClientTlsConfig::new().with_webpki_roots().timeout(settings.session().connect_timeout());
|
||||
return match endpoint.tls_config(tls) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => {
|
||||
std::result::Result::Err(grpc_channel_error(settings, "Yellowstone gRPC TLS configuration failed before connection"))
|
||||
},
|
||||
};
|
||||
}
|
||||
return std::result::Result::Ok(endpoint);
|
||||
}
|
||||
|
||||
fn grpc_channel_error(settings: &crate::YellowstoneGrpcEndpointSettings, message: &str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, message)
|
||||
.with_context("endpoint_name", settings.name())
|
||||
.with_context("provider", settings.provider().as_str())
|
||||
.with_context("cluster", settings.cluster().as_str());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/grpc_channel.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/grpc_settings.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
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);
|
||||
@@ -16,6 +16,9 @@ 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_METADATA_ENTRY_COUNT: usize = 64;
|
||||
const MAX_GRPC_METADATA_KEY_LENGTH_BYTES: usize = 128;
|
||||
const MAX_GRPC_METADATA_VALUE_LENGTH_BYTES: usize = 8 * 1024;
|
||||
const MAX_GRPC_RECONNECT_RETRIES: u32 = 100;
|
||||
const MAX_GRPC_RUNTIME_DURATION: std::time::Duration = std::time::Duration::from_secs(300);
|
||||
|
||||
@@ -25,6 +28,7 @@ const MAX_GRPC_RUNTIME_DURATION: std::time::Duration = std::time::Duration::from
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneGrpcEndpointUrl {
|
||||
value: std::string::String,
|
||||
uses_tls: bool,
|
||||
}
|
||||
|
||||
impl YellowstoneGrpcEndpointUrl {
|
||||
@@ -70,7 +74,7 @@ impl YellowstoneGrpcEndpointUrl {
|
||||
);
|
||||
}
|
||||
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, scheme = parsed.scheme(), "validated Yellowstone gRPC endpoint URL syntax");
|
||||
return std::result::Result::Ok(Self { value });
|
||||
return std::result::Result::Ok(Self { value, uses_tls: parsed.scheme() == "https" });
|
||||
}
|
||||
|
||||
/// Returns the sensitive runtime URL text.
|
||||
@@ -80,6 +84,12 @@ impl YellowstoneGrpcEndpointUrl {
|
||||
pub fn as_str(&self) -> &str {
|
||||
return self.value.as_str();
|
||||
}
|
||||
|
||||
/// Returns whether this endpoint URL requires TLS.
|
||||
#[must_use]
|
||||
pub const fn uses_tls(&self) -> bool {
|
||||
return self.uses_tls;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneGrpcEndpointUrl {
|
||||
@@ -88,6 +98,107 @@ impl std::fmt::Debug for YellowstoneGrpcEndpointUrl {
|
||||
}
|
||||
}
|
||||
|
||||
/// One validated ASCII metadata entry attached to Yellowstone gRPC requests.
|
||||
///
|
||||
/// Metadata values are intentionally omitted from [`std::fmt::Debug`] for both public and secret entries. Secret entries are additionally marked sensitive on
|
||||
/// the Tonic metadata value before transmission so the HTTP/2 stack avoids indexing them where supported. Binary `*-bin` metadata is not part of the
|
||||
/// `0.2.9-pre.003` contract.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneGrpcMetadataEntry {
|
||||
key: std::string::String,
|
||||
value: std::string::String,
|
||||
secret: bool,
|
||||
}
|
||||
|
||||
impl YellowstoneGrpcMetadataEntry {
|
||||
/// Creates one non-secret ASCII metadata entry.
|
||||
pub fn public(key: impl std::convert::Into<std::string::String>, value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
||||
return Self::new(key.into(), value.into(), false);
|
||||
}
|
||||
|
||||
/// Creates one secret ASCII metadata entry with redacted diagnostics.
|
||||
pub fn secret(key: impl std::convert::Into<std::string::String>, value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
||||
return Self::new(key.into(), value.into(), true);
|
||||
}
|
||||
|
||||
/// Returns the validated metadata key.
|
||||
#[must_use]
|
||||
pub fn key(&self) -> &str {
|
||||
return self.key.as_str();
|
||||
}
|
||||
|
||||
/// Returns whether the value must be treated as secret by Transport.
|
||||
#[must_use]
|
||||
pub const fn is_secret(&self) -> bool {
|
||||
return self.secret;
|
||||
}
|
||||
|
||||
/// Appends this validated value to an internal Tonic metadata map while preserving its sensitivity marker.
|
||||
pub(crate) fn append_to(&self, metadata: &mut tonic::metadata::MetadataMap) -> ksp_core_lib::Result<()> {
|
||||
let key = match tonic::metadata::MetadataKey::<tonic::metadata::Ascii>::from_bytes(self.key.as_bytes()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC metadata key is invalid")
|
||||
.with_context("field", "grpc_endpoint.metadata.key"),
|
||||
);
|
||||
},
|
||||
};
|
||||
let mut value = match tonic::metadata::AsciiMetadataValue::try_from(self.value.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC metadata value is invalid")
|
||||
.with_context("field", "grpc_endpoint.metadata.value")
|
||||
.with_context("metadata_key", self.key.as_str()),
|
||||
);
|
||||
},
|
||||
};
|
||||
value.set_sensitive(self.secret);
|
||||
metadata.append(key, value);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn new(key: std::string::String, value: std::string::String, secret: bool) -> ksp_core_lib::Result<Self> {
|
||||
if key.is_empty()
|
||||
|| key.len() > MAX_GRPC_METADATA_KEY_LENGTH_BYTES
|
||||
|| key != key.to_ascii_lowercase()
|
||||
|| key.starts_with("grpc-")
|
||||
|| key.ends_with("-bin")
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC metadata key violates the KSP ASCII metadata contract")
|
||||
.with_context("field", "grpc_endpoint.metadata.key"),
|
||||
);
|
||||
}
|
||||
if tonic::metadata::MetadataKey::<tonic::metadata::Ascii>::from_bytes(key.as_bytes()).is_err() {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC metadata key is invalid")
|
||||
.with_context("field", "grpc_endpoint.metadata.key"),
|
||||
);
|
||||
}
|
||||
if value.len() > MAX_GRPC_METADATA_VALUE_LENGTH_BYTES || tonic::metadata::AsciiMetadataValue::try_from(value.as_str()).is_err() {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "Yellowstone gRPC metadata value violates the KSP ASCII metadata contract")
|
||||
.with_context("field", "grpc_endpoint.metadata.value")
|
||||
.with_context("metadata_key", key.as_str()),
|
||||
);
|
||||
}
|
||||
return std::result::Result::Ok(Self { key, value, secret });
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneGrpcMetadataEntry {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneGrpcMetadataEntry")
|
||||
.field("key", &self.key)
|
||||
.field("secret", &self.secret)
|
||||
.field("value", &"<redacted>")
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// Open provider descriptor used by Yellowstone gRPC endpoint settings.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct YellowstoneGrpcProviderName {
|
||||
@@ -330,6 +441,7 @@ pub struct YellowstoneGrpcEndpointSettings {
|
||||
cluster: crate::YellowstoneGrpcClusterName,
|
||||
url: crate::YellowstoneGrpcEndpointUrl,
|
||||
session: crate::YellowstoneGrpcSessionSettings,
|
||||
metadata: std::vec::Vec<crate::YellowstoneGrpcMetadataEntry>,
|
||||
}
|
||||
|
||||
impl YellowstoneGrpcEndpointSettings {
|
||||
@@ -343,7 +455,7 @@ impl YellowstoneGrpcEndpointSettings {
|
||||
url: crate::YellowstoneGrpcEndpointUrl,
|
||||
session: crate::YellowstoneGrpcSessionSettings,
|
||||
) -> Self {
|
||||
return Self { name: name.into(), enabled, provider, cluster, url, session };
|
||||
return Self { name: name.into(), enabled, provider, cluster, url, session, metadata: std::vec::Vec::new() };
|
||||
}
|
||||
|
||||
/// Returns the logical endpoint name.
|
||||
@@ -382,6 +494,24 @@ impl YellowstoneGrpcEndpointSettings {
|
||||
return &self.session;
|
||||
}
|
||||
|
||||
/// Returns metadata entries in declaration order without exposing their values.
|
||||
#[must_use]
|
||||
pub fn metadata(&self) -> &[crate::YellowstoneGrpcMetadataEntry] {
|
||||
return self.metadata.as_slice();
|
||||
}
|
||||
|
||||
/// Replaces request metadata after validating KSP bounds and ASCII metadata rules.
|
||||
pub fn with_metadata(mut self, metadata: std::vec::Vec<crate::YellowstoneGrpcMetadataEntry>) -> ksp_core_lib::Result<Self> {
|
||||
if metadata.len() > MAX_GRPC_METADATA_ENTRY_COUNT {
|
||||
return grpc_invalid_settings_value("Yellowstone gRPC metadata entry count exceeds the KSP bound", "grpc_endpoint.metadata");
|
||||
}
|
||||
self.metadata = metadata;
|
||||
if let std::result::Result::Err(error) = self.validate() {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(self);
|
||||
}
|
||||
|
||||
/// Validates this endpoint without performing network I/O.
|
||||
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
||||
return validate_grpc_endpoint(self, "grpc_endpoint");
|
||||
@@ -464,6 +594,9 @@ fn validate_grpc_endpoint(endpoint: &crate::YellowstoneGrpcEndpointSettings, fie
|
||||
if let std::result::Result::Err(error) = endpoint.session().validate() {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if endpoint.metadata().len() > MAX_GRPC_METADATA_ENTRY_COUNT {
|
||||
return grpc_invalid_settings("Yellowstone gRPC metadata entry count exceeds the KSP bound", "grpc_endpoint.metadata");
|
||||
}
|
||||
ksp_logging_lib::trace!(
|
||||
target: crate::TRACING_TARGET,
|
||||
endpoint_name = endpoint.name(),
|
||||
@@ -523,6 +656,11 @@ fn grpc_invalid_settings(message: &str, field: &str) -> ksp_core_lib::Result<()>
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, message).with_context("field", field));
|
||||
}
|
||||
|
||||
fn grpc_invalid_settings_value<T>(message: &str, field: &str) -> ksp_core_lib::Result<T> {
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 35
|
||||
// version: 36
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -32,8 +32,9 @@
|
||||
//! transaction handle and typed `transactionNotification` union into the same actor-owned registry, remote-ID remap, unsubscribe-race handling and
|
||||
//! per-subscription backpressure path.
|
||||
//! `0.2.9-pre.002` opens the Yellowstone gRPC N1 engine foundation with Transport-owned redacted settings, bounded reconnect/channel/message policies, the
|
||||
//! published Yellowstone protobuf dependency and a lazy Tonic HTTP/2 channel wrapper that exposes no raw Tonic or upstream protobuf types. TLS, metadata and
|
||||
//! live unary calls remain deliberately deferred to `pre.003`.
|
||||
//! published Yellowstone protobuf dependency and a lazy Tonic HTTP/2 channel wrapper that exposes no raw Tonic or upstream protobuf types.
|
||||
//! `0.2.9-pre.003` adds bounded TLS/WebPKI connection establishment, generic redacted ASCII request metadata and the seven standard Yellowstone unary RPCs
|
||||
//! through KSP-owned DTOs. Streaming `Subscribe` remains deliberately deferred to `pre.004`.
|
||||
|
||||
mod client;
|
||||
mod constants;
|
||||
@@ -41,6 +42,7 @@ mod error;
|
||||
mod executor;
|
||||
mod grpc_channel;
|
||||
mod grpc_settings;
|
||||
mod grpc_unary;
|
||||
mod json_rpc;
|
||||
mod pool;
|
||||
mod resilience;
|
||||
@@ -77,6 +79,8 @@ pub use self::client::HttpEndpointSnapshot;
|
||||
pub use self::error::ERROR_CODE_ENDPOINT_SELECTION_FAILED;
|
||||
/// Error code used when a Yellowstone gRPC channel cannot be prepared safely.
|
||||
pub use self::error::ERROR_CODE_GRPC_CHANNEL_FAILED;
|
||||
/// Error code used when a Yellowstone gRPC endpoint returns a remote status.
|
||||
pub use self::error::ERROR_CODE_GRPC_STATUS;
|
||||
/// Error code used when an HTTP connection cannot be established.
|
||||
pub use self::error::ERROR_CODE_HTTP_CONNECTION_FAILED;
|
||||
/// Error code used when an HTTP request fails after connection establishment.
|
||||
@@ -109,7 +113,7 @@ pub use self::error::ERROR_CODE_WS_CONNECTION_FAILED;
|
||||
pub use self::error::ERROR_CODE_WS_PROTOCOL_ERROR;
|
||||
/// Error code used when a WebSocket session is no longer available.
|
||||
pub use self::error::ERROR_CODE_WS_SESSION_CLOSED;
|
||||
/// Prepared lazy Yellowstone gRPC channel owned by KSP Transport.
|
||||
/// Yellowstone gRPC channel owned by KSP Transport.
|
||||
pub use self::grpc_channel::YellowstoneGrpcChannel;
|
||||
/// Open cluster or network descriptor used by Yellowstone gRPC endpoint settings.
|
||||
pub use self::grpc_settings::YellowstoneGrpcClusterName;
|
||||
@@ -117,6 +121,8 @@ pub use self::grpc_settings::YellowstoneGrpcClusterName;
|
||||
pub use self::grpc_settings::YellowstoneGrpcEndpointSettings;
|
||||
/// Runtime Yellowstone gRPC endpoint URL with redacted diagnostics.
|
||||
pub use self::grpc_settings::YellowstoneGrpcEndpointUrl;
|
||||
/// Validated public or secret ASCII metadata attached to Yellowstone gRPC requests.
|
||||
pub use self::grpc_settings::YellowstoneGrpcMetadataEntry;
|
||||
/// Open provider descriptor used by Yellowstone gRPC endpoint settings.
|
||||
pub use self::grpc_settings::YellowstoneGrpcProviderName;
|
||||
/// Bounded reconnect settings owned by the Yellowstone gRPC runtime.
|
||||
@@ -125,6 +131,22 @@ pub use self::grpc_settings::YellowstoneGrpcReconnectSettings;
|
||||
pub use self::grpc_settings::YellowstoneGrpcSessionSettings;
|
||||
/// Complete runtime settings consumed by the KSP Yellowstone gRPC transport engine.
|
||||
pub use self::grpc_settings::YellowstoneGrpcTransportSettings;
|
||||
/// Standard Solana Yellowstone unary facade over one KSP-owned physical gRPC channel.
|
||||
pub use self::grpc_unary::SolanaYellowstoneGrpcUnaryClient;
|
||||
/// Block height returned by the standard Yellowstone unary surface.
|
||||
pub use self::grpc_unary::YellowstoneBlockHeight;
|
||||
/// Result of a standard Yellowstone blockhash-validity check.
|
||||
pub use self::grpc_unary::YellowstoneBlockhashValidity;
|
||||
/// Latest blockhash returned by the standard Yellowstone unary surface.
|
||||
pub use self::grpc_unary::YellowstoneLatestBlockhash;
|
||||
/// Echo returned by the standard Yellowstone unary Ping RPC.
|
||||
pub use self::grpc_unary::YellowstonePong;
|
||||
/// Replay availability advertised by the standard Yellowstone unary surface.
|
||||
pub use self::grpc_unary::YellowstoneReplayInfo;
|
||||
/// Current slot returned by the standard Yellowstone unary surface.
|
||||
pub use self::grpc_unary::YellowstoneSlot;
|
||||
/// Bounded endpoint version returned by the standard Yellowstone unary surface.
|
||||
pub use self::grpc_unary::YellowstoneVersionInfo;
|
||||
/// JSON-RPC 2.0 error payload returned by a remote Solana endpoint.
|
||||
pub use self::json_rpc::JsonRpcErrorObject;
|
||||
/// Validated JSON-RPC 2.0 error response.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_common.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
/// Commitment level accepted by typed Solana HTTP and WebSocket adapters.
|
||||
/// Commitment level accepted by typed Solana HTTP, WebSocket and Yellowstone gRPC adapters.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum SolanaCommitment {
|
||||
/// Query the most recent processed bank.
|
||||
|
||||
Reference in New Issue
Block a user