Files
khadhroony-solana-project/crates/ksp-worker-raw-transaction-ingest-lib/README.md
2026-09-10 17:33:02 +02:00

16 KiB

ksp-worker-raw-transaction-ingest-lib

ksp-worker-raw-transaction-ingest-lib est le Worker concret KSP chargé de l'alimentation continue de la couche RAW Transaction.

La crate possède deux niveaux publics complémentaires :

RawTransactionIngestWorker::start
    -> fondation source-neutral, sans source productive

RawTransactionIngestWorker::start_with_runtime_resources
    -> même runtime + 1..32 sources productives supervisées simultanément
       Yellowstone, WS standard logsSubscribe, WS standard blockSubscribe, Helius transactionSubscribe et/ou HTTP block polling
       + hydration HTTP getTransaction lorsque la source produit une référence

Le Worker reste indépendant de Config et de tout backend Store physique. Le caller compose les ressources Transport et Store, puis les remet à la crate par ses façades publiques.

Pipeline productif actuel

Les verticales live productives sont :

Yellowstone standard subscribe
    -> Transaction / TransactionStatus / Block
    -> signal source-neutral (network, signature, slot, commitment, provenance sûre)

Solana standard WS logsSubscribe
    -> context.slot + signature
    -> signal source-neutral (network, signature, slot, commitment, provenance sûre)
    -> coalescence bornée par (network, signature, commitment)
    -> HTTP getTransaction observed, Base64, maxSupportedTransactionVersion=1

Solana standard WS blockSubscribe
    -> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
    -> qualification explicite Legacy / V0 / V1
    -> matériau Common RAW direct par transaction du bloc

Helius transactionSubscribe
    -> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
    -> signature + slot + transactionIndex uniquement dans le signal Worker
    -> coalescence bornée par (network, signature, commitment)
    -> HTTP getTransaction observed avant Common RAW

Solana HTTP live block polling
    -> getSlot borne le run courant
    -> getBlocksWithLimit découvre les blocs live
    -> getBlock observed matérialise Full/Base64 Legacy/V0/V1
    -> matériau Common RAW direct par transaction du bloc

les chemins productifs
    -> ksp-raw-transaction-lib
    -> admission centrale bornée
    -> ksp-store-lib
    -> RawTransaction + RawTransactionObservation

BlockMeta et Slot ne produisent pas de RAW directement. Ils servent uniquement à la projection de continuité du run.

La qualification reste conservative : même lorsqu'une update Yellowstone contient une transaction complète côté protobuf, le Worker hydrate actuellement les signaux transactionnels par HTTP getTransaction avant de construire le RAW canonique. Il n'existe donc pas de second canonicaliseur Yellowstone.

Contrat de source Yellowstone + HTTP

RawTransactionIngestYellowstoneSource::new reçoit :

YellowstoneGrpcChannel
YellowstoneSubscribeRequest
HttpTransportPool
HttpRoleName d'hydration

La construction est sans I/O et refuse notamment :

  • une requête Yellowstone invalide ;
  • l'absence de famille porteuse d'ingestion ;
  • un commitment Processed ou implicite ;
  • un réseau Yellowstone non représentable ;
  • une provenance provider/endpoint non représentable ;
  • l'absence d'une route HTTP compatible pour getTransaction sur le même réseau.

Le runtime-resource aggregate public accepte une collection validée de 1 à 32 sources logiques et les démarre simultanément sous un supervisor privé. La collection est validée entièrement avant spawn ; aucun sous-ensemble silencieux, source primaire implicite ou standby n'est choisi. La collection interne, les source_key, les URLs, les filtres et les clients inférieurs ne sont pas exposés.

Le supervisor possède toutes les tâches source. Une source qui échoue est terminale pour le Worker et déclenche l'arrêt coopératif puis le join des autres sources, car cette release ne suppose aucune équivalence de coverage. Une fermeture propre d'une source alors que le Worker n'est pas en arrêt est également traitée comme une perte de source configurée et devient terminale.

Chaque source conserve provisoirement ses mécanismes de production/hydration existants. Un inventaire privé source_key -> latest processing/source state, borné à 32 entrées, agrège la projection run-local. La frontier agrégée reste conservative : elle n'expose un processing_frontier_slot que lorsque toutes les sources en possèdent un, choisit le minimum des frontiers connus et le plus ancien pending. La convergence/coalescence cross-source des mêmes transactions reste une tranche séparée.

Contrat de source Standard Logs + HTTP

RawTransactionIngestStandardLogsSource::new reçoit :

WsEndpointSettings kind solana_standard
SolanaLogsSubscribeFilter
SolanaCommitment Confirmed ou Finalized
HttpTransportPool
HttpRoleName d'hydration

