From 11e5247fbfdb4e68dfb44a68a075e20178263937 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Sun, 13 Sep 2026 23:05:18 +0200 Subject: [PATCH] v0.3.15-pre.008-fix.003 --- Cargo.toml | 4 +- .../README.md | 4 +- .../USAGE.md | 4 +- .../src/route_runtime.rs | 39 ++++++++++---- .../tests/release_completeness.rs | 6 ++- deltas/0.3.15/pre.008-fix.003.md | 51 +++++++++++++++++++ 6 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 deltas/0.3.15/pre.008-fix.003.md diff --git a/Cargo.toml b/Cargo.toml index f7f5ce9..ace2f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 611 +# version: 612 [workspace] resolver = "3" members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"] [workspace.package] -version = "0.3.15-pre.8.fix.2" +version = "0.3.15-pre.8.fix.3" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-app-raw-transaction-ingest-desk/README.md b/crates/ksp-app-raw-transaction-ingest-desk/README.md index f418be4..1fa8ebc 100644 --- a/crates/ksp-app-raw-transaction-ingest-desk/README.md +++ b/crates/ksp-app-raw-transaction-ingest-desk/README.md @@ -1,5 +1,5 @@ - + # `ksp-app-raw-transaction-ingest-desk` @@ -29,7 +29,7 @@ L'inventaire de routes est calculé côté Rust à partir du composite Config va Au Start, le backend revalide une sélection logique par `profile_id + route_id + inventory_generation + commitment`. Il recharge Config, reproouve le réseau Store/Transport, reconstruit le pool HTTP ou l'endpoint WS/gRPC exact requis, ouvre le Store via `ksp-store-lib`, exige un health `Ready` puis lance le Worker exact via `RawTransactionIngestWorker::start_with_runtime_resources`. Les ressources physiques et handles restent exclusivement côté Rust. -Le runtime est actuellement mono-route : un seul Start peut posséder le slot applicatif. Le handle Worker est installé avant l'accusé Start. Stop demande un arrêt coopératif, attend le terminal Worker, puis le monitor backend reprend l'unique `Arc` et exécute `Store::close()` avant de libérer le slot. Le partage d'un Store entre plusieurs Workers reste une responsabilité de la tranche multi-route suivante. +Le runtime est actuellement mono-route : un seul Start peut posséder le slot applicatif. Le handle Worker est installé avant l'accusé Start. Stop demande un arrêt coopératif, attend le terminal Worker, puis le monitor backend reprend l'unique `Arc` et exécute `Store::close()` avant de libérer le slot. Si le Worker est déjà devenu terminal et que son Store a déjà été fermé, un Stop tardif retourne idempotemment le dernier état terminal sûr au lieu d'échouer comme runtime inactif. Ce cache terminal ne contient ni handle ni ressource physique et est effacé dès la réservation du Start suivant. Le partage d'un Store entre plusieurs Workers reste une responsabilité de la tranche multi-route suivante. Les profils applicatifs sont réseau-centriques (`devnet`, `mainnet`, `testnet`). Un profil peut agréger plusieurs profils Transport du même réseau : le provider reste une source de capability et ne devient jamais une identité réseau ni un choix de profil applicatif. Une route explicitement liée à un provider n’est projetée pour un réseau que si le composite déclare une source Transport de ce provider. Ainsi, la route Helius Transaction existe sur Devnet/Mainnet mais n’est pas présentée sur Testnet tant qu’Helius ne fournit pas de source Testnet configurée. diff --git a/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md b/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md index a55e11b..04e4ff4 100644 --- a/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md +++ b/crates/ksp-app-raw-transaction-ingest-desk/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de `ksp-app-raw-transaction-ingest-desk` @@ -40,7 +40,7 @@ Les URLs, tokens, metadata secrètes, handles Transport/Store/Worker, URI Store, Pour une route `Configured`, choisir `confirmed` ou `finalized` puis utiliser `Start`. Le backend revalide la génération d'inventaire et la composition Config, ouvre le Store correspondant, exige son état `Ready`, construit le Worker exact et installe son handle avant de renvoyer l'accusé runtime. -Un seul Worker peut être actif ou en démarrage dans cette tranche. Un second Start est refusé tant que le Worker précédent n'est pas terminal et que son Store n'a pas été explicitement fermé. `Stop` est coopératif : la commande attend le terminal Worker et la fermeture Store avant de rendre le slot à nouveau disponible. +Un seul Worker peut être actif ou en démarrage dans cette tranche. Un second Start est refusé tant que le Worker précédent n'est pas terminal et que son Store n'a pas été explicitement fermé. `Stop` est coopératif : la commande attend le terminal Worker et la fermeture Store avant de rendre le slot à nouveau disponible. Si le Worker est déjà terminal et nettoyé avant le clic Stop, la commande retourne idempotemment le dernier état terminal sûr ; ce comportement permet de resynchroniser le contrôle `pre.008` sans introduire le monitoring continu réservé à une tranche ultérieure. Le frontend n'effectue aucun polling runtime continu dans cette tranche. Une terminaison spontanée est nettoyée côté backend ; sa projection temps réel vers l'UI appartient au monitoring ultérieur. diff --git a/crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs b/crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs index 4995df4..861f81a 100644 --- a/crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs +++ b/crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs -// version: 3 +// version: 4 //! Mono-route Store and Worker lifecycle owned by Raw Transaction Ingest Desk. @@ -8,6 +8,7 @@ const STORE_RECLAIM_POLL_INTERVAL: std::time::Duration = std::time::Duration::fr const STORE_RECLAIM_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1); struct RouteRuntimeInner { + last_terminal: std::option::Option, next_sequence: u64, slot: RouteRuntimeSlot, } @@ -21,7 +22,9 @@ impl crate::RouteRuntimeState { /// Creates an idle mono-route runtime state. #[must_use] pub(crate) fn new() -> Self { - return Self { inner: std::sync::Mutex::new(RouteRuntimeInner { next_sequence: 0, slot: RouteRuntimeSlot::Idle }) }; + return Self { + inner: std::sync::Mutex::new(RouteRuntimeInner { last_terminal: std::option::Option::None, next_sequence: 0, slot: RouteRuntimeSlot::Idle }), + }; } fn reserve(&self, prepared: &crate::PreparedRouteStart) -> ksp_core_lib::Result { @@ -63,6 +66,7 @@ impl crate::RouteRuntimeState { profile_id: prepared.profile_id.clone(), route_id: prepared.route_id, }; + inner.last_terminal = std::option::Option::None; inner.next_sequence = sequence; inner.slot = RouteRuntimeSlot::Starting { token: sequence }; return std::result::Result::Ok(RouteRuntimeReservation { identity, token: sequence, worker_id }); @@ -103,13 +107,14 @@ impl crate::RouteRuntimeState { } } - fn finish(&self, token: u64) { + fn finish(&self, token: u64, terminal: crate::RawIngestRouteRuntimeDto) { let inner = self.inner.lock(); let mut inner = match inner { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; if matches!(&inner.slot, RouteRuntimeSlot::Active { token: current, .. } if *current == token) { + inner.last_terminal = std::option::Option::Some(terminal); inner.slot = RouteRuntimeSlot::Idle; } } @@ -124,9 +129,16 @@ impl crate::RouteRuntimeState { }; match &inner.slot { RouteRuntimeSlot::Active { handle, identity, .. } => std::result::Result::Ok((handle.clone(), identity.clone())), - RouteRuntimeSlot::Idle | RouteRuntimeSlot::Starting { .. } => std::result::Result::Err(ksp_core_lib::Error::new( + RouteRuntimeSlot::Idle => match &inner.last_terminal { + std::option::Option::Some(terminal) => return std::result::Result::Ok(terminal.clone()), + std::option::Option::None => std::result::Result::Err(ksp_core_lib::Error::new( + crate::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE, + "Raw Transaction Ingest Desk has no active mono-route Worker to stop", + )), + }, + RouteRuntimeSlot::Starting { .. } => std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE, - "Raw Transaction Ingest Desk has no active mono-route Worker to stop", + "Raw Transaction Ingest Desk mono-route Worker is still starting and cannot be stopped yet", )), } }; @@ -147,7 +159,12 @@ impl crate::RouteRuntimeState { }; let started = std::time::Instant::now(); loop { - if self.is_idle()? { + let idle = self.is_idle(); + let idle = match idle { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + if idle { return std::result::Result::Ok(identity.dto(project_worker_state(terminal))); } if started.elapsed() >= ROUTE_STOP_CLEANUP_TIMEOUT { @@ -189,7 +206,7 @@ impl crate::RouteRuntimeLaunch { /// Waits for terminal Worker state, explicitly closes Store after the Worker releases its `Arc`, then frees the mono-route slot. pub(crate) async fn monitor(self) { let terminal = self.handle.wait_terminal().await; - match terminal { + let terminal_state = match terminal { std::result::Result::Ok(state) => { ksp_logging_lib::info!( target: crate::TRACING_TARGET, @@ -197,6 +214,7 @@ impl crate::RouteRuntimeLaunch { worker_state = state.code(), "Raw Transaction Ingest Desk mono-route Worker reached terminal state" ); + project_worker_state(state) }, std::result::Result::Err(error) => { ksp_logging_lib::warn!( @@ -206,8 +224,9 @@ impl crate::RouteRuntimeLaunch { error_code = error.code().code(), "Raw Transaction Ingest Desk terminal Worker wait failed" ); + crate::RawIngestRouteState::Faulted }, - } + }; let close = close_store_arc(self.store).await; if let std::result::Result::Err(error) = close { ksp_logging_lib::warn!( @@ -218,7 +237,9 @@ impl crate::RouteRuntimeLaunch { "Raw Transaction Ingest Desk Store shutdown failed after terminal Worker" ); } - self.runtime_state.finish(self.token); + let mut terminal = self.acknowledgement; + terminal.state = terminal_state; + self.runtime_state.finish(self.token, terminal); } } diff --git a/crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs b/crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs index d8bba81..c614c99 100644 --- a/crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs +++ b/crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs -// version: 5 +// version: 6 //! Release-completeness canaries for Raw Transaction Ingest Desk Start-resource reconstruction. @@ -98,7 +98,9 @@ fn pre_008_mono_route_slot_blocks_second_start_until_terminal_store_cleanup() { "RouteRuntimeSlot::Starting", "RouteRuntimeSlot::Active", "ERROR_CODE_ROUTE_RUNTIME_ACTIVE", - "runtime_state.finish(self.token)", + "inner.last_terminal = std::option::Option::None", + "std::option::Option::Some(terminal) => return std::result::Result::Ok(terminal.clone())", + "self.runtime_state.finish(self.token, terminal)", "Arc::try_unwrap", ] { assert!(runtime.contains(required), "missing mono-route ownership marker {required}"); diff --git a/deltas/0.3.15/pre.008-fix.003.md b/deltas/0.3.15/pre.008-fix.003.md new file mode 100644 index 0000000..f154189 --- /dev/null +++ b/deltas/0.3.15/pre.008-fix.003.md @@ -0,0 +1,51 @@ + + + +# Delta `0.3.15-pre.008-fix.003` + +## Objet + +Fermer le gate Clippy de `pre.008-fix.002` et corriger la resynchronisation Stop lorsqu'un Worker atteint spontanément un état terminal avant l'action utilisateur, sans modifier les capabilities Config ni le périmètre mono-route. + +## Défauts observés + +Le gate opérateur confirme `cargo check --workspace` et les suites de tests, mais Clippy strict rejette encore un `?` dans la boucle bornée de nettoyage Stop au titre de `clippy::question_mark_used`. + +Le parcours Tauri live montre également qu'une route peut atteindre spontanément `Faulted` et fermer son Store avant que le frontend ne clique Stop. Le slot backend est alors correctement libéré, mais le frontend possède encore son dernier accusé `Starting`; un Stop tardif retourne donc `route_runtime_not_active` au lieu de lui permettre de récupérer l'état terminal déjà observé. + +## Correction + +- le `?` interdit est remplacé par un `match` explicite avec `return` ; +- le runtime mono-route conserve uniquement le dernier `RawIngestRouteRuntimeDto` terminal sûr après la terminaison Worker et la fermeture Store ; +- un Stop tardif sur un slot déjà `Idle` retourne ce dernier terminal de manière idempotente ; +- la réservation d'un nouveau Start efface immédiatement ce terminal afin de ne jamais mélanger deux exécutions ; +- le cache terminal ne contient aucun handle Worker, aucun `Store`, aucun endpoint, aucun secret et aucune ressource Transport. + +## Caractérisation live inchangée + +Les terminaisons `Faulted` observées ne sont pas masquées par ce fix : + +- Yellowstone Mainnet a rencontré un overflow de queue Transport sous charge, concomitant à de l'hydration HTTP et à une mise en cooldown rate-limit ; +- `standard-block-direct` a atteint `Faulted` après la tentative `blockSubscribe`, méthode explicitement instable. + +Ces événements restent des résultats Worker/Transport réels. Le fix ne fabrique aucune capability, ne transforme pas `Configured` en garantie live et ne modifie aucune politique provider. + +## Périmètre inchangé + +- un seul runtime route à la fois ; +- même revalidation Start issue de `pre.007` ; +- même ouverture, health et fermeture Store via `ksp-store-lib` ; +- même lancement `RawTransactionIngestWorker::start_with_runtime_resources` ; +- aucun partage Store multi-worker de `pre.009` ; +- aucun monitoring continu de `pre.010`. + +## Version + +```text +header racine : 611 -> 612 +workspace : 0.3.15-pre.8.fix.2 -> 0.3.15-pre.8.fix.3 +``` + +## Gate opérateur + +Le gate complet de `pre.008` doit être rejoué après application de ce fix.