v0.3.15-pre.008-fix.003
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 611
|
# version: 612
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
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"]
|
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]
|
[workspace.package]
|
||||||
version = "0.3.15-pre.8.fix.2"
|
version = "0.3.15-pre.8.fix.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"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
|
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
|
||||||
<!-- version: 7 -->
|
<!-- version: 8 -->
|
||||||
|
|
||||||
# `ksp-app-raw-transaction-ingest-desk`
|
# `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.
|
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<Store>` 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<Store>` 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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
|
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# Utilisation de `ksp-app-raw-transaction-ingest-desk`
|
# 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.
|
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
|
// 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.
|
//! 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);
|
const STORE_RECLAIM_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
|
||||||
|
|
||||||
struct RouteRuntimeInner {
|
struct RouteRuntimeInner {
|
||||||
|
last_terminal: std::option::Option<crate::RawIngestRouteRuntimeDto>,
|
||||||
next_sequence: u64,
|
next_sequence: u64,
|
||||||
slot: RouteRuntimeSlot,
|
slot: RouteRuntimeSlot,
|
||||||
}
|
}
|
||||||
@@ -21,7 +22,9 @@ impl crate::RouteRuntimeState {
|
|||||||
/// Creates an idle mono-route runtime state.
|
/// Creates an idle mono-route runtime state.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn new() -> Self {
|
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<RouteRuntimeReservation> {
|
fn reserve(&self, prepared: &crate::PreparedRouteStart) -> ksp_core_lib::Result<RouteRuntimeReservation> {
|
||||||
@@ -63,6 +66,7 @@ impl crate::RouteRuntimeState {
|
|||||||
profile_id: prepared.profile_id.clone(),
|
profile_id: prepared.profile_id.clone(),
|
||||||
route_id: prepared.route_id,
|
route_id: prepared.route_id,
|
||||||
};
|
};
|
||||||
|
inner.last_terminal = std::option::Option::None;
|
||||||
inner.next_sequence = sequence;
|
inner.next_sequence = sequence;
|
||||||
inner.slot = RouteRuntimeSlot::Starting { token: sequence };
|
inner.slot = RouteRuntimeSlot::Starting { token: sequence };
|
||||||
return std::result::Result::Ok(RouteRuntimeReservation { identity, token: sequence, worker_id });
|
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 inner = self.inner.lock();
|
||||||
let mut inner = match inner {
|
let mut inner = match inner {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => return,
|
std::result::Result::Err(_) => return,
|
||||||
};
|
};
|
||||||
if matches!(&inner.slot, RouteRuntimeSlot::Active { token: current, .. } if *current == token) {
|
if matches!(&inner.slot, RouteRuntimeSlot::Active { token: current, .. } if *current == token) {
|
||||||
|
inner.last_terminal = std::option::Option::Some(terminal);
|
||||||
inner.slot = RouteRuntimeSlot::Idle;
|
inner.slot = RouteRuntimeSlot::Idle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,9 +129,16 @@ impl crate::RouteRuntimeState {
|
|||||||
};
|
};
|
||||||
match &inner.slot {
|
match &inner.slot {
|
||||||
RouteRuntimeSlot::Active { handle, identity, .. } => std::result::Result::Ok((handle.clone(), identity.clone())),
|
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,
|
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();
|
let started = std::time::Instant::now();
|
||||||
loop {
|
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)));
|
return std::result::Result::Ok(identity.dto(project_worker_state(terminal)));
|
||||||
}
|
}
|
||||||
if started.elapsed() >= ROUTE_STOP_CLEANUP_TIMEOUT {
|
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.
|
/// 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) {
|
pub(crate) async fn monitor(self) {
|
||||||
let terminal = self.handle.wait_terminal().await;
|
let terminal = self.handle.wait_terminal().await;
|
||||||
match terminal {
|
let terminal_state = match terminal {
|
||||||
std::result::Result::Ok(state) => {
|
std::result::Result::Ok(state) => {
|
||||||
ksp_logging_lib::info!(
|
ksp_logging_lib::info!(
|
||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
@@ -197,6 +214,7 @@ impl crate::RouteRuntimeLaunch {
|
|||||||
worker_state = state.code(),
|
worker_state = state.code(),
|
||||||
"Raw Transaction Ingest Desk mono-route Worker reached terminal state"
|
"Raw Transaction Ingest Desk mono-route Worker reached terminal state"
|
||||||
);
|
);
|
||||||
|
project_worker_state(state)
|
||||||
},
|
},
|
||||||
std::result::Result::Err(error) => {
|
std::result::Result::Err(error) => {
|
||||||
ksp_logging_lib::warn!(
|
ksp_logging_lib::warn!(
|
||||||
@@ -206,8 +224,9 @@ impl crate::RouteRuntimeLaunch {
|
|||||||
error_code = error.code().code(),
|
error_code = error.code().code(),
|
||||||
"Raw Transaction Ingest Desk terminal Worker wait failed"
|
"Raw Transaction Ingest Desk terminal Worker wait failed"
|
||||||
);
|
);
|
||||||
|
crate::RawIngestRouteState::Faulted
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
let close = close_store_arc(self.store).await;
|
let close = close_store_arc(self.store).await;
|
||||||
if let std::result::Result::Err(error) = close {
|
if let std::result::Result::Err(error) = close {
|
||||||
ksp_logging_lib::warn!(
|
ksp_logging_lib::warn!(
|
||||||
@@ -218,7 +237,9 @@ impl crate::RouteRuntimeLaunch {
|
|||||||
"Raw Transaction Ingest Desk Store shutdown failed after terminal Worker"
|
"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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
|
// 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.
|
//! 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::Starting",
|
||||||
"RouteRuntimeSlot::Active",
|
"RouteRuntimeSlot::Active",
|
||||||
"ERROR_CODE_ROUTE_RUNTIME_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",
|
"Arc::try_unwrap",
|
||||||
] {
|
] {
|
||||||
assert!(runtime.contains(required), "missing mono-route ownership marker {required}");
|
assert!(runtime.contains(required), "missing mono-route ownership marker {required}");
|
||||||
|
|||||||
51
deltas/0.3.15/pre.008-fix.003.md
Normal file
51
deltas/0.3.15/pre.008-fix.003.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!-- file: deltas/0.3.15/pre.008-fix.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# 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.
|
||||||
Reference in New Issue
Block a user