v0.3.11-pre.012

This commit is contained in:
2026-09-08 18:58:34 +02:00
parent 636a0c5f43
commit a8cabbebd5
13 changed files with 724 additions and 40 deletions

View File

@@ -0,0 +1,158 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/README.md -->
<!-- version: 1 -->
# ksp-worker-raw-transaction-ingest-lib
`ksp-worker-raw-transaction-ingest-lib` est le premier Worker concret de KSP pour l'alimentation continue de la couche RAW Transaction.
La crate fournit la fondation runtime **source-neutral** du Worker : identité et settings bornés, lifecycle Start/Stop, admission privée bornée, canonicalisation via `ksp-raw-transaction-lib`, persistance backend-neutral via `ksp-store-lib`, supervision des tâches, shutdown borné et snapshots latest-value projetables sur `ksp-worker-api`.
La fondation ne contient volontairement encore **aucune source réseau productive**. Elle ne dépend pas de `ksp-onchain-transport-lib` et n'expose pas d'API publique permettant au caller d'injecter directement des transactions dans la queue interne. Les adapters live/catch-up sont des responsabilités ultérieures du même Worker, pas de son API de fondation.
## Identité et réseau
Une exécution est liée à :
```text
RawNetworkId
WorkerId
Worker kind = raw_transaction_ingest
```
Le réseau logique doit être identique à celui du `Store` remis au démarrage. La transaction canonique conserve l'identité durable définie par la couche RAW commune ; le Worker n'ajoute ni provider, ni endpoint, ni protocole à cette identité.
## Runtime
Le point d'entrée public est :
```text
RawTransactionIngestWorker::start(settings, Arc<Store>)
-> RawTransactionIngestHandle
```
Le démarrage est synchrone mais nécessite qu'un runtime Tokio courant appartienne déjà au caller. Le Worker ne crée pas de runtime global et n'expose aucun `JoinHandle` public.
`RawTransactionIngestHandle` permet de :
- demander un stop coopératif et idempotent ;
- obtenir une source de snapshots concrets latest-value ;
- utiliser cette même source via `WorkerSnapshotSource` ;
- attendre le terminal après drain/abort+join des tâches possédées.
La destruction du dernier handle de contrôle ferme aussi la voie de contrôle privée ; le runtime termine alors selon les mêmes règles de shutdown.
## Admission et Common RAW
La queue centrale est un `tokio::sync::mpsc` privé borné par `admission_queue_capacity`. Les sources internes doivent subir la backpressure du channel ; aucune queue non bornée ni silent drop n'est autorisé.
Chaque ingress admis est :
1. vérifié contre le réseau attendu ;
2. canonicalisé exclusivement par `ksp-raw-transaction-lib` ;
3. associé à une observation key déterministe sous le domaine `ksp.raw_transaction_ingest.observation.v1` ;
4. assemblé en acquisition RAW commune ;
5. remis à la persistence Store.
La crate ne possède pas un second format RAW et ne duplique pas le canonicaliseur commun.
## Persistence Store
La persistance passe uniquement par `ksp-store-lib` avec `default-features = false` dans la crate Worker. Aucun backend physique n'est imposé ou importé directement.
L'écriture utilise le mode normal d'acquisition atomique `RawTransaction + RawTransactionObservation`. Les outcomes distingués sont notamment :
```text
entity inserted
entity already present
entity skipped purged
observation inserted
observation already present
observation not recorded for purged entity
content conflict
store failure
```
`persistence_concurrency` borne le nombre d'écritures Store simultanées. Un conflit de contenu est terminal et n'est jamais converti en succès idempotent.
## Shutdown et faults
Le shutdown possède une deadline bornée par `shutdown_drain_timeout`.
Avant publication terminale, le supervisor :
- arrête les nouvelles admissions ;
- signale le stop aux sources privées ;
- draine le travail déjà admis tant que la deadline le permet ;
- récolte les persistences et sources possédées ;
- en cas de timeout, abort les tâches restantes puis les rejoint avant le terminal.
Les codes d'erreur publics du Worker sont :
```text
worker_raw_transaction_ingest.settings_invalid
worker_raw_transaction_ingest.runtime_invalid
worker_raw_transaction_ingest.store_failed
worker_raw_transaction_ingest.content_conflict
worker_raw_transaction_ingest.counter_exhausted
worker_raw_transaction_ingest.source_failed
worker_raw_transaction_ingest.drain_timeout
```
Les diagnostics ne recopient pas de payload RAW, URL, credential, signature hostile ou texte backend/provider arbitraire.
## Snapshots
`RawTransactionIngestSnapshotSource` est latest-value. Les listeners peuvent rater des états intermédiaires mais obtiennent toujours une valeur complète et monotone par `WorkerSnapshotSequence`.
Le snapshot concret expose notamment :
```text
lifecycle / health / activity
admission_queue_capacity / admission_queue_depth
persistence_concurrency / in_flight_persistence
admitted_total / canonicalized_total / persisted_total
entity_inserted_total / entity_already_present_total / entity_skipped_purged_total
observation_inserted_total / observation_already_present_total
content_conflict_total / store_failure_total / source_failure_total
backpressure_wait_total
```
Les compteurs ne wrapent jamais silencieusement.
## Dépendances
Les dépendances normales sont exactement :
```text
ksp-core-lib
ksp-logging-lib
ksp-raw-transaction-lib
ksp-store-lib (default-features = false)
ksp-worker-api
sha2
tokio (macros, rt, sync, time)
```
La crate ne dépend pas de Config, Job, `ksp-store-api` directement, backend Store concret, Transport, Tauri ou SDK provider.
## Hors périmètre de la fondation source-neutral
Cette surface ne possède pas encore :
- adapter HTTP/WS/Yellowstone productif ;
- sélection Config de sources/endpoints/credentials ;
- discovery/hydration/replay de continuité live ;
- hot reconfiguration de listeners/sources ;
- application Desk ou process autonome ;
- campagne historique/backfill ;
- décodage STRUCTURAL/DECODED/DOMAIN.
Ces extensions doivent conserver la séparation avec `ksp-job-backfill-lib` et réutiliser les mêmes contrats RAW/Store.
## Documentation
- [`USAGE.md`](USAGE.md) — utilisation de la façade publique actuelle ;
- [`../ksp-worker-api/README.md`](../ksp-worker-api/README.md) — contrats Worker génériques ;
- [`../ksp-raw-transaction-lib/README.md`](../ksp-raw-transaction-lib/README.md) — canonicalisation RAW commune ;
- [`../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md`](../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md) — séparation Worker/Job ;
- [`../../docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`](../../docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md) — architecture d'acquisition RawTransaction.

