Files
khadhroony-solana-project/crates/ksp-job-backfill-lib/USAGE.md
2026-09-01 17:42:24 +02:00

7.5 KiB

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

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 :

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

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 :

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

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

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 :

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 :

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

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 :

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 :

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 :

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 :

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.