v0.2.9-pre.003
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 240
|
# version: 241
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.9-pre.2.fix.2"
|
version = "0.2.9-pre.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
@@ -23,6 +23,7 @@ ed25519-dalek = { version = "^3.0", default-features = false }
|
|||||||
fs2 = { version = "^0.4" }
|
fs2 = { version = "^0.4" }
|
||||||
futures-util = { version = "^0.3", default-features = false }
|
futures-util = { version = "^0.3", default-features = false }
|
||||||
getrandom = { version = "^0.4", default-features = false }
|
getrandom = { version = "^0.4", default-features = false }
|
||||||
|
http = { version = "^1.5", default-features = false }
|
||||||
jsonschema = { version = "^0.51", default-features = false }
|
jsonschema = { version = "^0.51", default-features = false }
|
||||||
reqwest = { version = "^0.13", default-features = false }
|
reqwest = { version = "^0.13", default-features = false }
|
||||||
serde = { version = "^1.0" }
|
serde = { version = "^1.0" }
|
||||||
@@ -40,6 +41,7 @@ tracing-appender = { version = "^0.2", default-features = false }
|
|||||||
tokio = { version = "^1.53", default-features = false }
|
tokio = { version = "^1.53", default-features = false }
|
||||||
tokio-tungstenite = { version = "^0.30", default-features = false }
|
tokio-tungstenite = { version = "^0.30", default-features = false }
|
||||||
tonic = { version = "^0.14", default-features = false }
|
tonic = { version = "^0.14", default-features = false }
|
||||||
|
tonic-prost = { version = "^0.14", default-features = false }
|
||||||
ts-rs = { version = "^12.0" }
|
ts-rs = { version = "^12.0" }
|
||||||
yellowstone-grpc-proto = { version = "^12.6", default-features = false }
|
yellowstone-grpc-proto = { version = "^12.6", default-features = false }
|
||||||
zeroize = { version = "^1.9" }
|
zeroize = { version = "^1.9" }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-core-lib/tests/workspace_dependencies.rs
|
// file: crates/ksp-core-lib/tests/workspace_dependencies.rs
|
||||||
// version: 6
|
// version: 7
|
||||||
|
|
||||||
//! Workspace-level dependency policy canaries owned by the foundational KSP test surface.
|
//! Workspace-level dependency policy canaries owned by the foundational KSP test surface.
|
||||||
|
|
||||||
@@ -62,13 +62,17 @@ fn transport_manifest_preserves_ksp_dependency_firewall() {
|
|||||||
assert!(manifest.contains("ksp-core-lib"));
|
assert!(manifest.contains("ksp-core-lib"));
|
||||||
assert!(manifest.contains("ksp-logging-lib"));
|
assert!(manifest.contains("ksp-logging-lib"));
|
||||||
assert!(manifest.contains("futures-util = { workspace = true, features = [\"sink\", \"std\"] }"));
|
assert!(manifest.contains("futures-util = { workspace = true, features = [\"sink\", \"std\"] }"));
|
||||||
|
assert!(manifest.contains("http.workspace = true"));
|
||||||
assert!(manifest.contains("reqwest = { workspace = true, features = [\"rustls\"] }"));
|
assert!(manifest.contains("reqwest = { workspace = true, features = [\"rustls\"] }"));
|
||||||
assert!(manifest.contains("tokio = { workspace = true, features = [\"macros\", \"net\", \"rt\", \"sync\", \"time\"] }"));
|
assert!(manifest.contains("tokio = { workspace = true, features = [\"macros\", \"net\", \"rt\", \"sync\", \"time\"] }"));
|
||||||
assert!(manifest.contains("tokio-tungstenite = { workspace = true, features = [\"connect\", \"rustls-tls-webpki-roots\"] }"));
|
assert!(manifest.contains("tokio-tungstenite = { workspace = true, features = [\"connect\", \"rustls-tls-webpki-roots\"] }"));
|
||||||
assert!(manifest.contains("tonic = { workspace = true, features = [\"channel\"] }"));
|
assert!(manifest.contains("tonic = { workspace = true, features = [\"channel\", \"tls-aws-lc\", \"tls-webpki-roots\"] }"));
|
||||||
|
assert!(manifest.contains("tonic-prost.workspace = true"));
|
||||||
assert!(manifest.contains("yellowstone-grpc-proto.workspace = true"));
|
assert!(manifest.contains("yellowstone-grpc-proto.workspace = true"));
|
||||||
assert!(manifest.contains("[dev-dependencies]"));
|
assert!(manifest.contains("[dev-dependencies]"));
|
||||||
assert!(manifest.contains("tokio = { workspace = true, features = [\"io-util\", \"net\", \"rt\", \"test-util\"] }"));
|
assert!(manifest.contains("tokio = { workspace = true, features = [\"io-util\", \"net\", \"rt\", \"test-util\"] }"));
|
||||||
|
assert!(manifest.contains("tonic = { workspace = true, features = [\"codegen\", \"server\"] }"));
|
||||||
|
assert!(manifest.contains("yellowstone-grpc-proto = { workspace = true, features = [\"tonic\"] }"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -90,6 +94,7 @@ fn transport_manifest_runtime_and_dev_dependency_names_are_exact() {
|
|||||||
dependency_names,
|
dependency_names,
|
||||||
std::vec![
|
std::vec![
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"http",
|
||||||
"ksp-core-lib",
|
"ksp-core-lib",
|
||||||
"ksp-logging-lib",
|
"ksp-logging-lib",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -98,7 +103,8 @@ fn transport_manifest_runtime_and_dev_dependency_names_are_exact() {
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tonic",
|
"tonic",
|
||||||
"yellowstone-grpc-proto"
|
"tonic-prost",
|
||||||
|
"yellowstone-grpc-proto",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
let dev_dependencies_tail = manifest.split("[dev-dependencies]").nth(1);
|
let dev_dependencies_tail = manifest.split("[dev-dependencies]").nth(1);
|
||||||
@@ -111,7 +117,7 @@ fn transport_manifest_runtime_and_dev_dependency_names_are_exact() {
|
|||||||
std::option::Option::Some(value) => value,
|
std::option::Option::Some(value) => value,
|
||||||
std::option::Option::None => return,
|
std::option::Option::None => return,
|
||||||
};
|
};
|
||||||
assert_eq!(manifest_dependency_names(dev_dependencies), std::vec!["tokio"]);
|
assert_eq!(manifest_dependency_names(dev_dependencies), std::vec!["tokio", "tonic", "yellowstone-grpc-proto"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
|
fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: crates/ksp-onchain-transport-lib/Cargo.toml
|
# file: crates/ksp-onchain-transport-lib/Cargo.toml
|
||||||
# version: 7
|
# version: 8
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "ksp-onchain-transport-lib"
|
name = "ksp-onchain-transport-lib"
|
||||||
@@ -11,16 +11,20 @@ repository.workspace = true
|
|||||||
ksp-core-lib = { path = "../ksp-core-lib" }
|
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||||
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
||||||
futures-util = { workspace = true, features = ["sink", "std"] }
|
futures-util = { workspace = true, features = ["sink", "std"] }
|
||||||
|
http.workspace = true
|
||||||
reqwest = { workspace = true, features = ["rustls"] }
|
reqwest = { workspace = true, features = ["rustls"] }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tokio = { workspace = true, features = ["macros", "net", "rt", "sync", "time"] }
|
tokio = { workspace = true, features = ["macros", "net", "rt", "sync", "time"] }
|
||||||
tokio-tungstenite = { workspace = true, features = ["connect", "rustls-tls-webpki-roots"] }
|
tokio-tungstenite = { workspace = true, features = ["connect", "rustls-tls-webpki-roots"] }
|
||||||
tonic = { workspace = true, features = ["channel"] }
|
tonic = { workspace = true, features = ["channel", "tls-aws-lc", "tls-webpki-roots"] }
|
||||||
|
tonic-prost.workspace = true
|
||||||
yellowstone-grpc-proto.workspace = true
|
yellowstone-grpc-proto.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["io-util", "net", "rt", "test-util"] }
|
tokio = { workspace = true, features = ["io-util", "net", "rt", "test-util"] }
|
||||||
|
tonic = { workspace = true, features = ["codegen", "server"] }
|
||||||
|
yellowstone-grpc-proto = { workspace = true, features = ["tonic"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/error.rs
|
// file: crates/ksp-onchain-transport-lib/src/error.rs
|
||||||
// version: 5
|
// version: 6
|
||||||
|
|
||||||
/// Error code used when no logical endpoint can satisfy a request.
|
/// 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");
|
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.
|
/// 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");
|
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.
|
/// 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");
|
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.
|
/// 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
|
// 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
|
/// The underlying Tonic channel, endpoint URL and request metadata remain private. Callers use the KSP-owned typed Yellowstone surfaces layered on this
|
||||||
/// connection/error semantics are added by `pre.003`. The underlying Tonic channel and upstream Yellowstone protobuf types are never exposed publicly.
|
/// physical channel instead of receiving a raw Tonic escape hatch.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct YellowstoneGrpcChannel {
|
pub struct YellowstoneGrpcChannel {
|
||||||
endpoint_name: std::string::String,
|
endpoint_name: std::string::String,
|
||||||
provider: crate::YellowstoneGrpcProviderName,
|
provider: crate::YellowstoneGrpcProviderName,
|
||||||
cluster: crate::YellowstoneGrpcClusterName,
|
cluster: crate::YellowstoneGrpcClusterName,
|
||||||
_channel: tonic::transport::Channel,
|
channel: tonic::transport::Channel,
|
||||||
|
metadata: std::vec::Vec<crate::YellowstoneGrpcMetadataEntry>,
|
||||||
|
session: crate::YellowstoneGrpcSessionSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YellowstoneGrpcChannel {
|
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`
|
/// HTTPS endpoints receive the KSP TLS configuration immediately, so invalid local TLS setup is rejected before a typed client is created. Tonic lazy
|
||||||
/// before any live call is allowed.
|
/// channels require an active Tokio runtime even though no socket is opened yet.
|
||||||
pub fn prepare(endpoint: &crate::YellowstoneGrpcEndpointSettings) -> ksp_core_lib::Result<Self> {
|
pub fn prepare(settings: &crate::YellowstoneGrpcEndpointSettings) -> ksp_core_lib::Result<Self> {
|
||||||
if let std::result::Result::Err(error) = endpoint.validate() {
|
if let std::result::Result::Err(error) = settings.validate() {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
if !endpoint.enabled() {
|
if !settings.enabled() {
|
||||||
return std::result::Result::Err(
|
return std::result::Result::Err(
|
||||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "disabled Yellowstone gRPC endpoint cannot prepare a channel")
|
ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "disabled Yellowstone gRPC endpoint cannot prepare a channel")
|
||||||
.with_context("field", "grpc_endpoint.enabled")
|
.with_context("endpoint_name", settings.name()),
|
||||||
.with_context("endpoint_name", endpoint.name()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if tokio::runtime::Handle::try_current().is_err() {
|
if tokio::runtime::Handle::try_current().is_err() {
|
||||||
ksp_logging_lib::warn!(
|
return std::result::Result::Err(grpc_channel_error(settings, "Yellowstone gRPC channel preparation requires an active Tokio runtime"));
|
||||||
target: crate::TRACING_TARGET,
|
}
|
||||||
endpoint_name = endpoint.name(),
|
let endpoint = match build_tonic_endpoint(settings) {
|
||||||
provider = endpoint.provider().as_str(),
|
std::result::Result::Ok(value) => value,
|
||||||
cluster = endpoint.cluster().as_str(),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
"Yellowstone gRPC channel preparation requires an active Tokio runtime"
|
};
|
||||||
);
|
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(
|
return std::result::Result::Err(
|
||||||
ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "Yellowstone gRPC channel requires an active Tokio runtime")
|
ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "disabled Yellowstone gRPC endpoint cannot connect")
|
||||||
.with_context("endpoint_name", endpoint.name()),
|
.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::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => {
|
std::result::Result::Err(_) => {
|
||||||
ksp_logging_lib::warn!(
|
ksp_logging_lib::warn!(
|
||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
endpoint_name = endpoint.name(),
|
endpoint_name = settings.name(),
|
||||||
provider = endpoint.provider().as_str(),
|
provider = settings.provider().as_str(),
|
||||||
cluster = endpoint.cluster().as_str(),
|
cluster = settings.cluster().as_str(),
|
||||||
"failed to prepare Yellowstone gRPC channel URI"
|
tls = settings.url().uses_tls(),
|
||||||
);
|
"Yellowstone gRPC channel connection failed"
|
||||||
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()),
|
|
||||||
);
|
);
|
||||||
|
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!(
|
ksp_logging_lib::debug!(
|
||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
endpoint_name = endpoint.name(),
|
endpoint_name = settings.name(),
|
||||||
provider = endpoint.provider().as_str(),
|
provider = settings.provider().as_str(),
|
||||||
cluster = endpoint.cluster().as_str(),
|
cluster = settings.cluster().as_str(),
|
||||||
"prepared lazy Yellowstone gRPC channel"
|
tls = settings.url().uses_tls(),
|
||||||
|
"connected Yellowstone gRPC channel"
|
||||||
);
|
);
|
||||||
return std::result::Result::Ok(Self {
|
return std::result::Result::Ok(Self::from_parts(settings, channel));
|
||||||
endpoint_name: endpoint.name().to_owned(),
|
|
||||||
provider: endpoint.provider().clone(),
|
|
||||||
cluster: endpoint.cluster().clone(),
|
|
||||||
_channel: channel,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the safe logical endpoint name.
|
/// Returns the safe logical endpoint name.
|
||||||
@@ -83,17 +99,34 @@ impl YellowstoneGrpcChannel {
|
|||||||
return self.endpoint_name.as_str();
|
return self.endpoint_name.as_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the safe open provider descriptor.
|
/// Returns the open provider descriptor.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn provider(&self) -> &crate::YellowstoneGrpcProviderName {
|
pub const fn provider(&self) -> &crate::YellowstoneGrpcProviderName {
|
||||||
return &self.provider;
|
return &self.provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the safe open cluster descriptor.
|
/// Returns the open cluster descriptor.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn cluster(&self) -> &crate::YellowstoneGrpcClusterName {
|
pub const fn cluster(&self) -> &crate::YellowstoneGrpcClusterName {
|
||||||
return &self.cluster;
|
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 {
|
impl std::fmt::Debug for YellowstoneGrpcChannel {
|
||||||
@@ -103,10 +136,39 @@ impl std::fmt::Debug for YellowstoneGrpcChannel {
|
|||||||
.field("endpoint_name", &self.endpoint_name)
|
.field("endpoint_name", &self.endpoint_name)
|
||||||
.field("provider", &self.provider)
|
.field("provider", &self.provider)
|
||||||
.field("cluster", &self.cluster)
|
.field("cluster", &self.cluster)
|
||||||
|
.field("metadata_count", &self.metadata.len())
|
||||||
|
.field("channel", &"<private>")
|
||||||
.finish();
|
.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)]
|
#[cfg(test)]
|
||||||
#[path = "../unit_tests/grpc_channel.rs"]
|
#[path = "../unit_tests/grpc_channel.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/grpc_settings.rs
|
// 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_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_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_COUNT: usize = 128;
|
||||||
const MAX_GRPC_ENDPOINT_URL_LENGTH_BYTES: usize = 8 * 1024;
|
const MAX_GRPC_ENDPOINT_URL_LENGTH_BYTES: usize = 8 * 1024;
|
||||||
const MAX_GRPC_MESSAGE_SIZE_BYTES: usize = 512 * 1024 * 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_RECONNECT_RETRIES: u32 = 100;
|
||||||
const MAX_GRPC_RUNTIME_DURATION: std::time::Duration = std::time::Duration::from_secs(300);
|
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)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct YellowstoneGrpcEndpointUrl {
|
pub struct YellowstoneGrpcEndpointUrl {
|
||||||
value: std::string::String,
|
value: std::string::String,
|
||||||
|
uses_tls: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YellowstoneGrpcEndpointUrl {
|
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");
|
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.
|
/// Returns the sensitive runtime URL text.
|
||||||
@@ -80,6 +84,12 @@ impl YellowstoneGrpcEndpointUrl {
|
|||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
return self.value.as_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 {
|
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.
|
/// Open provider descriptor used by Yellowstone gRPC endpoint settings.
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct YellowstoneGrpcProviderName {
|
pub struct YellowstoneGrpcProviderName {
|
||||||
@@ -330,6 +441,7 @@ pub struct YellowstoneGrpcEndpointSettings {
|
|||||||
cluster: crate::YellowstoneGrpcClusterName,
|
cluster: crate::YellowstoneGrpcClusterName,
|
||||||
url: crate::YellowstoneGrpcEndpointUrl,
|
url: crate::YellowstoneGrpcEndpointUrl,
|
||||||
session: crate::YellowstoneGrpcSessionSettings,
|
session: crate::YellowstoneGrpcSessionSettings,
|
||||||
|
metadata: std::vec::Vec<crate::YellowstoneGrpcMetadataEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YellowstoneGrpcEndpointSettings {
|
impl YellowstoneGrpcEndpointSettings {
|
||||||
@@ -343,7 +455,7 @@ impl YellowstoneGrpcEndpointSettings {
|
|||||||
url: crate::YellowstoneGrpcEndpointUrl,
|
url: crate::YellowstoneGrpcEndpointUrl,
|
||||||
session: crate::YellowstoneGrpcSessionSettings,
|
session: crate::YellowstoneGrpcSessionSettings,
|
||||||
) -> Self {
|
) -> 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.
|
/// Returns the logical endpoint name.
|
||||||
@@ -382,6 +494,24 @@ impl YellowstoneGrpcEndpointSettings {
|
|||||||
return &self.session;
|
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.
|
/// Validates this endpoint without performing network I/O.
|
||||||
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
||||||
return validate_grpc_endpoint(self, "grpc_endpoint");
|
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() {
|
if let std::result::Result::Err(error) = endpoint.session().validate() {
|
||||||
return std::result::Result::Err(error);
|
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!(
|
ksp_logging_lib::trace!(
|
||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
endpoint_name = endpoint.name(),
|
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));
|
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)]
|
#[cfg(test)]
|
||||||
#[path = "../unit_tests/grpc_settings.rs"]
|
#[path = "../unit_tests/grpc_settings.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||||
// version: 35
|
// version: 36
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![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
|
//! transaction handle and typed `transactionNotification` union into the same actor-owned registry, remote-ID remap, unsubscribe-race handling and
|
||||||
//! per-subscription backpressure path.
|
//! 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
|
//! `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
|
//! published Yellowstone protobuf dependency and a lazy Tonic HTTP/2 channel wrapper that exposes no raw Tonic or upstream protobuf types.
|
||||||
//! live unary calls remain deliberately deferred to `pre.003`.
|
//! `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 client;
|
||||||
mod constants;
|
mod constants;
|
||||||
@@ -41,6 +42,7 @@ mod error;
|
|||||||
mod executor;
|
mod executor;
|
||||||
mod grpc_channel;
|
mod grpc_channel;
|
||||||
mod grpc_settings;
|
mod grpc_settings;
|
||||||
|
mod grpc_unary;
|
||||||
mod json_rpc;
|
mod json_rpc;
|
||||||
mod pool;
|
mod pool;
|
||||||
mod resilience;
|
mod resilience;
|
||||||
@@ -77,6 +79,8 @@ pub use self::client::HttpEndpointSnapshot;
|
|||||||
pub use self::error::ERROR_CODE_ENDPOINT_SELECTION_FAILED;
|
pub use self::error::ERROR_CODE_ENDPOINT_SELECTION_FAILED;
|
||||||
/// Error code used when a Yellowstone gRPC channel cannot be prepared safely.
|
/// Error code used when a Yellowstone gRPC channel cannot be prepared safely.
|
||||||
pub use self::error::ERROR_CODE_GRPC_CHANNEL_FAILED;
|
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.
|
/// Error code used when an HTTP connection cannot be established.
|
||||||
pub use self::error::ERROR_CODE_HTTP_CONNECTION_FAILED;
|
pub use self::error::ERROR_CODE_HTTP_CONNECTION_FAILED;
|
||||||
/// Error code used when an HTTP request fails after connection establishment.
|
/// 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;
|
pub use self::error::ERROR_CODE_WS_PROTOCOL_ERROR;
|
||||||
/// Error code used when a WebSocket session is no longer available.
|
/// Error code used when a WebSocket session is no longer available.
|
||||||
pub use self::error::ERROR_CODE_WS_SESSION_CLOSED;
|
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;
|
pub use self::grpc_channel::YellowstoneGrpcChannel;
|
||||||
/// Open cluster or network descriptor used by Yellowstone gRPC endpoint settings.
|
/// Open cluster or network descriptor used by Yellowstone gRPC endpoint settings.
|
||||||
pub use self::grpc_settings::YellowstoneGrpcClusterName;
|
pub use self::grpc_settings::YellowstoneGrpcClusterName;
|
||||||
@@ -117,6 +121,8 @@ pub use self::grpc_settings::YellowstoneGrpcClusterName;
|
|||||||
pub use self::grpc_settings::YellowstoneGrpcEndpointSettings;
|
pub use self::grpc_settings::YellowstoneGrpcEndpointSettings;
|
||||||
/// Runtime Yellowstone gRPC endpoint URL with redacted diagnostics.
|
/// Runtime Yellowstone gRPC endpoint URL with redacted diagnostics.
|
||||||
pub use self::grpc_settings::YellowstoneGrpcEndpointUrl;
|
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.
|
/// Open provider descriptor used by Yellowstone gRPC endpoint settings.
|
||||||
pub use self::grpc_settings::YellowstoneGrpcProviderName;
|
pub use self::grpc_settings::YellowstoneGrpcProviderName;
|
||||||
/// Bounded reconnect settings owned by the Yellowstone gRPC runtime.
|
/// 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;
|
pub use self::grpc_settings::YellowstoneGrpcSessionSettings;
|
||||||
/// Complete runtime settings consumed by the KSP Yellowstone gRPC transport engine.
|
/// Complete runtime settings consumed by the KSP Yellowstone gRPC transport engine.
|
||||||
pub use self::grpc_settings::YellowstoneGrpcTransportSettings;
|
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.
|
/// JSON-RPC 2.0 error payload returned by a remote Solana endpoint.
|
||||||
pub use self::json_rpc::JsonRpcErrorObject;
|
pub use self::json_rpc::JsonRpcErrorObject;
|
||||||
/// Validated JSON-RPC 2.0 error response.
|
/// Validated JSON-RPC 2.0 error response.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/rpc_common.rs
|
// 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)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum SolanaCommitment {
|
pub enum SolanaCommitment {
|
||||||
/// Query the most recent processed bank.
|
/// Query the most recent processed bank.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
// version: 40
|
// version: 41
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||||
|
|
||||||
@@ -789,3 +789,39 @@ async fn public_v0_2_9_pre_002_yellowstone_engine_settings_and_lazy_channel_are_
|
|||||||
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_CHANNEL_FAILED.domain(), "onchain_transport");
|
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_CHANNEL_FAILED.domain(), "onchain_transport");
|
||||||
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_CHANNEL_FAILED.code(), "grpc_channel_failed");
|
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_CHANNEL_FAILED.code(), "grpc_channel_failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_v0_2_9_pre_003_yellowstone_metadata_and_seven_unary_contracts_are_available_from_crate_root() {
|
||||||
|
let public = ksp_onchain_transport_lib::YellowstoneGrpcMetadataEntry::public("x-ksp-public", "fixture");
|
||||||
|
assert!(public.is_ok());
|
||||||
|
let secret = ksp_onchain_transport_lib::YellowstoneGrpcMetadataEntry::secret("x-ksp-token", "secret");
|
||||||
|
assert!(secret.is_ok());
|
||||||
|
let replay = ksp_onchain_transport_lib::YellowstoneReplayInfo::new(std::option::Option::Some(1));
|
||||||
|
assert_eq!(replay.first_available(), std::option::Option::Some(1));
|
||||||
|
let pong = ksp_onchain_transport_lib::YellowstonePong::new(2);
|
||||||
|
assert_eq!(pong.count(), 2);
|
||||||
|
let latest = ksp_onchain_transport_lib::YellowstoneLatestBlockhash::new(3, "hash".to_owned(), 4);
|
||||||
|
assert_eq!(latest.slot(), 3);
|
||||||
|
assert_eq!(latest.blockhash(), "hash");
|
||||||
|
assert_eq!(latest.last_valid_block_height(), 4);
|
||||||
|
assert_eq!(ksp_onchain_transport_lib::YellowstoneBlockHeight::new(5).block_height(), 5);
|
||||||
|
assert_eq!(ksp_onchain_transport_lib::YellowstoneSlot::new(6).slot(), 6);
|
||||||
|
let validity = ksp_onchain_transport_lib::YellowstoneBlockhashValidity::new(7, true);
|
||||||
|
assert_eq!(validity.slot(), 7);
|
||||||
|
assert!(validity.valid());
|
||||||
|
assert_eq!(ksp_onchain_transport_lib::YellowstoneVersionInfo::new("v".to_owned()).version(), "v");
|
||||||
|
let _replay_info = ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient::subscribe_replay_info;
|
||||||
|
let _ping = ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient::ping;
|
||||||
|
let _latest_blockhash = ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient::get_latest_blockhash;
|
||||||
|
let _block_height = ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient::get_block_height;
|
||||||
|
let _slot = ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient::get_slot;
|
||||||
|
async fn call_is_blockhash_valid(
|
||||||
|
client: &ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient,
|
||||||
|
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::YellowstoneBlockhashValidity> {
|
||||||
|
return client.is_blockhash_valid("fixture-blockhash", std::option::Option::None).await;
|
||||||
|
}
|
||||||
|
let _is_blockhash_valid = call_is_blockhash_valid;
|
||||||
|
let _version = ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient::get_version;
|
||||||
|
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_STATUS.domain(), "onchain_transport");
|
||||||
|
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_STATUS.code(), "grpc_status");
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
// version: 33
|
// version: 34
|
||||||
|
|
||||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||||
|
|
||||||
@@ -1008,7 +1008,7 @@ fn release_v0_2_9_pre_002_materializes_minimal_yellowstone_engine_without_provid
|
|||||||
let crate_root = include_str!("../src/lib.rs");
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
assert!(root_manifest.contains("tonic = { version = \"^0.14\", default-features = false }"));
|
assert!(root_manifest.contains("tonic = { version = \"^0.14\", default-features = false }"));
|
||||||
assert!(root_manifest.contains("yellowstone-grpc-proto = { version = \"^12.6\", default-features = false }"));
|
assert!(root_manifest.contains("yellowstone-grpc-proto = { version = \"^12.6\", default-features = false }"));
|
||||||
assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"channel\"] }"));
|
assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"channel\""));
|
||||||
assert!(transport_manifest.contains("yellowstone-grpc-proto.workspace = true"));
|
assert!(transport_manifest.contains("yellowstone-grpc-proto.workspace = true"));
|
||||||
assert!(!transport_manifest.contains("yellowstone-grpc-client"));
|
assert!(!transport_manifest.contains("yellowstone-grpc-client"));
|
||||||
assert!(!transport_manifest.contains("ksp-config-lib"));
|
assert!(!transport_manifest.contains("ksp-config-lib"));
|
||||||
@@ -1019,10 +1019,50 @@ fn release_v0_2_9_pre_002_materializes_minimal_yellowstone_engine_without_provid
|
|||||||
assert!(!settings_source.contains("KSP_SECRET_"));
|
assert!(!settings_source.contains("KSP_SECRET_"));
|
||||||
assert!(channel_source.contains("tonic::transport::Endpoint::from_shared"));
|
assert!(channel_source.contains("tonic::transport::Endpoint::from_shared"));
|
||||||
assert!(channel_source.contains("connect_lazy"));
|
assert!(channel_source.contains("connect_lazy"));
|
||||||
assert!(!channel_source.contains("tls_config"));
|
|
||||||
assert!(!channel_source.contains("MetadataMap"));
|
|
||||||
assert!(!channel_source.contains("WsSession"));
|
assert!(!channel_source.contains("WsSession"));
|
||||||
assert!(!crate_root.contains("pub use tonic"));
|
assert!(!crate_root.contains("pub use tonic"));
|
||||||
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
||||||
let _published_wire = std::any::type_name::<yellowstone_grpc_proto::geyser::SubscribeRequest>();
|
let _published_wire = std::any::type_name::<yellowstone_grpc_proto::geyser::SubscribeRequest>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_2_9_pre_003_adds_tls_metadata_and_exactly_seven_standard_unary_methods_without_subscribe() {
|
||||||
|
let manifest_directory = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let workspace = manifest_directory.parent().and_then(std::path::Path::parent).expect("Transport test must resolve workspace root");
|
||||||
|
let root_manifest = std::fs::read_to_string(workspace.join("Cargo.toml")).expect("workspace manifest must be readable");
|
||||||
|
let transport_manifest = std::fs::read_to_string(manifest_directory.join("Cargo.toml")).expect("Transport manifest must be readable");
|
||||||
|
let settings_source = include_str!("../src/grpc_settings.rs");
|
||||||
|
let channel_source = include_str!("../src/grpc_channel.rs");
|
||||||
|
let unary_source = include_str!("../src/grpc_unary.rs");
|
||||||
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
|
assert!(root_manifest.contains("http = { version = \"^1.5\", default-features = false }"));
|
||||||
|
assert!(root_manifest.contains("tonic-prost = { version = \"^0.14\", default-features = false }"));
|
||||||
|
assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"channel\", \"tls-aws-lc\", \"tls-webpki-roots\"] }"));
|
||||||
|
assert!(transport_manifest.contains("tonic = { workspace = true, features = [\"codegen\", \"server\"] }"));
|
||||||
|
assert!(transport_manifest.contains("tonic-prost.workspace = true"));
|
||||||
|
assert!(transport_manifest.contains("yellowstone-grpc-proto.workspace = true"));
|
||||||
|
assert!(transport_manifest.contains("yellowstone-grpc-proto = { workspace = true, features = [\"tonic\"] }"));
|
||||||
|
assert!(!transport_manifest.contains("yellowstone-grpc-client"));
|
||||||
|
assert!(settings_source.contains("YellowstoneGrpcMetadataEntry"));
|
||||||
|
assert!(settings_source.contains("set_sensitive"));
|
||||||
|
assert!(channel_source.contains("ClientTlsConfig"));
|
||||||
|
assert!(channel_source.contains("with_webpki_roots"));
|
||||||
|
assert!(channel_source.contains("pub async fn connect"));
|
||||||
|
for path in [
|
||||||
|
"/geyser.Geyser/SubscribeReplayInfo",
|
||||||
|
"/geyser.Geyser/Ping",
|
||||||
|
"/geyser.Geyser/GetLatestBlockhash",
|
||||||
|
"/geyser.Geyser/GetBlockHeight",
|
||||||
|
"/geyser.Geyser/GetSlot",
|
||||||
|
"/geyser.Geyser/IsBlockhashValid",
|
||||||
|
"/geyser.Geyser/GetVersion",
|
||||||
|
] {
|
||||||
|
assert!(unary_source.contains(path), "missing standard Yellowstone unary path: {path}");
|
||||||
|
}
|
||||||
|
assert!(!unary_source.contains("const PATH_SUBSCRIBE: "));
|
||||||
|
assert!(!unary_source.contains("SubscribeDeshred"));
|
||||||
|
assert!(!unary_source.contains("PublicNode"));
|
||||||
|
assert!(!crate_root.contains("pub use tonic"));
|
||||||
|
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
||||||
|
let _client = std::any::type_name::<ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient>();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_channel.rs
|
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_channel.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
fn endpoint(enabled: bool, value: &str) -> crate::YellowstoneGrpcEndpointSettings {
|
fn endpoint(enabled: bool, value: &str) -> crate::YellowstoneGrpcEndpointSettings {
|
||||||
let parsed = crate::YellowstoneGrpcEndpointUrl::parse(value).expect("fixture Yellowstone gRPC URL must parse");
|
let parsed = crate::YellowstoneGrpcEndpointUrl::parse(value).expect("fixture Yellowstone gRPC URL must parse");
|
||||||
@@ -28,7 +28,7 @@ async fn grpc_channel_prepare_is_lazy_safe_and_keeps_tonic_private() {
|
|||||||
let rendered = format!("{channel:?}");
|
let rendered = format!("{channel:?}");
|
||||||
assert!(!rendered.contains("GRPC-SECRET-CANARY"));
|
assert!(!rendered.contains("GRPC-SECRET-CANARY"));
|
||||||
assert!(!rendered.contains("127.0.0.1"));
|
assert!(!rendered.contains("127.0.0.1"));
|
||||||
let _channel_type = std::any::type_name_of_val(&channel._channel);
|
let _client = channel.standard_unary_client();
|
||||||
let _wire_type = std::any::type_name::<yellowstone_grpc_proto::geyser::SubscribeRequest>();
|
let _wire_type = std::any::type_name::<yellowstone_grpc_proto::geyser::SubscribeRequest>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,3 +48,13 @@ fn grpc_channel_prepare_rejects_disabled_endpoint_before_network_io() {
|
|||||||
let result = crate::YellowstoneGrpcChannel::prepare(&endpoint);
|
let result = crate::YellowstoneGrpcChannel::prepare(&endpoint);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn grpc_channel_prepare_configures_https_without_exposing_url() {
|
||||||
|
let endpoint = endpoint(true, "https://example.invalid:443/GRPC-SECRET-CANARY");
|
||||||
|
let result = crate::YellowstoneGrpcChannel::prepare(&endpoint);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let rendered = format!("{result:?}");
|
||||||
|
assert!(!rendered.contains("GRPC-SECRET-CANARY"));
|
||||||
|
assert!(!rendered.contains("example.invalid"));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_settings.rs
|
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_settings.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
fn endpoint(name: &str, enabled: bool, url: &str, session: crate::YellowstoneGrpcSessionSettings) -> crate::YellowstoneGrpcEndpointSettings {
|
fn endpoint(name: &str, enabled: bool, url: &str, session: crate::YellowstoneGrpcSessionSettings) -> crate::YellowstoneGrpcEndpointSettings {
|
||||||
let parsed = crate::YellowstoneGrpcEndpointUrl::parse(url).expect("fixture Yellowstone gRPC URL must parse");
|
let parsed = crate::YellowstoneGrpcEndpointUrl::parse(url).expect("fixture Yellowstone gRPC URL must parse");
|
||||||
@@ -149,3 +149,50 @@ fn grpc_transport_settings_require_one_enabled_endpoint() {
|
|||||||
assert!(crate::YellowstoneGrpcTransportSettings::new(std::vec![disabled]).validate().is_err());
|
assert!(crate::YellowstoneGrpcTransportSettings::new(std::vec![disabled]).validate().is_err());
|
||||||
assert!(crate::YellowstoneGrpcTransportSettings::new(std::vec![]).validate().is_err());
|
assert!(crate::YellowstoneGrpcTransportSettings::new(std::vec![]).validate().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn grpc_metadata_validates_ascii_bounds_sensitivity_and_redacted_debug() {
|
||||||
|
let public = crate::YellowstoneGrpcMetadataEntry::public("x-ksp-public", "VISIBLE-CANARY").expect("public metadata must validate");
|
||||||
|
let secret = crate::YellowstoneGrpcMetadataEntry::secret("x-ksp-token", "GRPC-SECRET-CANARY").expect("secret metadata must validate");
|
||||||
|
assert_eq!(public.key(), "x-ksp-public");
|
||||||
|
assert!(!public.is_secret());
|
||||||
|
assert!(secret.is_secret());
|
||||||
|
let rendered = format!("{public:?} {secret:?}");
|
||||||
|
assert!(!rendered.contains("VISIBLE-CANARY"));
|
||||||
|
assert!(!rendered.contains("GRPC-SECRET-CANARY"));
|
||||||
|
let mut metadata = tonic::metadata::MetadataMap::new();
|
||||||
|
assert!(secret.append_to(&mut metadata).is_ok());
|
||||||
|
let appended = metadata.get("x-ksp-token").expect("secret metadata must be appended");
|
||||||
|
assert!(appended.is_sensitive());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn grpc_metadata_rejects_reserved_binary_uppercase_malformed_and_oversized_values() {
|
||||||
|
assert!(crate::YellowstoneGrpcMetadataEntry::public("grpc-timeout", "1").is_err());
|
||||||
|
assert!(crate::YellowstoneGrpcMetadataEntry::public("x-ksp-bin", "1").is_err());
|
||||||
|
assert!(crate::YellowstoneGrpcMetadataEntry::public("X-KSP-UPPER", "1").is_err());
|
||||||
|
assert!(crate::YellowstoneGrpcMetadataEntry::public("x-ksp-bad", "line\nfeed").is_err());
|
||||||
|
assert!(crate::YellowstoneGrpcMetadataEntry::secret("x-ksp-large", "S".repeat(super::MAX_GRPC_METADATA_VALUE_LENGTH_BYTES + 1)).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn grpc_endpoint_metadata_count_is_bounded_and_url_tracks_tls_scheme() {
|
||||||
|
let http = crate::YellowstoneGrpcEndpointUrl::parse("http://127.0.0.1:10000").expect("HTTP URL must parse");
|
||||||
|
let https = crate::YellowstoneGrpcEndpointUrl::parse("https://example.invalid:443").expect("HTTPS URL must parse");
|
||||||
|
assert!(!http.uses_tls());
|
||||||
|
assert!(https.uses_tls());
|
||||||
|
let metadata = (0..=super::MAX_GRPC_METADATA_ENTRY_COUNT)
|
||||||
|
.map(|index| {
|
||||||
|
return crate::YellowstoneGrpcMetadataEntry::public(format!("x-ksp-{index}"), "value").expect("generated metadata must be valid");
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let endpoint = crate::YellowstoneGrpcEndpointSettings::new(
|
||||||
|
"metadata-bound",
|
||||||
|
true,
|
||||||
|
crate::YellowstoneGrpcProviderName::new("fixture"),
|
||||||
|
crate::YellowstoneGrpcClusterName::new("devnet"),
|
||||||
|
http,
|
||||||
|
crate::YellowstoneGrpcSessionSettings::default(),
|
||||||
|
);
|
||||||
|
assert!(endpoint.with_metadata(metadata).is_err());
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md -->
|
<!-- file: docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md -->
|
||||||
<!-- version: 7 -->
|
<!-- version: 8 -->
|
||||||
|
|
||||||
# Plan `0.2.9` — moteur Yellowstone gRPC + standard Solana + PublicNode
|
# Plan `0.2.9` — moteur Yellowstone gRPC + standard Solana + PublicNode
|
||||||
|
|
||||||
> **Statut : `0.2.9-pre.002-fix.002` — second correctif du gate opérateur : `fix.001` est confirmé vert sur fmt/audit/check/Clippy et les 346 unit tests ; seul le canari d’intégration `public_api` appelait encore le channel lazy depuis un test synchrone. `fix.002` place ce canari sous runtime Tokio sans modifier N1 en production. Les tests Transport/workspace doivent être réexécutés avant commit. `0.2.9` reste bornée à un moteur client Yellowstone partagé, une façade Solana Yellowstone standard et une première intégration concrète PublicNode. Seuls OrbitFlare puis Helius LaserStream gRPC sont actuellement planifiés comme releases provider suivantes ; les autres providers restent en TODO/IDEAS sans numéro réservé. Chaque prerelease vise 15–20 minutes de travail effectif et la release complète doit rester clôturable dans une seule session de chat.**
|
> **Statut : `0.2.9-pre.003` — `pre.002-fix.002` est fermée sur gate opérateur intégralement vert (fmt/audit/check/Clippy, Transport 346 unit + 42 public API + 35 completeness + 4 doctests, dependency canary et workspace complet). `pre.003` ajoute uniquement TLS client, metadata générique redacted, connexion HTTP/2 réelle, fixture Geyser locale et les sept unary standard ; `Subscribe`, PublicNode et Config V3 restent hors tranche. `0.2.9` reste bornée à un moteur client Yellowstone partagé, une façade Solana Yellowstone standard et une première intégration concrète PublicNode. Seuls OrbitFlare puis Helius LaserStream gRPC sont actuellement planifiés comme releases provider suivantes ; les autres providers restent en TODO/IDEAS sans numéro réservé. Chaque prerelease vise 15–20 minutes de travail effectif et la release complète doit rester clôturable dans une seule session de chat.**
|
||||||
|
|
||||||
## 1. Objet, base et état d'ouverture
|
## 1. Objet, base et état d'ouverture
|
||||||
|
|
||||||
@@ -298,19 +298,22 @@ https://docs.rs/crate/yellowstone-grpc-proto/latest
|
|||||||
|
|
||||||
KSP suit la **Rust stable courante de l'opérateur** et ne documente pas de numéro Rust upstream comme objectif de projet. Le seul gate utile est opérationnel : les dépendances finalement retenues doivent compiler avec la stable courante utilisée par le workspace.
|
KSP suit la **Rust stable courante de l'opérateur** et ne documente pas de numéro Rust upstream comme objectif de projet. Le seul gate utile est opérationnel : les dépendances finalement retenues doivent compiler avec la stable courante utilisée par le workspace.
|
||||||
|
|
||||||
Résultat de matérialisation `pre.002` :
|
Résultat après matérialisation `pre.003` :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
yellowstone-grpc-proto ^12.6 workspace dependency, default-features = false, aucune feature KSP activée
|
yellowstone-grpc-proto ^12.6 runtime : default-features = false, aucune feature `tonic`
|
||||||
tonic ^0.14 workspace dependency, default-features = false ; Transport active seulement `channel`
|
dev/test : feature `tonic` uniquement pour GeyserServer de fixture
|
||||||
|
tonic ^0.14 runtime : channel + tls-aws-lc + tls-webpki-roots
|
||||||
|
dev/test : codegen + server ajoutés explicitement
|
||||||
|
http ^1.5 direct KSP pour PathAndQuery unary sans constructeur panic
|
||||||
|
tonic-prost ^0.14 direct KSP pour ProstCodec ; sa dépendance Tonic désactive les defaults
|
||||||
yellowstone-grpc-client absent
|
yellowstone-grpc-client absent
|
||||||
prost/prost-types aucune dépendance KSP directe
|
prost/prost-types aucune dépendance KSP directe
|
||||||
tonic-prost aucune dépendance KSP directe à ce stade
|
|
||||||
proto crate publiée/générée ; aucun .proto copié dans KSP
|
proto crate publiée/générée ; aucun .proto copié dans KSP
|
||||||
protoc aucun outil système KSP ajouté ; la crate proto publiée utilise son build vendored
|
protoc aucun outil système KSP ajouté ; la crate proto publiée utilise son build vendored
|
||||||
```
|
```
|
||||||
|
|
||||||
Le feature `tonic` optionnel de `yellowstone-grpc-proto` n'est **pas** activé en `pre.002`. KSP n'a besoin à ce stade que des messages Protobuf générés bruts ; le channel physique est construit directement avec Tonic. Cette décision évite de tirer prématurément la surface client/serveur Tonic générée et laisse `pre.003` choisir explicitement les features supplémentaires réellement nécessaires aux unary/TLS/metadata.
|
La séparation reste intentionnelle : les messages Protobuf officiels sont consommés sans activer le client généré Yellowstone dans le runtime. La feature `tonic` du proto est réservée au graphe dev/test pour matérialiser le serveur Geyser de fixture. KSP utilise le dispatcher `tonic::client::Grpc` + `tonic-prost::ProstCodec` derrière sa façade N2 et conserve ainsi ses propres timeouts, redactions et erreurs.
|
||||||
|
|
||||||
Un minimum Rust déclaré par une dépendance n'est enregistré que s'il devient un **blocage réel** lors de la compilation ; il n'est pas suivi comme métrique de release.
|
Un minimum Rust déclaré par une dépendance n'est enregistré que s'il devient un **blocage réel** lors de la compilation ; il n'est pas suivi comme métrique de release.
|
||||||
|
|
||||||
@@ -535,19 +538,21 @@ Avantages :
|
|||||||
|
|
||||||
**Décision : stratégie cible retenue.**
|
**Décision : stratégie cible retenue.**
|
||||||
|
|
||||||
Matérialisation réalisée en `pre.002` :
|
Matérialisation après `pre.003` :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
yellowstone-grpc-proto ^12.6 root : default-features = false ; Transport : .workspace = true sans feature
|
yellowstone-grpc-proto ^12.6 runtime sans feature ; dev/test feature `tonic` pour le serveur de fixture uniquement
|
||||||
tonic ^0.14 root : default-features = false ; Transport : features = ["channel"]
|
tonic ^0.14 runtime features = ["channel", "tls-aws-lc", "tls-webpki-roots"]
|
||||||
|
dev/test ajoute ["codegen", "server"]
|
||||||
|
tonic-prost ^0.14 runtime, ProstCodec bas niveau
|
||||||
|
http ^1.5 runtime, PathAndQuery borné/interne
|
||||||
yellowstone-grpc-client absent
|
yellowstone-grpc-client absent
|
||||||
prost/prost-types pas de dépendance KSP directe
|
prost/prost-types pas de dépendance KSP directe
|
||||||
tonic-prost pas de dépendance KSP directe en pre.002
|
tokio-stream pas ajouté directement
|
||||||
tokio-stream pas ajouté
|
futures-util dépendance existante réutilisée par la fixture Stream
|
||||||
futures-util dépendance existante inchangée
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Le choix est volontairement plus étroit que le forecast initial : `pre.002` matérialise les messages wire publiés et le channel HTTP/2 lazy, mais **pas encore le client RPC généré, TLS, metadata ou compression**. Les éventuelles features/dependencies supplémentaires doivent être justifiées par `pre.003` et vérifiées par `cargo tree`; elles ne sont pas activées par anticipation.
|
`pre.003` n'active toujours ni client Yellowstone upstream ni compression. Les sept unary passent par une façade KSP au-dessus du channel N1 ; la surface `GeyserServer` générée n'existe que dans les tests pour prouver le wire exact localement. Le graphe Cargo doit être réinspecté après application parce que les features TLS et les deux dépendances directes `http`/`tonic-prost` changent le graphe runtime.
|
||||||
|
|
||||||
Le premier gate opérateur de `pre.002` confirme l'alignement de versions utile : Tonic 0.14.6 réutilise `http` 1.5, `hyper` 1.11, `hyper-util` 0.1, `tower` 0.5 et `bytes` 1.12 déjà présents ; `yellowstone-grpc-proto` unifie `solana-pubkey` en 4.3.0. Les occurrences Prost 0.14.4 visibles dans `cargo tree --duplicates` correspondent aux contextes runtime/build de la même version, notamment `prost-build`/`tonic-prost-build`, et ne constituent pas une seconde génération de version à corriger.
|
Le premier gate opérateur de `pre.002` confirme l'alignement de versions utile : Tonic 0.14.6 réutilise `http` 1.5, `hyper` 1.11, `hyper-util` 0.1, `tower` 0.5 et `bytes` 1.12 déjà présents ; `yellowstone-grpc-proto` unifie `solana-pubkey` en 4.3.0. Les occurrences Prost 0.14.4 visibles dans `cargo tree --duplicates` correspondent aux contextes runtime/build de la même version, notamment `prost-build`/`tonic-prost-build`, et ne constituent pas une seconde génération de version à corriger.
|
||||||
|
|
||||||
@@ -897,11 +902,11 @@ Prévision courante :
|
|||||||
pre.001 DONE — audit upstream/service/proto + providers gratuits + licences/deps + architecture + threat model + sizing
|
pre.001 DONE — audit upstream/service/proto + providers gratuits + licences/deps + architecture + threat model + sizing
|
||||||
budget : 15–20 min nominal ; preuve : plan + matrice + stratégie B + forecast recalibré
|
budget : 15–20 min nominal ; preuve : plan + matrice + stratégie B + forecast recalibré
|
||||||
|
|
||||||
pre.002 FIX.002 CANDIDATE — moteur Yellowstone : proto/dependencies + settings/errors + channel minimal
|
pre.002 DONE — moteur Yellowstone : proto/dependencies + settings/errors + channel minimal
|
||||||
budget : 15–20 min ; second gate : fmt/audit/check/Clippy + 346 unit PASS ; public_api sync corrigé sous Tokio ; cargo tree inspecté
|
budget : 15–20 min ; gate final fix.002 : fmt/audit/check/Clippy + Transport 346/42/35/4 + dependency canary + workspace PASS
|
||||||
|
|
||||||
pre.003 moteur Yellowstone : TLS/metadata générique + fixture locale + 7 unary RPCs
|
pre.003 CANDIDATE — moteur TLS/metadata + façade N2 unary + fixture locale + 7 unary RPCs
|
||||||
budget : 15–20 min ; preuve : connect/TLS/timeouts/Status safe + wire unary exact
|
budget : 15–20 min ; preuve cible : connect/TLS/timeouts/Status safe + metadata redacted + wire unary exact + cargo tree
|
||||||
|
|
||||||
pre.004 standard Solana : Subscribe foundation + maps/commitment/ping/from_slot/data slices/bounds
|
pre.004 standard Solana : Subscribe foundation + maps/commitment/ping/from_slot/data slices/bounds
|
||||||
budget : 15–20 min ; preuve : omitted/empty/oneof exact + rejets avant I/O
|
budget : 15–20 min ; preuve : omitted/empty/oneof exact + rejets avant I/O
|
||||||
@@ -962,12 +967,17 @@ crates/ksp-onchain-transport-lib/src/grpc_channel.rs
|
|||||||
crates/ksp-onchain-transport-lib/unit_tests/grpc_settings.rs
|
crates/ksp-onchain-transport-lib/unit_tests/grpc_settings.rs
|
||||||
crates/ksp-onchain-transport-lib/unit_tests/grpc_channel.rs
|
crates/ksp-onchain-transport-lib/unit_tests/grpc_channel.rs
|
||||||
|
|
||||||
# pre.003+
|
# pre.003
|
||||||
|
crates/ksp-onchain-transport-lib/src/grpc_unary.rs
|
||||||
|
crates/ksp-onchain-transport-lib/unit_tests/grpc_unary.rs
|
||||||
|
crates/ksp-onchain-transport-lib/src/grpc_channel.rs
|
||||||
|
crates/ksp-onchain-transport-lib/src/grpc_settings.rs
|
||||||
|
|
||||||
|
# pre.004+
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_session.rs
|
crates/ksp-onchain-transport-lib/src/grpc_session.rs
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_protocol.rs
|
crates/ksp-onchain-transport-lib/src/grpc_protocol.rs
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_updates.rs
|
crates/ksp-onchain-transport-lib/src/grpc_updates.rs
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_unary.rs
|
|
||||||
unit_tests/ correspondants
|
unit_tests/ correspondants
|
||||||
tests/public_api.rs
|
tests/public_api.rs
|
||||||
tests/release_completeness.rs
|
tests/release_completeness.rs
|
||||||
@@ -1008,6 +1018,7 @@ Après ajout/modification de la stack gRPC :
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo tree -p ksp-onchain-transport-lib
|
cargo tree -p ksp-onchain-transport-lib
|
||||||
|
cargo tree -p ksp-onchain-transport-lib -e features
|
||||||
cargo tree -p ksp-onchain-transport-lib --duplicates
|
cargo tree -p ksp-onchain-transport-lib --duplicates
|
||||||
cargo tree --duplicates
|
cargo tree --duplicates
|
||||||
```
|
```
|
||||||
@@ -1021,9 +1032,43 @@ prost / prost-types
|
|||||||
bytes / http / hyper / hyper-util
|
bytes / http / hyper / hyper-util
|
||||||
tower
|
tower
|
||||||
rustls / tokio-rustls
|
rustls / tokio-rustls
|
||||||
|
features Tonic : channel/TLS runtime ; codegen/server dev ; pas de router/gzip/zstd KSP
|
||||||
solana-* transitifs
|
solana-* transitifs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 19.1 Gate opérateur final `pre.002-fix.002`
|
||||||
|
|
||||||
|
Preuve opérateur du `2026-08-24` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS / clean
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
Transport unit PASS 346/346
|
||||||
|
Transport public_api PASS 42/42
|
||||||
|
Transport release_completeness PASS 35/35
|
||||||
|
Transport doctests PASS 4/4
|
||||||
|
Core workspace_dependencies PASS 3/3
|
||||||
|
cargo test --workspace PASS ; seuls smokes/bench diagnostics explicitement ignored
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.002` est donc **fermée**. Les graphes Cargo fournis au premier gate restent valides pour son dependency set ; `pre.003` doit les relancer parce qu'elle active TLS et ajoute `http`/`tonic-prost`.
|
||||||
|
|
||||||
|
## 19.2 Gate source `pre.003`
|
||||||
|
|
||||||
|
La candidate matérialise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
N1 : connect() réel + TLS WebPKI/rustls + metadata ASCII publique/secrète redacted
|
||||||
|
N2 : exactement 7 unary Yellowstone standard via dispatcher Tonic privé
|
||||||
|
fixture : GeyserServer local dev-only, metadata + commitment + timeout + Status hostile
|
||||||
|
OUT : Subscribe, SubscribeDeshred, PublicNode, Config V3, reconnect/stream lifecycle
|
||||||
|
```
|
||||||
|
|
||||||
|
Le runtime n'active pas la feature `tonic` de `yellowstone-grpc-proto`; cette feature et `tonic codegen/server` sont réservées au graphe dev/test de la fixture. Le gate Cargo opérateur reste requis avant commit.
|
||||||
|
|
||||||
## 20. Conditions de clôture
|
## 20. Conditions de clôture
|
||||||
|
|
||||||
`0.2.9` ne devient stable que si :
|
`0.2.9` ne devient stable que si :
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/validation/012-V0_2_9_YELLOWSTONE_GRPC.md -->
|
<!-- file: docs/validation/012-V0_2_9_YELLOWSTONE_GRPC.md -->
|
||||||
<!-- version: 8 -->
|
<!-- version: 9 -->
|
||||||
|
|
||||||
# Validation `0.2.9` — moteur Yellowstone + standard Solana + PublicNode
|
# Validation `0.2.9` — moteur Yellowstone + standard Solana + PublicNode
|
||||||
|
|
||||||
> **Statut : matrice ouverte par `0.2.9-pre.001`, corrigée par `pre.001-fix.002`, avancée par `pre.002`, puis corrigée par `pre.002-fix.001` et `pre.002-fix.002`. Le second gate opérateur confirme fmt/audit/check/Clippy et les 346 unit tests ; il a isolé le dernier écart dans le canari d'intégration `public_api`, encore synchrone alors que `prepare()` exige désormais un runtime Tokio actif. `fix.002` corrige uniquement ce canari et attend la réexécution des tests Transport/workspace. Le scope distingue moteur Yellowstone, façade Solana standard et première intégration PublicNode. OrbitFlare et Helius sont les seules releases provider suivantes actuellement planifiées ; les autres providers restent en TODO/IDEAS sans numéro réservé.**
|
> **Statut : `pre.002-fix.002` est fermé sur gate opérateur intégralement vert : fmt/audit/check/Clippy, Transport 346 unit + 42 public API + 35 release completeness + 4 doctests, Core dependency canary et workspace complet. `0.2.9-pre.003` est maintenant candidate : TLS client, metadata générique redacted, connexion réelle et exactement sept unary Yellowstone standard sont matérialisés avec une fixture Geyser locale ; `Subscribe`, PublicNode et Config V3 restent hors tranche. OrbitFlare et Helius sont les seules releases provider suivantes planifiées ; les autres providers restent en TODO/IDEAS sans numéro réservé.**
|
||||||
|
|
||||||
## 1. Autorités du gate
|
## 1. Autorités du gate
|
||||||
|
|
||||||
@@ -195,33 +195,34 @@ Preuve cible : fixtures issues du wire Protobuf + cas old/current + malformed/ov
|
|||||||
|
|
||||||
## 7. Unary RPC matrix
|
## 7. Unary RPC matrix
|
||||||
|
|
||||||
| RPC | Request exact | Response exact | État |
|
| RPC | Request exact | Response exact | État |
|
||||||
|------------------|---------------------------|-----------------------------|------|
|
| ---------------- | ------------------------- | --------------------------- | --------- |
|
||||||
| ReplayInfo | empty | `first_available?` | TODO |
|
| ReplayInfo | empty | `first_available?` | CANDIDATE |
|
||||||
| Ping | `count` | `count` | TODO |
|
| Ping | `count` | `count` | CANDIDATE |
|
||||||
| LatestBlockhash | `commitment?` | slot/hash/last_valid_height | TODO |
|
| LatestBlockhash | `commitment?` | slot/hash/last_valid_height | CANDIDATE |
|
||||||
| BlockHeight | `commitment?` | block_height | TODO |
|
| BlockHeight | `commitment?` | block_height | CANDIDATE |
|
||||||
| Slot | `commitment?` | slot | TODO |
|
| Slot | `commitment?` | slot | CANDIDATE |
|
||||||
| IsBlockhashValid | blockhash + `commitment?` | slot + bool | TODO |
|
| IsBlockhashValid | blockhash + `commitment?` | slot + bool | CANDIDATE |
|
||||||
| Version | empty | version | TODO |
|
| Version | empty | version | CANDIDATE |
|
||||||
|
|
||||||
## 8. Dépendances / licence
|
## 8. Dépendances / licence
|
||||||
|
|
||||||
| Gate | Décision / matérialisation `pre.002` | Preuve / état |
|
| Gate | Décision / matérialisation `pre.003` | Preuve / état |
|
||||||
|--------------------------------|---------------------------------------------------------------|---------------------------------|
|
| ------------------------------ | ------------------------------------------------------------------- | ------------------------------- |
|
||||||
| repository default | AGPL-3.0-only | `LICENSING.md` |
|
| repository default | AGPL-3.0-only | `LICENSING.md` |
|
||||||
| client subtree | Apache-2.0 | `LICENSING.md` |
|
| client subtree | Apache-2.0 | `LICENSING.md` |
|
||||||
| proto subtree | Apache-2.0 | `LICENSING.md` |
|
| proto subtree | Apache-2.0 | `LICENSING.md` |
|
||||||
| strategy A client+proto | rejetée comme default | documenté |
|
| strategy A client+proto | rejetée comme default | documenté |
|
||||||
| strategy B proto+Tonic KSP | **retenue et matérialisée** | source PASS / Cargo pending |
|
| strategy B proto+Tonic KSP | **retenue ; TLS/unary matérialisés** | source PASS / Cargo pending |
|
||||||
| `yellowstone-grpc-proto` | `^12.6`, `default-features = false`, aucune feature KSP | Cargo tree pending |
|
| `yellowstone-grpc-proto` | `^12.6`, `default-features = false`, aucune feature KSP | Cargo tree pending |
|
||||||
| `tonic` | `^0.14`, `default-features = false`, feature `channel` locale | Cargo tree pending |
|
| `tonic` | runtime `channel+tls-aws-lc+tls-webpki-roots`; dev `codegen+server` | Cargo tree pending |
|
||||||
| `yellowstone-grpc-client` | absent | manifest canary ajouté |
|
| `yellowstone-grpc-client` | absent | manifest canary ajouté |
|
||||||
| `prost/prost-types` direct KSP | absent | manifest / Cargo tree pending |
|
| `prost/prost-types` direct KSP | absent | manifest / Cargo tree pending |
|
||||||
| strategy C vendored proto | fallback seulement | aucun vendoring |
|
| `http` / `tonic-prost` direct | `^1.5` / `^0.14`, runtime minimal pour PathAndQuery/ProstCodec | Cargo tree pending |
|
||||||
| system protoc | aucun outil système KSP ajouté | proto publié/build vendored |
|
| strategy C vendored proto | fallback seulement | aucun vendoring |
|
||||||
| raw upstream types public | interdit ; aucun reexport Tonic/Yellowstone | public API canary ajouté |
|
| system protoc | aucun outil système KSP ajouté | proto publié/build vendored |
|
||||||
| executable Yellowstone deps | interdit | dependency firewall à revalider |
|
| raw upstream types public | interdit ; aucun reexport Tonic/Yellowstone | public API canary ajouté |
|
||||||
|
| executable Yellowstone deps | interdit | dependency firewall à revalider |
|
||||||
|
|
||||||
Versions observées :
|
Versions observées :
|
||||||
|
|
||||||
@@ -234,29 +235,29 @@ prost/prost-types 0.14.x (0.14.4 latest observé)
|
|||||||
|
|
||||||
## 9. Architecture / ownership
|
## 9. Architecture / ownership
|
||||||
|
|
||||||
| Invariant | Preuve cible | État `pre.002` |
|
| Invariant | Preuve cible | État `pre.003` |
|
||||||
|--------------------------------------------------------------------------------|--------------------------------|---------------------------------|
|
| ------------------------------------------------------------------------------ | ------------------------------ | ------------------------------------- |
|
||||||
| backend gRPC distinct HTTP/WS | source/API canary | IMPLEMENTED / Cargo pending |
|
| backend gRPC distinct HTTP/WS | source/API canary | IMPLEMENTED / Cargo pending |
|
||||||
| aucun `WsProtocolKind` gRPC | source scan | SOURCE PASS |
|
| aucun `WsProtocolKind` gRPC | source scan | SOURCE PASS |
|
||||||
| Transport owns gRPC | dependency graph | IMPLEMENTED / tree pending |
|
| Transport owns gRPC | dependency graph | IMPLEMENTED / tree pending |
|
||||||
| Config -> Transport seulement | ownership test | hérité / final TODO |
|
| Config -> Transport seulement | ownership test | hérité / final TODO |
|
||||||
| Transport -X-> Config | ownership test | SOURCE PASS / Cargo pending |
|
| Transport -X-> Config | ownership test | SOURCE PASS / Cargo pending |
|
||||||
| Transport -X-> std::env KSP_* | ownership test | SOURCE PASS |
|
| Transport -X-> std::env KSP_* | ownership test | SOURCE PASS |
|
||||||
| Logging façade KSP only | logging ownership | SOURCE PASS |
|
| Logging façade KSP only | logging ownership | SOURCE PASS |
|
||||||
| no raw Tonic client escape hatch | public API canary | IMPLEMENTED / Cargo pending |
|
| no raw Tonic client escape hatch | public API canary | IMPLEMENTED / Cargo pending |
|
||||||
| moteur Yellowstone partagé sans duplication provider | source/API canary | N1 FOUNDATION IMPLEMENTED |
|
| moteur Yellowstone partagé sans duplication provider | source/API canary | N1 FOUNDATION IMPLEMENTED |
|
||||||
| façade Solana Yellowstone standard distincte du moteur | public API canary | TODO `pre.003+` |
|
| façade Solana Yellowstone standard distincte du moteur | public API canary | PARTIAL : 7 unary N2 / Subscribe TODO |
|
||||||
| PublicNode représenté comme provider/capabilities, pas comme nouveau protocole | public API/config canary | TODO `pre.010/011` |
|
| PublicNode représenté comme provider/capabilities, pas comme nouveau protocole | public API/config canary | TODO `pre.010/011` |
|
||||||
| provider peut réutiliser, restreindre ou étendre N2 sans dupliquer N1 | capability/completeness review | architecture conservée |
|
| provider peut réutiliser, restreindre ou étendre N2 sans dupliquer N1 | capability/completeness review | architecture conservée |
|
||||||
| aucune équivalence provider/standard présumée sans preuve | provider matrix/tests | architecture conservée |
|
| aucune équivalence provider/standard présumée sans preuve | provider matrix/tests | architecture conservée |
|
||||||
| façade provider spécialisée seulement si delta réel | completeness review | TODO provider integration |
|
| façade provider spécialisée seulement si delta réel | completeness review | TODO provider integration |
|
||||||
|
|
||||||
## 10. Settings, bounds et redaction
|
## 10. Settings, bounds et redaction
|
||||||
|
|
||||||
État après `pre.002` :
|
État après `pre.003` :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DONE/source+tests ajoutés endpoint schemes http/https validés ; TLS réel reporté pre.003
|
DONE/source+tests ajoutés endpoint schemes http/https validés ; TLS réel matérialisé en pre.003
|
||||||
DONE/source+tests ajoutés endpoint URL, descripteurs et nombre d’endpoints plafonnés
|
DONE/source+tests ajoutés endpoint URL, descripteurs et nombre d’endpoints plafonnés
|
||||||
DONE/source+tests ajoutés connect timeout > 0 et plafonné
|
DONE/source+tests ajoutés connect timeout > 0 et plafonné
|
||||||
DONE/source+tests ajoutés unary timeout > 0 et plafonné
|
DONE/source+tests ajoutés unary timeout > 0 et plafonné
|
||||||
@@ -266,7 +267,7 @@ DONE/source+tests ajoutés request/update queue capacities > 0 et plafonnées
|
|||||||
DONE/source+tests ajoutés reconnect attempt/backoff bornés
|
DONE/source+tests ajoutés reconnect attempt/backoff bornés
|
||||||
TODO pre.004 filter-group count
|
TODO pre.004 filter-group count
|
||||||
TODO pre.004 filter-name length/uniqueness
|
TODO pre.004 filter-name length/uniqueness
|
||||||
TODO pre.003 metadata key/value count/size
|
DONE/source+tests ajoutés metadata ASCII key/value count/size + reserved/bin bounds
|
||||||
TODO pre.005+ account/owner/include/exclude/required counts
|
TODO pre.005+ account/owner/include/exclude/required counts
|
||||||
TODO pre.004+ memcmp/data slice bounds
|
TODO pre.004+ memcmp/data slice bounds
|
||||||
TODO pre.005+ Cuckoo dimensions/data bounds
|
TODO pre.005+ Cuckoo dimensions/data bounds
|
||||||
@@ -276,11 +277,11 @@ Security canaries :
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
DONE/source+tests ajoutés endpoint URL Debug redacted
|
DONE/source+tests ajoutés endpoint URL Debug redacted
|
||||||
TODO pre.003 secret metadata Debug/Display absent
|
DONE/source+tests ajoutés metadata publique/secrète redacted ; secret marqué sensitive
|
||||||
TODO pre.003 Status message/details distants non recopiés aveuglément en context
|
DONE/source+fixture Status message/details/metadata distants exclus des KspError
|
||||||
PARTIAL pre.002 URI Tonic invalide mappée sans recopier l'erreur ; TLS/connect réel pre.003
|
CANDIDATE pre.003 connect réel + TLS WebPKI configuré ; erreur connect/TLS safe, Cargo pending
|
||||||
TODO pre.004+ request/update Debug sans payload arbitraire sensible
|
TODO pre.004+ request/update Debug sans payload arbitraire sensible
|
||||||
PARTIAL pre.002 channel Debug ne contient ni URL ni raw Tonic ; snapshot lifecycle ultérieur
|
DONE pre.003 source/tests channel/client Debug sans URL, metadata value ni raw Tonic ; lifecycle stream ultérieur
|
||||||
```
|
```
|
||||||
|
|
||||||
## 11. Lifecycle / backpressure / replay
|
## 11. Lifecycle / backpressure / replay
|
||||||
@@ -299,7 +300,7 @@ PARTIAL pre.002 channel Debug ne contient ni URL ni raw Tonic ; snap
|
|||||||
| reconnect budget | borné | TODO |
|
| reconnect budget | borné | TODO |
|
||||||
| resubscribe order | déterministe | TODO |
|
| resubscribe order | déterministe | TODO |
|
||||||
| `from_slot` | utilisé sans promesse lossless | TODO |
|
| `from_slot` | utilisé sans promesse lossless | TODO |
|
||||||
| ReplayInfo | informatif | TODO |
|
| ReplayInfo | informatif | CANDIDATE |
|
||||||
| duplicates | observables | TODO |
|
| duplicates | observables | TODO |
|
||||||
| gaps | observables | TODO |
|
| gaps | observables | TODO |
|
||||||
| divergent node history | couverture documentée | TODO |
|
| divergent node history | couverture documentée | TODO |
|
||||||
@@ -402,8 +403,8 @@ Les autres providers restent en TODO/IDEAS sans release dédiée. Aucune façade
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
pre.001 DONE audit/sizing/architecture 15–20 min nominal
|
pre.001 DONE audit/sizing/architecture 15–20 min nominal
|
||||||
pre.002 FIX.001 CANDIDATE moteur: deps/settings/errors/channel 15–20 min
|
pre.002 DONE moteur: deps/settings/errors/channel 15–20 min ; gate final fix.002 PASS
|
||||||
pre.003 TODO moteur: TLS/metadata + fixture + 7 unary 15–20 min
|
pre.003 CANDIDATE TLS/metadata + fixture + 7 unary standard 15–20 min
|
||||||
pre.004 TODO standard: Subscribe common/from_slot/bounds 15–20 min
|
pre.004 TODO standard: Subscribe common/from_slot/bounds 15–20 min
|
||||||
pre.005 TODO standard: accounts + slots 15–20 min
|
pre.005 TODO standard: accounts + slots 15–20 min
|
||||||
pre.006 TODO standard: transactions + transaction_status 15–20 min
|
pre.006 TODO standard: transactions + transaction_status 15–20 min
|
||||||
@@ -510,3 +511,44 @@ Le second échec ne révèle aucune nouvelle faiblesse du moteur N1 : il confirm
|
|||||||
|
|
||||||
**Verdict `pre.002-fix.002` : correctif de test d'intégration prêt ; `pre.002` reste ouverte jusqu'à réexécution verte de `cargo test -p ksp-onchain-transport-lib`, `cargo test --workspace` et des gates habituels.**
|
**Verdict `pre.002-fix.002` : correctif de test d'intégration prêt ; `pre.002` reste ouverte jusqu'à réexécution verte de `cargo test -p ksp-onchain-transport-lib`, `cargo test --workspace` et des gates habituels.**
|
||||||
|
|
||||||
|
### Gate final opérateur `pre.002-fix.002`
|
||||||
|
|
||||||
|
| Gate | Résultat final |
|
||||||
|
| ---------------------------------------- | -------------- |
|
||||||
|
| `cargo fmt --all` | PASS |
|
||||||
|
| audit Rust workspace | PASS / clean |
|
||||||
|
| `cargo check --workspace` | PASS |
|
||||||
|
| `cargo clippy --workspace --all-targets` | PASS |
|
||||||
|
| Transport unit | 346/346 PASS |
|
||||||
|
| Transport `public_api` | 42/42 PASS |
|
||||||
|
| Transport `release_completeness` | 35/35 PASS |
|
||||||
|
| Transport doctests | 4/4 PASS |
|
||||||
|
| Core dependency canary | 3/3 PASS |
|
||||||
|
| `cargo test --workspace` | PASS |
|
||||||
|
|
||||||
|
**Verdict : `pre.002` fermée.**
|
||||||
|
|
||||||
|
## 18. Gate `pre.003` — candidate
|
||||||
|
|
||||||
|
| Gate / surface | État candidate |
|
||||||
|
| ------------------------------------------- | -------------- |
|
||||||
|
| workspace version | `0.2.9-pre.3` |
|
||||||
|
| runtime proto sans feature `tonic` | SOURCE PASS |
|
||||||
|
| Tonic runtime channel + AWS-LC + WebPKI TLS | SOURCE PASS |
|
||||||
|
| Tonic server/codegen uniquement dev/test | SOURCE PASS |
|
||||||
|
| metadata ASCII publique/secrète redacted | SOURCE PASS |
|
||||||
|
| connexion réelle bornée | SOURCE PASS |
|
||||||
|
| 7 unary standard exacts | SOURCE PASS |
|
||||||
|
| fixture Geyser locale | SOURCE PASS |
|
||||||
|
| commitment mapping | SOURCE PASS |
|
||||||
|
| Status hostile sans payload distant | SOURCE PASS |
|
||||||
|
| timeout unary borné | SOURCE PASS |
|
||||||
|
| Subscribe / SubscribeDeshred | OUT pre.003 |
|
||||||
|
| PublicNode / Config V3 | OUT pre.003 |
|
||||||
|
| audit Rust workspace local | PASS / clean |
|
||||||
|
| fmt/check/Clippy/tests/Cargo trees | opérateur TODO |
|
||||||
|
|
||||||
|
La fixture active `yellowstone-grpc-proto/tonic` et Tonic `codegen+server` uniquement dans le graphe dev/test. Le runtime utilise les messages Protobuf publiés, `tonic::client::Grpc` et `tonic-prost::ProstCodec` derrière une façade KSP sans raw escape hatch.
|
||||||
|
|
||||||
|
**Verdict `pre.003` : candidate source prête ; fermeture seulement après gate Cargo opérateur complet et réinspection des graphes.**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user