v0.3.6-pre.012
This commit is contained in:
123
crates/ksp-job-backfill-lib/README.md
Normal file
123
crates/ksp-job-backfill-lib/README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
<!-- file: crates/ksp-job-backfill-lib/README.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# ksp-job-backfill-lib
|
||||
|
||||
`ksp-job-backfill-lib` implémente le premier job historique concret de KSP : un backfill borné de transactions Solana vers la couche RAW durable.
|
||||
|
||||
La crate compose les contrats Job, Transport observé et Store backend-neutral sans posséder leurs politiques internes. Elle couvre l'admission, la découverte historique, l'hydratation `getTransaction`, la conversion RAW v1, la persistance atomique, la concurrence bornée, la frontier contiguë, le checkpoint caller-owned, l'annulation coopérative et les snapshots latest-value.
|
||||
|
||||
## Identité
|
||||
|
||||
L'identité logique d'une transaction reste :
|
||||
|
||||
```text
|
||||
(RawNetworkId, RawTransactionSignature)
|
||||
```
|
||||
|
||||
Le rôle HTTP, le provider, l'endpoint et le protocole ne font pas partie de cette identité. Ils décrivent la sélection et/ou la provenance d'acquisition.
|
||||
|
||||
Le fingerprint d'un scope est déterministe sur sa sémantique de découverte et son réseau ; il n'intègre pas `JobId`, provider, endpoint, protocole ou rôle Transport.
|
||||
|
||||
## Scopes
|
||||
|
||||
Quatre scopes sont exposés :
|
||||
|
||||
```text
|
||||
LatestAddress
|
||||
BeforeAddress
|
||||
AfterAddress
|
||||
ExplicitSignatures
|
||||
```
|
||||
|
||||
La requête borne explicitement page size, nombre de pages, nombre de candidats et concurrence d'hydratation. Les signatures explicites sont dédupliquées de façon stable à la première occurrence.
|
||||
|
||||
## Hydratation et RAW v1
|
||||
|
||||
L'hydratation passe uniquement par la voie observée de `ksp-onchain-transport-lib`.
|
||||
|
||||
Un résultat disponible produit en mémoire :
|
||||
|
||||
- un `RawTransaction` canonique ;
|
||||
- une `RawTransactionObservation` liée à la même référence logique ;
|
||||
- une provenance contenant le provider et l'endpoint réellement gagnants ;
|
||||
- un payload JSON canonique `ksp.solana.raw_transaction`, version `1` ;
|
||||
- un digest SHA-256 des bytes canoniques.
|
||||
|
||||
Un `getTransaction = null` devient `BackfillHydrationOutcome::Missing` et ne fabrique ni payload ni provenance.
|
||||
|
||||
## Persistance
|
||||
|
||||
La persistance utilise exclusivement `ksp-store-lib` avec `default-features = false` côté dépendance de crate :
|
||||
|
||||
```text
|
||||
ksp-job-backfill-lib
|
||||
-> ksp-store-lib
|
||||
-X-> backend imposé par le job
|
||||
```
|
||||
|
||||
Le chemin d'écriture est l'acquisition atomique `RawTransaction + RawTransactionObservation` en mode normal. La crate distingue insertion, déjà présent, tombstone purgé, observation nouvelle/existante, missing et conflit. Un conflit de contenu n'est jamais converti en succès idempotent.
|
||||
|
||||
## Concurrence et checkpoint
|
||||
|
||||
L'exécution maintient au plus `hydration_concurrency` candidats actifs. Les terminaisons hors ordre sont réconciliées par index stable ; le checkpoint n'avance que sur un préfixe **contigu** d'outcomes durablement sûrs.
|
||||
|
||||
`BackfillCheckpoint` est opaque et caller-owned. Il lie :
|
||||
|
||||
```text
|
||||
JobId
|
||||
scope fingerprint
|
||||
completed contiguous prefix
|
||||
private Before resume cursor
|
||||
```
|
||||
|
||||
La crate ne persiste pas elle-même ce checkpoint. Une reprise crash-safe durable nécessite donc que le caller choisisse explicitement où conserver le checkpoint retourné.
|
||||
|
||||
## Runtime et annulation
|
||||
|
||||
`BackfillJobRuntime` est un coordinator single-run. `BackfillJobHandle` peut être cloné pour :
|
||||
|
||||
- demander une annulation coopérative ;
|
||||
- lire l'état de cette demande ;
|
||||
- obtenir une source latest-value indépendante.
|
||||
|
||||
L'annulation arrête l'admission de nouveau travail et peut interrompre certaines attentes pré-Store. Une persistance Store déjà soumise est toujours drainée avant publication terminale.
|
||||
|
||||
Les snapshots exposent uniquement des compteurs, phases, boundary, checkpoint et code d'échec sûrs ; ils ne contiennent ni payload RAW, ni URL/credential Transport.
|
||||
|
||||
## Dépendances
|
||||
|
||||
Les dépendances normales sont :
|
||||
|
||||
```text
|
||||
ksp-core-lib
|
||||
ksp-job-api
|
||||
ksp-logging-lib
|
||||
ksp-onchain-transport-lib
|
||||
ksp-store-lib (default-features = false)
|
||||
futures-util
|
||||
serde_json
|
||||
sha2
|
||||
tokio (macros + sync, détail runtime privé)
|
||||
```
|
||||
|
||||
La crate ne dépend pas de Config, `ksp-store-api` directement, `ksp-store-postgres-lib` ni d'un SDK provider.
|
||||
|
||||
## Hors périmètre
|
||||
|
||||
La crate ne possède pas :
|
||||
|
||||
- application desktop ou CLI ;
|
||||
- Worker API / worker live ;
|
||||
- retry, pacing ou sélection d'endpoint Transport ;
|
||||
- backend Store concret ;
|
||||
- decoding Program ou matérialisation CORE/DECODE/SPECIALIZED ;
|
||||
- table dédiée de checkpoint ;
|
||||
- control plane Job générique.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`USAGE.md`](USAGE.md) — construction des scopes/requêtes, runtime, snapshots et reprise ;
|
||||
- [`../ksp-job-api/README.md`](../ksp-job-api/README.md) — contrats Job runtime-neutral ;
|
||||
- [`../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md`](../../docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md) — architecture durable acquisition/jobs ;
|
||||
- [`../../docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md`](../../docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md) — frontière RAW/Store.
|
||||
206
crates/ksp-job-backfill-lib/USAGE.md
Normal file
206
crates/ksp-job-backfill-lib/USAGE.md
Normal file
@@ -0,0 +1,206 @@
|
||||
<!-- file: crates/ksp-job-backfill-lib/USAGE.md -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Utilisation de ksp-job-backfill-lib
|
||||
|
||||
Cette page décrit l'utilisation durable de la façade publique de `ksp-job-backfill-lib`. Elle suppose qu'un caller a déjà construit un `HttpTransportPool` et un `Store` compatibles avec le même réseau logique.
|
||||
|
||||
## Construire une signature et un scope
|
||||
|
||||
```rust
|
||||
let anchor = match ksp_job_backfill_lib::BackfillSignature::new(
|
||||
"1111111111111111111111111111111111111111111111111111111111111111",
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
let address = ksp_core_lib::Pubkey::new_from_array([7_u8; 32]);
|
||||
let scope = ksp_job_backfill_lib::BackfillScope::before_address(address, anchor);
|
||||
```
|
||||
|
||||
Les autres formes sont :
|
||||
|
||||
```rust
|
||||
let latest = ksp_job_backfill_lib::BackfillScope::latest_address(address);
|
||||
let before = ksp_job_backfill_lib::BackfillScope::before_address(address, anchor.clone());
|
||||
let after = ksp_job_backfill_lib::BackfillScope::after_address(address, anchor.clone());
|
||||
let explicit = ksp_job_backfill_lib::BackfillScope::explicit_signatures(vec![anchor]);
|
||||
```
|
||||
|
||||
`ExplicitSignatures` déduplique la liste en conservant la première occurrence. Les scopes address utilisent `getSignaturesForAddress` ; le scope explicite n'effectue aucune découverte address.
|
||||
|
||||
## Construire une requête bornée
|
||||
|
||||
```rust
|
||||
fn request(scope: ksp_job_backfill_lib::BackfillScope) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillRequest> {
|
||||
let job_id = match ksp_job_api::JobId::new("raw-backfill-0001") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
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 role = ksp_onchain_transport_lib::HttpRoleName::new("historical");
|
||||
|
||||
return ksp_job_backfill_lib::BackfillRequest::new(
|
||||
job_id,
|
||||
network,
|
||||
role,
|
||||
ksp_job_backfill_lib::BackfillCommitment::Finalized,
|
||||
scope,
|
||||
500,
|
||||
20,
|
||||
5_000,
|
||||
16,
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Bornes publiques :
|
||||
|
||||
```text
|
||||
page_size 1 ..= 1_000
|
||||
max_pages 1 ..= 10_000
|
||||
max_candidates 1 ..= 10_000
|
||||
hydration_concurrency 1 ..= 64
|
||||
```
|
||||
|
||||
Le `min_context_slot` n'est pas admis pour `ExplicitSignatures`.
|
||||
|
||||
## Comprendre le fingerprint
|
||||
|
||||
```rust
|
||||
let fingerprint = request.scope_fingerprint();
|
||||
let bytes: &[u8; 32] = fingerprint.as_bytes();
|
||||
```
|
||||
|
||||
Le fingerprint représente le scope sémantique et le réseau. Il ne change pas uniquement parce que le rôle Transport, le provider ou l'endpoint d'acquisition change.
|
||||
|
||||
Ne pas utiliser le fingerprint comme identité de transaction : l'identité durable reste `(network, signature)`.
|
||||
|
||||
## Exécuter le runtime complet
|
||||
|
||||
```rust
|
||||
async fn run_backfill(
|
||||
request: ksp_job_backfill_lib::BackfillRequest,
|
||||
transport: &ksp_onchain_transport_lib::HttpTransportPool,
|
||||
store: &ksp_store_lib::Store,
|
||||
) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillJobSnapshot> {
|
||||
let runtime = match ksp_job_backfill_lib::BackfillJobRuntime::new(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return runtime.run(transport, store).await;
|
||||
}
|
||||
```
|
||||
|
||||
Le Store et la requête doivent cibler le même `RawNetworkId`. Un mismatch est rejeté avant l'écriture.
|
||||
|
||||
## Observer la progression
|
||||
|
||||
Obtenir le handle avant de déplacer le runtime dans `run` :
|
||||
|
||||
```rust
|
||||
let runtime = ksp_job_backfill_lib::BackfillJobRuntime::new(request)?;
|
||||
let handle = runtime.handle();
|
||||
let snapshots = handle.snapshots();
|
||||
|
||||
let current = ksp_job_api::JobSnapshotSource::current(&snapshots);
|
||||
let observed = current.sequence();
|
||||
let newer = ksp_job_api::JobSnapshotSource::wait_for_change(&snapshots, observed).await;
|
||||
|
||||
assert!(newer.sequence().is_after(observed));
|
||||
```
|
||||
|
||||
Chaque listener peut cloner sa propre `BackfillSnapshotSource`. La source est latest-value : plusieurs mises à jour intermédiaires peuvent être coalescées, mais la valeur retournée est toujours un snapshot complet.
|
||||
|
||||
Les compteurs publics du snapshot couvrent notamment :
|
||||
|
||||
```text
|
||||
candidates_selected / admitted / finished
|
||||
entities_inserted / existing / purged
|
||||
observations_inserted / existing
|
||||
missing / conflicts / holes
|
||||
maximum_in_flight
|
||||
contiguous_completed
|
||||
checkpoint
|
||||
failure_code
|
||||
```
|
||||
|
||||
## Demander une annulation
|
||||
|
||||
```rust
|
||||
let accepted = handle.cancel();
|
||||
if accepted {
|
||||
assert!(handle.is_cancellation_requested());
|
||||
}
|
||||
```
|
||||
|
||||
L'annulation est coopérative. Elle peut stopper de nouvelles admissions et certaines attentes avant persistance. Une écriture Store déjà soumise est drainée ; le caller ne doit donc pas supposer qu'une demande d'annulation rend immédiatement toutes les opérations in-flight inexistantes.
|
||||
|
||||
Une demande faite après publication terminale est rejetée (`false`).
|
||||
|
||||
## Reprendre avec un checkpoint
|
||||
|
||||
Le snapshot ou le batch d'exécution peut fournir un `BackfillCheckpoint` sûr. Pour une reprise contrôlée :
|
||||
|
||||
```rust
|
||||
let resumed = match request.with_checkpoint(checkpoint) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
```
|
||||
|
||||
Le checkpoint doit appartenir au même `JobId` et au même fingerprint de scope.
|
||||
|
||||
Sémantique de reprise :
|
||||
|
||||
```text
|
||||
LatestAddress repart du latest courant ; frontier contiguë conservée dans le nouveau run
|
||||
BeforeAddress reprend avec le dernier cursor before prouvé
|
||||
AfterAddress rejoue le scope et saute seulement le préfixe contigu prouvé
|
||||
ExplicitSignatures rejoue la liste et saute seulement le préfixe contigu prouvé
|
||||
```
|
||||
|
||||
`BackfillCheckpoint` n'est pas persisté automatiquement. Si le caller exige une reprise après crash/process restart, il doit stocker ce checkpoint dans une surface durable appropriée puis le réinjecter explicitement.
|
||||
|
||||
## Utiliser les primitives séparément
|
||||
|
||||
La façade expose aussi les étapes pour des compositions/tests spécialisés :
|
||||
|
||||
```text
|
||||
discover_backfill_candidates
|
||||
hydrate_backfill_candidate
|
||||
persist_backfill_hydration
|
||||
execute_backfill_discovery
|
||||
```
|
||||
|
||||
`hydrate_backfill_candidate` ne persiste rien. `persist_backfill_hydration` n'effectue aucun appel Transport. `execute_backfill_discovery` combine hydratation/persistance sur un résultat de découverte déjà validé.
|
||||
|
||||
Pour un flux applicatif normal qui veut lifecycle + snapshots + annulation, préférer `BackfillJobRuntime::run`.
|
||||
|
||||
## Interpréter les outcomes de persistance
|
||||
|
||||
Les outcomes distinguent explicitement :
|
||||
|
||||
```text
|
||||
entity: Inserted | AlreadyPresent | SkippedPurged | Conflict
|
||||
observation: Inserted | AlreadyPresent | NotRecorded | NotApplicable
|
||||
```
|
||||
|
||||
`Missing` ne provoque aucune écriture Store. Un conflit de contenu reste observable comme conflit et ne doit pas être traité comme une relance idempotente réussie.
|
||||
|
||||
## Frontières à respecter
|
||||
|
||||
Le caller ne doit pas :
|
||||
|
||||
- pré-lire le Store pour décider s'il faut hydrater une transaction ;
|
||||
- appeler directement `ksp-store-postgres-lib` depuis le job ;
|
||||
- ajouter une politique de retry/pacing qui concurrence Transport ;
|
||||
- utiliser provider/endpoint comme identité transactionnelle ;
|
||||
- inventer une provenance pour `getTransaction = null` ;
|
||||
- interpréter un checkpoint caller-owned comme une garantie de persistence crash-safe automatique ;
|
||||
- utiliser le job RAW comme decoder Program ou processor CORE.
|
||||
Reference in New Issue
Block a user