La construction est sans I/O. Elle refuse un endpoint WS invalide ou non standard, Processed, un réseau/provenance non représentable et l'absence de route HTTP getTransaction compatible sur le même réseau. Le filtre All, AllWithVotes ou Mentions(pubkey) participe uniquement à une empreinte privée ; le pubkey d'un filtre Mentions n'est pas recopié dans Debug, snapshot ou provenance textuelle.

Au runtime, le Worker ouvre SolanaStandardWsSession::connect, puis logs_subscribe. Les lignes de logs et err restent dans Transport et ne sont jamais stockées dans le signal Worker. Seuls context.slot et signature sont projetés vers l'hydration commune. Reconnect, resubscribe et backpressure de la subscription restent possédés par Transport.

Contrat de source Standard Block direct RAW

RawTransactionIngestStandardBlockSource::new reçoit :

WsEndpointSettings kind solana_standard
SolanaBlockSubscribeFilter
SolanaCommitment Confirmed ou Finalized

Le runtime ouvre la SolanaStandardWsSession existante et demande exactement Base64, Full, maxSupportedTransactionVersion = 1 et showRewards = false. Aucun HttpTransportPool n'est attaché à cette source : les transactions dont la version est explicitement Legacy, 0 ou 1 sont transformées directement en RawTransactionMaterial à partir du wire Base64 du bloc, avec signature embarquée, slot, block time, meta, version et index de transaction.

La qualification est fermée : une version omise/nulle ou supérieure à 1, une transaction non Base64, block: null, une erreur distante de notification, un slot de contexte incohérent ou un champ transactions absent/nul termine la source par une erreur sûre. Aucun de ces cas n'est converti en bloc vide, en succès silencieux ou en progression artificielle de frontier.

Pour un bloc multi-transaction, le slot n'est projeté settled qu'après l'admission réussie de toutes ses transactions. Cette voie RAW-direct n'incrémente pas hydration_pending; un stop ou une erreur au milieu du bloc ne produit aucune progression artificielle. Reconnect, resubscribe et backpressure WebSocket restent possédés par Transport.

Contrat de source Helius Transaction + HTTP

RawTransactionIngestHeliusTransactionSource::new reçoit :

WsEndpointSettings kind helius_laserstream
HeliusTransactionSubscribeFilter
SolanaCommitment Confirmed ou Finalized
HttpTransportPool
HttpRoleName d'hydration

La construction est sans I/O. Elle réutilise le contrat Transport existant et impose Full, Base64, showRewards = false et maxSupportedTransactionVersion = 1. Le Worker ne lit pas Config, ne lit pas KSP_SECRET_HELIUS_API_KEY et ne code aucun tier provider ; l'URL résolue et le credential restent dans l'endpoint Transport fourni par le caller.

Au runtime, HeliusLaserStreamWsSession::connect puis transaction_subscribe sont utilisés. Seule une notification Full conforme au mode demandé est admise ; une forme Signature, Unknown ou future devient une faute source sûre. Le payload Helius transaction n'est jamais copié dans l'état Worker : la projection conserve uniquement signature, slot et transaction index, puis réutilise le coordinateur d'hydration commun getTransaction observed.

La clé logique Helius inclut réseau, identités provider/endpoint sûres, commitment et empreinte privée du filtre. Les listes de pubkeys du filtre sont normalisées avant hash afin que leur ordre ne crée pas artificiellement deux sources logiques ; le rôle HTTP d'hydration reste exclu de l'identité live.

Contrat de source HTTP Block Polling

RawTransactionIngestHttpBlockPollingSource::new reçoit :

HttpTransportPool
HttpRoleName de polling
SolanaCommitment Confirmed ou Finalized

La construction est sans I/O et vérifie que le rôle HTTP possède, sur un seul réseau, les capacités getSlot, getBlocksWithLimit et getBlock. La cadence est bornée entre 100 ms et 30 s, avec 1 s par défaut ; la découverte est bornée entre 1 et 1024 blocs par cycle, avec 128 par défaut. Ces réglages de cadence ne font pas partie de l'identité logique de la source.

Au démarrage, le premier getSlot fixe la borne inférieure du run. Le Worker ne demande aucun slot antérieur. Chaque cycle relit le tip, découvre les blocs disponibles avec getBlocksWithLimit, puis matérialise chaque slot listé par getBlock observed en Full + Base64 + maxSupportedTransactionVersion = 1 + showRewards = false. Seules les transactions Legacy/V0/V1 explicitement qualifiées entrent directement dans Common RAW.

Un slot listé dont getBlock retourne null reste la tête de reprise du cycle suivant ; il n'est ni considéré vide ni marqué settled. Les slots absents de la liste de découverte sont traités comme non produits/skipped pour ce run. Le slot n'est settled qu'après admission réussie de toutes ses transactions. Le polling reste run-local : aucun checkpoint durable, aucun scan avant la borne initiale et aucun Backfill implicite ne sont créés. Les retries/reroutages HTTP restent possédés par ksp-onchain-transport-lib.