View File

@@ -0,0 +1,206 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md -->
<!-- version: 1 -->
# Utilisation de ksp-worker-raw-transaction-ingest-lib
Cette page décrit l'utilisation de la façade publique source-neutral de `ksp-worker-raw-transaction-ingest-lib`. Le caller possède la composition du runtime Tokio et du `Store` ; la crate Worker ne construit ni Config, ni backend physique, ni Transport.
## Construire les settings
Les identités sont validées par leurs couches propriétaires :
```rust
fn worker_settings() -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings> {
let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let worker_id = match ksp_worker_api::WorkerId::new("raw-ingest-mainnet-0001") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings::with_defaults(
network,
worker_id,
));
}
```
Le Worker kind est fixe :
```rust
assert_eq!(
ksp_worker_raw_transaction_ingest_lib::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE,
"raw_transaction_ingest",
);
```
Pour des limites explicites :
```rust
let settings = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings::new(
network,
worker_id,
512,
16,
std::time::Duration::from_secs(10),
);
```
Bornes publiques :
```text
admission_queue_capacity 1 ..= 65_536 défaut 256
persistence_concurrency 1 ..= 64 défaut 8
shutdown_drain_timeout 100 ms ..= 30 s défaut 5 s
```
Une valeur hors borne retourne `worker_raw_transaction_ingest.settings_invalid` avec uniquement le nom stable du champ invalide.
## Démarrer sur le runtime du caller
`RawTransactionIngestWorker::start` doit être appelé depuis un contexte possédant déjà un runtime Tokio courant. Le `Store` est passé dans un `Arc` et doit cibler exactement le même `RawNetworkId` que les settings.
```rust
async fn start_worker(
settings: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings,
store: std::sync::Arc<ksp_store_lib::Store>,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle> {
return ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start(settings, store);
}
```
Le démarrage retourne avant la fin du Worker. Aucun `JoinHandle` Tokio n'est exposé au caller.
Un appel hors runtime Tokio courant retourne une erreur `worker_raw_transaction_ingest.runtime_invalid`. Un mismatch réseau Store/settings est également rejeté avant le spawn.
## Observer le snapshot concret
```rust
let source = handle.snapshot_source();
let current = source.current();
let sequence = current.worker_snapshot().sequence();
let state = current.worker_snapshot().state();
let queue_depth = current.admission_queue_depth();
let in_flight = current.in_flight_persistence();
```
Pour attendre une valeur plus récente :
```rust
let observed = source.current().worker_snapshot().sequence();
let newer = source.wait_for_change(observed).await;
assert!(newer.worker_snapshot().sequence().is_after(observed));
```
Le flux est latest-value : les transitions intermédiaires peuvent être coalescées. Ne pas l'utiliser comme journal d'événements exhaustif.
## Utiliser la projection Worker API
La même source implémente `ksp_worker_api::WorkerSnapshotSource` :
```rust
let source = handle.worker_snapshot_source();
let current = ksp_worker_api::WorkerSnapshotSource::current(&source);
let observed = current.sequence();
let newer = ksp_worker_api::WorkerSnapshotSource::wait_for_change(&source, observed).await;
assert!(newer.sequence().is_after(observed));
```
Cette projection commune ne contient que les dimensions génériques Worker. Les compteurs d'admission/persistence restent disponibles sur `RawTransactionIngestSnapshot`.
## Lire les compteurs concrets
Les compteurs principaux sont monotones :
```rust
let snapshot = handle.snapshot_source().current();
let admitted = snapshot.admitted_total();
let canonicalized = snapshot.canonicalized_total();
let persisted = snapshot.persisted_total();
let inserted = snapshot.entity_inserted_total();
let existing = snapshot.entity_already_present_total();
let purged = snapshot.entity_skipped_purged_total();
let observations = snapshot.observation_inserted_total();
let conflicts = snapshot.content_conflict_total();
let store_failures = snapshot.store_failure_total();
let source_failures = snapshot.source_failure_total();
let backpressure = snapshot.backpressure_wait_total();
```
`admission_queue_depth()` et `in_flight_persistence()` sont des gauges latest-value, pas des totaux cumulés.
L'épuisement d'un compteur ou de la séquence est terminal et utilise `worker_raw_transaction_ingest.counter_exhausted` au lieu de wrapper silencieusement.
## Demander un stop et attendre le terminal
```rust
let accepted = handle.request_stop();
let terminal = handle.wait_terminal().await;
match terminal {
std::result::Result::Ok(state) => {
assert!(state.is_terminal());
}
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
if accepted {
// La première demande de stop a été remise au runtime vivant.
}
```
`request_stop()` est idempotent. Il exprime une intention coopérative ; le terminal n'est publié qu'après traitement du drain et des tâches possédées.
Le shutdown est borné par `shutdown_drain_timeout`. Si les tâches ne peuvent pas être drainées à temps, le Worker les abort puis les rejoint avant de publier `worker_raw_transaction_ingest.drain_timeout`.
## Interpréter les terminaux faultés
Les codes Worker publics sont :
```text
settings_invalid settings techniques hors contrat
runtime_invalid invariant runtime/lifecycle impossible
store_failed erreur Store non-conflict classifiée
content_conflict transaction canonique incompatible avec le contenu durable
counter_exhausted compteur/séquence monotone arrivé à sa borne
source_failed tâche source privée terminée en erreur
drain_timeout drain du shutdown hors deadline
```
Un consumer doit traiter le `ErrorCode` comme contrat stable et ne pas dépendre d'un texte backend/provider arbitraire.
## Frontière d'admission
La queue d'admission et le type ingress sont volontairement privés. La façade publique actuelle ne propose ni `send`, ni `enqueue`, ni registration d'un adapter réseau.
Les adapters d'acquisition qui alimentent cette queue appartiennent à l'implémentation du Worker et doivent conserver :
```text
backpressure mpsc bornée
stop prioritaire sur send bloqué
canonicalisation via ksp-raw-transaction-lib
persistance via ksp-store-lib
aucun backend physique direct
aucun provider dans l'identité canonique
```
Le caller ne doit pas contourner cette frontière en écrivant directement dans les structures privées du Worker.
## Composition supérieure
Le pattern de composition attendu reste :
```text
Config / application / service owner
-> construit le Store
-> choisit ultérieurement les sources/adapters supportés
-> construit RawTransactionIngestSettings
-> démarre RawTransactionIngestWorker
-> observe RawTransactionIngestSnapshotSource
-> demande stop lorsque nécessaire
```
Le Worker ne reçoit pas de requête historique métier. Une campagne `signature/program_id/plage/limite` appartient à `ksp-job-backfill-lib`, pas à ce runtime continu.