v0.3.15-pre.006-fix.002
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# `ksp-app-raw-transaction-ingest-desk`
|
||||
|
||||
@@ -41,6 +41,8 @@ helius-transaction-hydrated
|
||||
http-block-polling
|
||||
```
|
||||
|
||||
Les profils standard publics committés déclarent `Block` en plus de `Logs`; `standard-block-direct` est donc composable depuis Config sur Devnet, Mainnet et Testnet. Cette déclaration ne transforme pas `blockSubscribe` en méthode stable et ne remplace pas la revalidation runtime.
|
||||
|
||||
Une route `Configured` est uniquement **composable depuis Config**. La disponibilité réelle des connexions, du Store et du Worker est revalidée lors du Start avant qu'une route puisse devenir `Running`.
|
||||
|
||||
## Ports de développement
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# Utilisation de `ksp-app-raw-transaction-ingest-desk`
|
||||
|
||||
@@ -29,7 +29,7 @@ raison sûre lorsque la route est indisponible
|
||||
génération d'inventaire
|
||||
```
|
||||
|
||||
`Configured` signifie uniquement que toutes les requirements Config nécessaires à la route sont cohérentes. Aucun probe réseau, aucune ouverture de Store et aucun Worker ne sont déclenchés par l'inventaire.
|
||||
`Configured` signifie uniquement que toutes les requirements Config nécessaires à la route sont cohérentes. Les profils `solana-public` standard committés déclarent explicitement les capabilities `Logs` et `Block`; `standard-block-direct` est donc Configured sur les trois réseaux logiques tant que ce socle Config reste valide. Aucun probe réseau, aucune ouverture de Store et aucun Worker ne sont déclenchés par l'inventaire.
|
||||
|
||||
Un refresh reconstruit l'inventaire depuis Config et publie une nouvelle génération. La sélection visuelle d'un réseau ne choisit aucune URL, provider ou ressource physique et ne démarre aucune acquisition. Une capability optionnelle dont le secret n'est pas résolu rend uniquement la route qui en dépend indisponible ; les routes satisfaites par le Transport de base du même réseau restent composables.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
#[test]
|
||||
fn pre_006_route_requirement_projection_is_fail_closed_and_ordered() {
|
||||
@@ -38,14 +38,19 @@ fn pre_006_unavailable_profile_projects_all_five_routes_without_physical_materia
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_006_fix_001_network_profiles_aggregate_same_network_transport_capabilities() {
|
||||
fn pre_006_fix_002_network_profiles_aggregate_same_network_transport_capabilities_and_standard_block() {
|
||||
let management = committed_management();
|
||||
assert!(management.is_ok());
|
||||
let management = match management {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let environment = all_optional_source_secrets();
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
assert!(environment.is_ok());
|
||||
let environment = match environment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let inventory = super::build_route_inventory_with_environment(&management, &environment, 7);
|
||||
assert!(inventory.is_ok());
|
||||
let inventory = match inventory {
|
||||
@@ -63,32 +68,29 @@ fn pre_006_fix_001_network_profiles_aggregate_same_network_transport_capabilitie
|
||||
if let std::option::Option::Some(devnet) = devnet {
|
||||
assert!(devnet.is_default);
|
||||
assert_eq!(devnet.network.as_deref(), std::option::Option::Some("devnet"));
|
||||
assert_route(devnet, crate::RawIngestRouteId::YellowstoneHydrated, true, std::option::Option::None);
|
||||
assert_optional_secret_route(devnet, crate::RawIngestRouteId::YellowstoneHydrated);
|
||||
assert_route(devnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None);
|
||||
assert_route(
|
||||
devnet,
|
||||
crate::RawIngestRouteId::StandardBlockDirect,
|
||||
false,
|
||||
std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingWsBlockCapability),
|
||||
);
|
||||
assert_route(devnet, crate::RawIngestRouteId::HeliusTransactionHydrated, true, std::option::Option::None);
|
||||
assert_route(devnet, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None);
|
||||
assert_optional_secret_route(devnet, crate::RawIngestRouteId::HeliusTransactionHydrated);
|
||||
assert_route(devnet, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None);
|
||||
}
|
||||
let mainnet = profile(&inventory, "mainnet");
|
||||
assert!(mainnet.is_some());
|
||||
if let std::option::Option::Some(mainnet) = mainnet {
|
||||
assert_eq!(mainnet.network.as_deref(), std::option::Option::Some("mainnet"));
|
||||
assert_route(mainnet, crate::RawIngestRouteId::YellowstoneHydrated, true, std::option::Option::None);
|
||||
assert_optional_secret_route(mainnet, crate::RawIngestRouteId::YellowstoneHydrated);
|
||||
assert_route(mainnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None);
|
||||
assert_route(mainnet, crate::RawIngestRouteId::HeliusTransactionHydrated, true, std::option::Option::None);
|
||||
assert_route(mainnet, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None);
|
||||
assert_optional_secret_route(mainnet, crate::RawIngestRouteId::HeliusTransactionHydrated);
|
||||
assert_route(mainnet, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None);
|
||||
}
|
||||
let testnet = profile(&inventory, "testnet");
|
||||
assert!(testnet.is_some());
|
||||
if let std::option::Option::Some(testnet) = testnet {
|
||||
assert_eq!(testnet.network.as_deref(), std::option::Option::Some("testnet"));
|
||||
assert_route(testnet, crate::RawIngestRouteId::YellowstoneHydrated, true, std::option::Option::None);
|
||||
assert_optional_secret_route(testnet, crate::RawIngestRouteId::YellowstoneHydrated);
|
||||
assert_route(testnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None);
|
||||
assert_route(testnet, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None);
|
||||
assert_route(
|
||||
testnet,
|
||||
crate::RawIngestRouteId::HeliusTransactionHydrated,
|
||||
@@ -119,37 +121,33 @@ fn pre_006_fix_001_network_profiles_aggregate_same_network_transport_capabilitie
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_006_fix_001_missing_optional_source_secret_does_not_disable_base_network_routes() {
|
||||
fn pre_006_fix_002_base_network_routes_remain_configured_independently_of_optional_sources() {
|
||||
let management = committed_management();
|
||||
assert!(management.is_ok());
|
||||
let management = match management {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let environment = ksp_config_lib::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
assert!(environment.is_ok());
|
||||
let environment = match environment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let inventory = super::build_route_inventory_with_environment(&management, &environment, 11);
|
||||
assert!(inventory.is_ok());
|
||||
let inventory = match inventory {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let mainnet = profile(&inventory, "mainnet");
|
||||
assert!(mainnet.is_some());
|
||||
if let std::option::Option::Some(mainnet) = mainnet {
|
||||
assert_route(mainnet, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None);
|
||||
assert_route(mainnet, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None);
|
||||
assert_route(
|
||||
mainnet,
|
||||
crate::RawIngestRouteId::YellowstoneHydrated,
|
||||
false,
|
||||
std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingRequiredSecret),
|
||||
);
|
||||
assert_route(
|
||||
mainnet,
|
||||
crate::RawIngestRouteId::HeliusTransactionHydrated,
|
||||
false,
|
||||
std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingRequiredSecret),
|
||||
);
|
||||
for profile_id in ["devnet", "mainnet", "testnet"] {
|
||||
let selected = profile(&inventory, profile_id);
|
||||
assert!(selected.is_some());
|
||||
if let std::option::Option::Some(selected) = selected {
|
||||
assert_route(selected, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None);
|
||||
assert_route(selected, crate::RawIngestRouteId::StandardBlockDirect, true, std::option::Option::None);
|
||||
assert_route(selected, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,19 +180,24 @@ fn committed_management() -> ksp_core_lib::Result<ksp_config_lib::ConfigManageme
|
||||
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry)));
|
||||
}
|
||||
|
||||
fn all_optional_source_secrets() -> ksp_config_lib::ConfigEnvironment {
|
||||
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||
process.insert("KSP_SECRET_HELIUS_API_KEY".to_owned(), "helius-route-inventory-canary".to_owned());
|
||||
process.insert("KSP_SECRET_ORBITFLARE_DEVNET_GRPC_X_TOKEN".to_owned(), "orbitflare-route-inventory-canary".to_owned());
|
||||
process.insert("KSP_SECRET_PUBLICNODE_MAINNET_GRPC_X_TOKEN".to_owned(), "publicnode-mainnet-route-inventory-canary".to_owned());
|
||||
process.insert("KSP_SECRET_PUBLICNODE_TESTNET_GRPC_X_TOKEN".to_owned(), "publicnode-testnet-route-inventory-canary".to_owned());
|
||||
return ksp_config_lib::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
}
|
||||
|
||||
fn profile<'a>(inventory: &'a crate::RawIngestRouteInventoryDto, profile_id: &str) -> std::option::Option<&'a crate::RawIngestProfileInventoryDto> {
|
||||
return inventory.profiles.iter().find(|profile| return profile.profile_id == profile_id);
|
||||
}
|
||||
|
||||
fn assert_optional_secret_route(profile: &crate::RawIngestProfileInventoryDto, route_id: crate::RawIngestRouteId) {
|
||||
let route = profile.routes.iter().find(|route| return route.route_id == route_id);
|
||||
assert!(route.is_some());
|
||||
if let std::option::Option::Some(route) = route {
|
||||
if route.selectable {
|
||||
assert_eq!(route.state, crate::RawIngestRouteState::Configured);
|
||||
assert_eq!(route.reason, std::option::Option::None);
|
||||
} else {
|
||||
assert_eq!(route.state, crate::RawIngestRouteState::Unavailable);
|
||||
assert_eq!(route.reason, std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingRequiredSecret));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_route(
|
||||
profile: &crate::RawIngestProfileInventoryDto,
|
||||
route_id: crate::RawIngestRouteId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
||||
<!-- version: 17 -->
|
||||
<!-- version: 18 -->
|
||||
|
||||
# Utilisation de ksp-config-lib
|
||||
|
||||
@@ -164,7 +164,7 @@ L'accesseur historique `into_transport_settings()` conserve volontairement son t
|
||||
|
||||
Les protocoles WebSocket restent `kind = "solana_standard"` et `kind = "helius_laserstream"`. Chaque endpoint peut en plus déclarer `capabilities`, liste de descriptors `WsSubscriptionKind` (`account`, `block`, `logs`, `program`, `root`, `signature`, `slot`, `slots_updates`, `vote`, `helius_transaction`). L'absence de cette liste reste backward-compatible mais ne constitue aucune preuve de support ; une liste présente est mappée explicitement vers Transport puis validée contre le protocole. Config ne déduit jamais une capability depuis `provider`.
|
||||
|
||||
Les profils Solana standard versionnés déclarent conservativement les six familles stables `account/logs/program/root/signature/slot` et ne déclarent donc pas `block`. Le profil `testnet_public` fournit le socle HTTP + WS standard Testnet sans credential ; `publicnode_testnet` reste une source Yellowstone optionnelle séparée. Les profils `helius_devnet` et `helius_mainnet` déclarent les sept familles Helius déjà supportées par la façade (`account/logs/program/root/signature/slot/slots_updates`) plus `helius_transaction`, sans `block` ni `vote`. Helius LaserStream WebSocket conserve ses URLs Config-owned et sa clé `KSP_SECRET_HELIUS_API_KEY`; cette surface est indépendante de Yellowstone gRPC.
|
||||
Les profils Solana standard publics versionnés déclarent `account/block/logs/program/root/signature/slot`. `block` reste une capability explicite, instable et validator/provider-gated : Config ne l’infère jamais du protocol kind ni du provider, mais les endpoints publics committés l’annoncent désormais explicitement pour rendre la route Standard Block composable. Le profil `testnet_public` fournit le socle HTTP + WS standard Testnet sans credential ; `publicnode_testnet` reste une source Yellowstone optionnelle séparée. Les profils `helius_devnet` et `helius_mainnet` déclarent les sept familles Helius déjà supportées par la façade (`account/logs/program/root/signature/slot/slots_updates`) plus `helius_transaction`, sans `block` ni `vote`. Helius LaserStream WebSocket conserve ses URLs Config-owned et sa clé `KSP_SECRET_HELIUS_API_KEY`; cette surface est indépendante de Yellowstone gRPC.
|
||||
|
||||
Les scalaires `*_ms` restent des valeurs Config et sont convertis en `std::time::Duration` par l'adapter. La dépendance reste unidirectionnelle : Config connaît les contrats Transport pour les construire ; Transport ne connaît ni Config, ni `.env`, ni les variables KSP.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/transport.rs
|
||||
// version: 15
|
||||
// version: 16
|
||||
|
||||
#[test]
|
||||
fn fixture_transport_profile_maps_complete_runtime_contract() {
|
||||
@@ -146,7 +146,7 @@ fn committed_transport_document_maps_default_and_explicit_profiles() {
|
||||
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.devnet.solana.com");
|
||||
assert_eq!(ws.endpoints()[0].protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
|
||||
assert!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs));
|
||||
assert!(!ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||
assert!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||
}
|
||||
}
|
||||
if let std::result::Result::Ok(mainnet) = mainnet {
|
||||
@@ -160,7 +160,7 @@ fn committed_transport_document_maps_default_and_explicit_profiles() {
|
||||
if let std::option::Option::Some(ws) = ws {
|
||||
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.mainnet.solana.com");
|
||||
assert!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs));
|
||||
assert!(!ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||
assert!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ fn v0_3_15_pre_006_fix_001_testnet_public_is_keyless_standard_transport_without_
|
||||
assert_eq!(ws.endpoints()[0].cluster().as_str(), "testnet");
|
||||
assert_eq!(ws.endpoints()[0].protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
|
||||
assert!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs));
|
||||
assert!(!ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||
assert!(ws.endpoints()[0].supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||
}
|
||||
assert!(resolved.grpc_settings().is_none(), "keyless Testnet base profile must not conflate PublicNode Yellowstone with the network itself");
|
||||
}
|
||||
@@ -588,6 +588,35 @@ fn helius_laserstream_mainnet_and_devnet_api_key_map_to_protocol_and_safe_redact
|
||||
assert!(devnet_debug.contains(crate::REDACTED_CONFIG_VALUE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_006_fix_002_committed_public_standard_profiles_declare_block_capability() {
|
||||
let engine = committed_engine();
|
||||
let engine = match engine {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
for (profile_id, cluster) in [("devnet_public", "devnet"), ("mainnet_public", "mainnet"), ("testnet_public", "testnet")] {
|
||||
let resolved = engine.load_resolved_transport_config(std::option::Option::Some(profile_id), &environment);
|
||||
assert!(resolved.is_ok(), "committed public standard profile {profile_id} should resolve: {resolved:?}");
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => continue,
|
||||
};
|
||||
let ws = resolved.ws_settings();
|
||||
assert!(ws.is_some(), "committed public standard profile {profile_id} should expose WebSocket settings");
|
||||
if let std::option::Option::Some(ws) = ws {
|
||||
assert_eq!(ws.endpoints().len(), 1);
|
||||
let endpoint = &ws.endpoints()[0];
|
||||
assert_eq!(endpoint.provider().as_str(), "solana-public");
|
||||
assert_eq!(endpoint.cluster().as_str(), cluster);
|
||||
assert_eq!(endpoint.protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
|
||||
assert!(endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs));
|
||||
assert!(endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_003_committed_helius_profiles_map_transaction_capability_without_block_or_vote() {
|
||||
let engine = committed_engine();
|
||||
|
||||
Reference in New Issue
Block a user