# 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 { 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 { 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.