Runtime et lifecycle

Le Worker s'exécute sur le runtime Tokio courant du caller. Il ne crée pas de runtime global et n'expose aucun JoinHandle public.

RawTransactionIngestHandle permet de :

  • demander un stop coopératif et idempotent ;
  • lire une source de snapshots concrets latest-value ;
  • utiliser la même source via WorkerSnapshotSource ;
  • attendre le terminal après drain et join des tâches possédées.

Le shutdown est borné par shutdown_drain_timeout. Le supervisor multi-source relaie le stop à toutes les sources et les rejoint avant de rendre son résultat au supervisor Worker ; les tâches source, hydration et persistence possédées sont ensuite drainées ou abort+join avant publication terminale. L'abandon terminal d'une hydration retire son pending run-local sans le convertir artificiellement en travail settled.

Admission, coalescence et backpressure

La queue centrale est un tokio::sync::mpsc privé borné par admission_queue_capacity. Les sources internes subissent la backpressure ; aucune queue non bornée ni silent drop n'est autorisé.

Le Worker possède un coordinateur d'hydration source-neutral borné, réutilisé par Yellowstone, Standard Logs et Helius Transaction. Standard Block n'entre pas dans ce coordinateur lorsqu'une transaction est direct-qualified :

in-flight hydration <= persistence_concurrency
pending source signals <= admission_queue_capacity

Les signaux partageant le même (network, signature, commitment) sont coalescés avant le fan-out HTTP. Les provenances utiles restent néanmoins conservées pour les ingress produits après hydration.

Les retries/reroutages HTTP appartiennent à ksp-onchain-transport-lib. Le Worker ne possède pas une seconde boucle de retry autour de getTransaction.

Processing frontier run-local

Le snapshot expose :

hydration_pending
processing_frontier_slot
oldest_pending_slot

Cette frontier mesure uniquement le traitement des signaux réellement observés pendant le run courant. Elle n'est ni un checkpoint durable, ni une preuve de complétude blockchain, ni un curseur de Backfill.

Un signal transactionnel devient pending après validation de sa clé d'hydration et insertion dans le coordinateur. Il devient settled pour la source lorsque :

getTransaction -> Missing
ou
getTransaction -> Available puis ingress envoyé avec succès vers l'admission centrale

Un BlockMeta ou Slot continuity-only est settled localement sans produire de RAW. La frontier n'avance jamais à travers le plus ancien pending connu.

Reconnect, replay et continuité

Le reconnect/replay Yellowstone appartient à Transport. Le Worker n'écrit pas from_slot et n'interprète pas directement SubscribeReplayInfo.

Le snapshot Worker projette uniquement des informations sûres :

source_state
source_reconnect_total
source_replay_attempt_total
source_continuity_gap_total

RawTransactionIngestSourceState distingue :

Active
Reconnecting
Closing
Closed
Failed

Un replay attempt n'est pas une preuve de replay réussi ni de continuité parfaite. Si Transport augmente son compteur de continuity gap parce que la borne de rétention prouve que le slot demandé n'est plus rejouable, le Worker classe la source en failure et termine avec worker_raw_transaction_ingest.source_failed.

Le Worker ne lance alors ni Job Backfill ni campagne historique automatique.

Persistence Store

La persistance passe exclusivement par ksp-store-lib avec default-features = false. Aucun backend physique n'est importé directement.

L'écriture utilise le mode normal atomique RawTransaction + RawTransactionObservation. Les outcomes distingués incluent :

entity inserted
entity already present
entity skipped purged
observation inserted
observation already present
observation not recorded for purged entity
content conflict
store failure

Un content conflict est terminal et n'est jamais converti en succès idempotent.

Snapshots et erreurs

RawTransactionIngestSnapshotSource est latest-value : les lecteurs peuvent rater des transitions intermédiaires mais récupèrent toujours la dernière projection complète et monotone.

Les codes Worker publics sont :

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 et Debug ne recopient pas de payload RAW, signature, URL, credential, filtre provider, texte backend/provider arbitraire ou client inférieur.

Dépendances

Les dépendances normales sont exactement :

ksp-core-lib
ksp-logging-lib
ksp-onchain-transport-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, reqwest, tonic, yellowstone-grpc-proto ou Tauri.

Hors périmètre

La verticale actuelle ne possède pas :

  • plusieurs sources productives simultanées dans RawTransactionIngestRuntimeResources ;
  • sélection Config interne au Worker ;
  • checkpoint persistent de processing frontier ;
  • campagne de réparation historique automatique ;
  • application Desk ou process autonome ;
  • décodage STRUCTURAL/DECODED/DOMAIN.

Les futures extensions doivent conserver la séparation avec ksp-job-backfill-lib et réutiliser les mêmes contrats Common RAW/Store.

Documentation