v0.3.15-pre.006

This commit is contained in:
2026-09-13 11:30:56 +02:00
parent 9ee3cd4a53
commit 0a8ce6e5f9
23 changed files with 1298 additions and 112 deletions

View File

@@ -0,0 +1,375 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_inventory.rs
// version: 1
//! Config-only logical route inventory for Raw Transaction Ingest Desk.
struct CompositeProfileCatalog {
default_profile: String,
profile_ids: std::vec::Vec<String>,
}
/// Rebuilds the complete safe route inventory from validated Config without opening any network, Store or Worker resource.
pub(crate) fn build_route_inventory(management: &ksp_config_lib::ConfigManagement, generation: u32) -> ksp_core_lib::Result<crate::RawIngestRouteInventoryDto> {
let catalog = composite_profile_catalog(management);
let catalog = match catalog {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let environment = ksp_config_lib::ConfigEnvironment::load();
let environment = match environment {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut profiles = std::vec::Vec::with_capacity(catalog.profile_ids.len());
for profile_id in &catalog.profile_ids {
profiles.push(build_profile_inventory(management, &environment, profile_id.as_str(), profile_id == &catalog.default_profile));
}
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_INVENTORY,
generation,
profile_count = profiles.len(),
"rebuilt Raw Transaction Ingest Desk Config-only route inventory"
);
return std::result::Result::Ok(crate::RawIngestRouteInventoryDto { default_profile: catalog.default_profile, generation, profiles });
}
fn composite_profile_catalog(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<CompositeProfileCatalog> {
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_RAW_TRANSACTION_INGEST_DESK);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let document = management.engine().load_validated_document(&file_id);
let document = match document {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let root = document.value().as_object();
let root = match root {
std::option::Option::Some(value) => value,
std::option::Option::None => return inventory_invalid("validated Raw Transaction Ingest Desk composite root is not an object"),
};
let default_profile = root.get("default_profile").and_then(|value| return value.as_str());
let default_profile = match default_profile {
std::option::Option::Some(value) if !value.is_empty() => value.to_owned(),
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite has no readable default profile"),
};
let profile_values = root.get("profiles").and_then(|value| return value.as_array());
let profile_values = match profile_values {
std::option::Option::Some(value) if !value.is_empty() => value,
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite has no profile inventory"),
};
let mut profile_ids = std::vec::Vec::with_capacity(profile_values.len());
let mut seen = std::collections::BTreeSet::<String>::new();
for profile in profile_values {
let profile_id = profile.get("profile_id").and_then(|value| return value.as_str());
let profile_id = match profile_id {
std::option::Option::Some(value) if !value.is_empty() => value,
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite contains an unreadable profile identifier"),
};
if !seen.insert(profile_id.to_owned()) {
return inventory_invalid("validated Raw Transaction Ingest Desk composite contains duplicate profile identifiers");
}
profile_ids.push(profile_id.to_owned());
}
if !seen.contains(default_profile.as_str()) {
return inventory_invalid("validated Raw Transaction Ingest Desk composite default profile is absent from the profile inventory");
}
return std::result::Result::Ok(CompositeProfileCatalog { default_profile, profile_ids });
}
fn build_profile_inventory(
management: &ksp_config_lib::ConfigManagement,
environment: &ksp_config_lib::ConfigEnvironment,
profile_id: &str,
is_default: bool,
) -> crate::RawIngestProfileInventoryDto {
let composite = crate::load_raw_transaction_ingest_desk_composite_profile(management, std::option::Option::Some(profile_id));
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let transport_profile =
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
let transport_profile = match transport_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let store_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
let store_profile = match store_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let transport = management.engine().resolve_transport_config_profile(&transport_profile, environment);
let store = management.engine().resolve_store_config_profile(&store_profile, environment);
let network_hint = resolved_network_hint(&transport, &store);
if transport.is_err() || store.is_err() {
let reason = resolution_failure_reason(&transport, &store);
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), reason);
}
let transport = match transport {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let store = match store {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let store_network = store.settings().network().as_str().to_owned();
if !transport_matches_network(&transport, store_network.as_str()) {
return unavailable_profile(
profile_id,
is_default,
std::option::Option::Some(store_network.as_str()),
crate::RawIngestRouteUnavailableReason::NetworkMismatch,
);
}
let routes = build_configured_profile_routes(&transport, store_network.as_str());
return crate::RawIngestProfileInventoryDto {
is_default,
network: std::option::Option::Some(store_network),
profile_id: profile_id.to_owned(),
routes,
};
}
fn build_configured_profile_routes(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> std::vec::Vec<crate::RawIngestRouteDto> {
let has_get_transaction = http_role_supports_methods(transport.http_settings(), &["getTransaction"]);
let has_http_block_scan = http_role_supports_methods(transport.http_settings(), &["getSlot", "getBlocksWithLimit", "getBlock"]);
let has_yellowstone = has_yellowstone_grpc(transport, network);
let has_standard_logs =
has_ws_capability(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Logs);
let has_standard_block =
has_ws_capability(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Block);
let has_helius_transaction = has_ws_capability(
transport,
network,
ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream,
ksp_onchain_transport_lib::WsSubscriptionKind::HeliusTransaction,
);
return vec![
route_with_requirements(
crate::RawIngestRouteId::YellowstoneHydrated,
network,
&[
(has_yellowstone, crate::RawIngestRouteUnavailableReason::MissingYellowstoneGrpc),
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
],
),
route_with_requirements(
crate::RawIngestRouteId::StandardLogsHydrated,
network,
&[
(has_standard_logs, crate::RawIngestRouteUnavailableReason::MissingWsLogsCapability),
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
],
),
route_with_requirements(
crate::RawIngestRouteId::StandardBlockDirect,
network,
&[(has_standard_block, crate::RawIngestRouteUnavailableReason::MissingWsBlockCapability)],
),
route_with_requirements(
crate::RawIngestRouteId::HeliusTransactionHydrated,
network,
&[
(has_helius_transaction, crate::RawIngestRouteUnavailableReason::MissingHeliusTransactionCapability),
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
],
),
route_with_requirements(
crate::RawIngestRouteId::HttpBlockPolling,
network,
&[(has_http_block_scan, crate::RawIngestRouteUnavailableReason::MissingHttpBlockScan)],
),
];
}
fn route_with_requirements(
route_id: crate::RawIngestRouteId,
network: &str,
requirements: &[(bool, crate::RawIngestRouteUnavailableReason)],
) -> crate::RawIngestRouteDto {
for (available, reason) in requirements.iter().copied() {
if !available {
return crate::RawIngestRouteDto::unavailable(route_id, std::option::Option::Some(network), reason);
}
}
return crate::RawIngestRouteDto::configured(route_id, network);
}
fn unavailable_profile(
profile_id: &str,
is_default: bool,
network: std::option::Option<&str>,
reason: crate::RawIngestRouteUnavailableReason,
) -> crate::RawIngestProfileInventoryDto {
let routes = crate::RawIngestRouteId::all()
.into_iter()
.map(|route_id| return crate::RawIngestRouteDto::unavailable(route_id, network, reason))
.collect::<std::vec::Vec<_>>();
return crate::RawIngestProfileInventoryDto { is_default, network: network.map(str::to_owned), profile_id: profile_id.to_owned(), routes };
}
fn resolution_failure_reason(
transport: &ksp_core_lib::Result<ksp_config_lib::ResolvedTransportConfig>,
store: &ksp_core_lib::Result<ksp_config_lib::ResolvedStoreConfig>,
) -> crate::RawIngestRouteUnavailableReason {
let transport_missing_secret = match transport {
std::result::Result::Err(error) => error.code() == ksp_config_lib::ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING,
std::result::Result::Ok(_) => false,
};
let store_missing_secret = match store {
std::result::Result::Err(error) => error.code() == ksp_config_lib::ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING,
std::result::Result::Ok(_) => false,
};
if transport_missing_secret || store_missing_secret {
return crate::RawIngestRouteUnavailableReason::MissingRequiredSecret;
}
return crate::RawIngestRouteUnavailableReason::ProfileUnresolved;
}
fn resolved_network_hint(
transport: &ksp_core_lib::Result<ksp_config_lib::ResolvedTransportConfig>,
store: &ksp_core_lib::Result<ksp_config_lib::ResolvedStoreConfig>,
) -> std::option::Option<String> {
if let std::result::Result::Ok(store) = store {
return std::option::Option::Some(store.settings().network().as_str().to_owned());
}
if let std::result::Result::Ok(transport) = transport {
let networks = enabled_transport_networks(transport);
if networks.len() == 1 {
return networks.into_iter().next();
}
}
return std::option::Option::None;
}
fn transport_matches_network(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> bool {
let networks = enabled_transport_networks(transport);
return networks.len() == 1 && networks.contains(network);
}
fn enabled_transport_networks(transport: &ksp_config_lib::ResolvedTransportConfig) -> std::collections::BTreeSet<String> {
let mut networks = std::collections::BTreeSet::<String>::new();
for endpoint in transport.http_settings().endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
if let std::option::Option::Some(settings) = transport.ws_settings() {
for endpoint in settings.endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
}
if let std::option::Option::Some(settings) = transport.grpc_settings() {
for endpoint in settings.endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
}
return networks;
}
fn has_ws_capability(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
protocol: ksp_onchain_transport_lib::WsProtocolKind,
capability: ksp_onchain_transport_lib::WsSubscriptionKind,
) -> bool {
let settings = match transport.ws_settings() {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
for endpoint in settings.endpoints() {
if endpoint.enabled() && endpoint.cluster().as_str() == network && endpoint.protocol() == protocol && endpoint.supports_subscription(capability) {
return true;
}
}
return false;
}
fn has_yellowstone_grpc(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> bool {
let settings = match transport.grpc_settings() {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
return settings.endpoints().iter().any(|endpoint| return endpoint.enabled() && endpoint.cluster().as_str() == network);
}
fn http_role_supports_methods(settings: &ksp_onchain_transport_lib::HttpTransportSettings, methods: &[&'static str]) -> bool {
let mut request_kinds = std::vec::Vec::<&'static str>::with_capacity(methods.len());
for method_name in methods {
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name);
let descriptor = match descriptor {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
request_kinds.push(descriptor.request_kind());
}
let mut role_names = std::collections::BTreeSet::<String>::new();
for endpoint in settings.endpoints() {
if !endpoint.enabled() {
continue;
}
for role in endpoint.roles() {
if role.enabled() {
role_names.insert(role.role().as_str().to_owned());
}
}
}
for role_name in role_names {
let mut complete = true;
for request_kind in &request_kinds {
if !http_role_supports_request_kind(settings, role_name.as_str(), request_kind) {
complete = false;
break;
}
}
if complete {
return true;
}
}
return false;
}
fn http_role_supports_request_kind(settings: &ksp_onchain_transport_lib::HttpTransportSettings, role_name: &str, request_kind: &str) -> bool {
for endpoint in settings.endpoints() {
if !endpoint.enabled() {
continue;
}
for role in endpoint.roles() {
if !role.enabled() || role.role().as_str() != role_name {
continue;
}
for capability in role.request_kinds() {
if capability.is_wildcard() || capability.as_str() == request_kind {
return true;
}
}
}
}
return false;
}
fn inventory_invalid(message: &'static str) -> ksp_core_lib::Result<CompositeProfileCatalog> {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_INVENTORY_INVALID, message));
}
#[cfg(test)]
#[path = "../unit_tests/route_inventory.rs"]
mod tests;