411 lines
12 KiB
Markdown
411 lines
12 KiB
Markdown
<!-- file: docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md -->
|
||
<!-- version: 7 -->
|
||
|
||
# Data, Materialization et Store
|
||
|
||
## Objet
|
||
|
||
Ce document définit la chaîne durable KSP, la responsabilité du Store et les frontières de replay.
|
||
|
||
La nomenclature canonique est désormais :
|
||
|
||
```text
|
||
RAW -> STRUCTURAL -> DECODED -> DOMAIN
|
||
```
|
||
|
||
Les aliases D1–D4 restent utilisés pour les niveaux persistés :
|
||
|
||
```text
|
||
D1 = RAW
|
||
D2 = STRUCTURAL
|
||
D3 = DECODED / matérialisation générique décodée
|
||
D4 = DOMAIN
|
||
```
|
||
|
||
Le nom de couche historique `CORE` est abandonné pour D2 parce qu'il confondait la fondation commune `ksp-core-lib` avec une opération de décomposition structurelle du Store. `Core` reste inchangé lorsqu'il désigne la crate fondamentale ou un nom propre comme « Solana Core Programs ».
|
||
|
||
Cette clarification remplace l'ancienne interprétation où D1 -> D2 pouvait déjà dépendre de `ksp-program-api`. **RAW et STRUCTURAL sont indépendants de tout decoder Program.**
|
||
|
||
## Principes structurants
|
||
|
||
- le Store persiste des contrats de données ; il ne possède ni transport, ni decoder, ni materializer ;
|
||
- chaque frontière durable peut être rejouée indépendamment ;
|
||
- les données de provenance/versioning permettent de savoir quel processor a produit quel output ;
|
||
- les couches dérivées ne rendent jamais obligatoire une nouvelle acquisition réseau lorsque l'input durable nécessaire existe déjà ;
|
||
- D4 privilégie les faits métier génériques lorsqu'une normalisation inter-protocoles est pertinente.
|
||
|
||
## D1 — RAW
|
||
|
||
### Mission
|
||
|
||
RAW conserve l'acquisition suffisamment fidèlement pour reconstruire STRUCTURAL sans redemander la donnée au provider lorsqu'elle a déjà été capturée.
|
||
|
||
Le transport peut normaliser plusieurs providers vers un modèle KSP homogène, mais D1 doit rester lossless pour les besoins de replay couverts.
|
||
|
||
D1 ne décode aucun programme Solana/SPL/Metaplex/DEX.
|
||
|
||
### Frontière Transport -> RAW
|
||
|
||
```text
|
||
HTTP / WS / gRPC / provider
|
||
|
|
||
v
|
||
ksp-onchain-transport-lib
|
||
|
|
||
v
|
||
conversion explicite
|
||
|
|
||
v
|
||
ksp-store-api RAW DTO
|
||
|
|
||
v
|
||
ksp-store-lib
|
||
```
|
||
|
||
`ksp-onchain-transport-lib` ne dépend ni de `ksp-store-api` ni de `ksp-store-lib`.
|
||
|
||
### Provenance RAW
|
||
|
||
Selon la catégorie, D1 doit pouvoir conserver notamment :
|
||
|
||
- cluster/network ;
|
||
- slot/signature/pubkey/identité blockchain applicable ;
|
||
- payload replayable ;
|
||
- provider ;
|
||
- endpoint/source/transport ;
|
||
- rôle d'acquisition : live, backfill, import ;
|
||
- instant d'observation ;
|
||
- instant de persistence ;
|
||
- identité/hash d'idempotence ;
|
||
- cursor/page/range/checkpoint lorsque pertinent.
|
||
|
||
## D2 — STRUCTURAL
|
||
|
||
### Mission
|
||
|
||
STRUCTURAL est une **normalisation canonique générique de la blockchain Solana**.
|
||
|
||
Cette couche doit fonctionner même si `ksp-program-api` et `ksp-program-lib` ne sont pas encore capables de décoder le moindre programme métier.
|
||
|
||
Exemples de faits STRUCTURAL candidats :
|
||
|
||
- slots ;
|
||
- blocks et block metadata ;
|
||
- signatures ;
|
||
- transactions ;
|
||
- messages legacy/versioned ;
|
||
- account keys et address lookups ;
|
||
- comptes et états bruts structurés génériquement ;
|
||
- instructions top-level brutes ;
|
||
- instructions CPI brutes ;
|
||
- logs ;
|
||
- transaction meta ;
|
||
- balances/fees/rewards lorsqu'ils appartiennent au contrat blockchain générique ;
|
||
- return data brute ;
|
||
- relations structurelles transaction/message/instruction/account.
|
||
|
||
Un fait STRUCTURAL peut contenir un `program_id`, des bytes et des indexes sans savoir que l'instruction représente un `Transfer`, un `Swap` ou une mutation Metadata.
|
||
|
||
### Frontière RAW -> STRUCTURAL
|
||
|
||
```text
|
||
D1 RAW
|
||
|
|
||
v
|
||
normalisation Solana générique
|
||
|
|
||
v
|
||
D2 STRUCTURAL
|
||
```
|
||
|
||
Interdictions :
|
||
|
||
```text
|
||
RAW -> STRUCTURAL -X-> ksp-program-api
|
||
RAW -> STRUCTURAL -X-> ksp-program-lib
|
||
RAW -> STRUCTURAL -X-> ksp-materializer-api
|
||
```
|
||
|
||
Les codecs/wires génériques nécessaires à la structure Solana peuvent provenir de `ksp-interface-lib` lorsqu'ils appartiennent à la façade wire officielle, sans transformer cette étape en décodage Program.
|
||
|
||
### Provenance STRUCTURAL
|
||
|
||
D2 doit pouvoir relier chaque résultat à :
|
||
|
||
- son input D1 ;
|
||
- l'identité/version du normalizer STRUCTURAL ;
|
||
- un hash logique d'input ;
|
||
- l'instant de processing/persistence ;
|
||
- son état de processing durable lorsque nécessaire.
|
||
|
||
## D3 — DECODED / matérialisation générique
|
||
|
||
### Mission
|
||
|
||
DECODED commence lorsque KSP interprète un `program_id`, un layout d'instruction, un compte ou un événement selon un contrat Program/protocole.
|
||
|
||
La progression logique d'un groupe est :
|
||
|
||
```text
|
||
STRUCTURAL
|
||
|
|
||
v
|
||
decoder Program
|
||
|
|
||
v
|
||
decoded facts
|
||
|
|
||
v
|
||
materialisation générique / journal durable
|
||
|
|
||
v
|
||
D3 DECODED
|
||
```
|
||
|
||
D3 conserve l'équivalent conceptuel obligatoire du journal générique de matérialisation de bot3 (`k_sol_mat_outputs`), sans imposer son ancien schéma ou son nom physique.
|
||
|
||
Le journal doit pouvoir répondre au minimum :
|
||
|
||
```text
|
||
quel input STRUCTURAL ?
|
||
quel program/decoder ?
|
||
quelle version ?
|
||
quel materializer ?
|
||
quelle version ?
|
||
quel output logique ?
|
||
quel type/domaine ?
|
||
quel hash ?
|
||
quel instant ?
|
||
quel état/superseded/failed/replay ?
|
||
```
|
||
|
||
Les types exacts de decoded facts et du journal sont décidés lorsque les premiers vertical slices Program existent.
|
||
|
||
### Frontière STRUCTURAL -> DECODED
|
||
|
||
```text
|
||
D2 STRUCTURAL
|
||
|
|
||
v
|
||
ksp-program-api implementation
|
||
|
|
||
v
|
||
decoded output
|
||
|
|
||
v
|
||
ksp-materializer-api implementation
|
||
|
|
||
v
|
||
D3 journal / decoded materialization
|
||
```
|
||
|
||
Les implémentations officielles pourront provenir de `ksp-program-lib` et `ksp-materializer-lib`; des implémentations externes compatibles restent possibles.
|
||
|
||
Program et Materializer ne dépendent pas du backend Store.
|
||
|
||
## D4 — DOMAIN
|
||
|
||
### Mission
|
||
|
||
DOMAIN expose des projections queryables utiles aux applications, analyses et futurs modèles ML.
|
||
|
||
Exemples :
|
||
|
||
- token/asset state ;
|
||
- metadata canonique d'asset ;
|
||
- pools/markets ;
|
||
- reserves/liquidity ;
|
||
- positions ;
|
||
- swaps/trades ;
|
||
- fees ;
|
||
- observations de prix ;
|
||
- OHLC/candles ;
|
||
- routes et legs ;
|
||
- faits trading-adjacent ;
|
||
- projections d'autres domaines futurs.
|
||
|
||
### Faits métier génériques
|
||
|
||
Les projections de trading ne sont pas séparées automatiquement par protocole.
|
||
|
||
Préférer lorsque possible :
|
||
|
||
```text
|
||
liquidity_pools
|
||
trades
|
||
positions
|
||
price_observations
|
||
ohlc
|
||
routes
|
||
route_legs
|
||
```
|
||
|
||
plutôt que :
|
||
|
||
```text
|
||
meteora_trades
|
||
raydium_trades
|
||
orca_trades
|
||
pump_trades
|
||
```
|
||
|
||
Les champs réellement protocol-specific peuvent être conservés dans une extension ou une projection dédiée uniquement lorsqu'un besoin de requête/invariant le justifie.
|
||
|
||
### Metadata
|
||
|
||
La direction reste :
|
||
|
||
- projection canonique commune pour metadata d'assets/tokens alimentée par Metaplex Token Metadata et Token-2022 Metadata ;
|
||
- SPM reste distinct et sera redéveloppé plus tard avec le décodage généraliste.
|
||
|
||
### OHLC
|
||
|
||
Les candles sont des projections DOMAIN calculées à partir des trades/price observations persistés.
|
||
|
||
Une application marché lit les OHLC matérialisés ; elle ne reparcourt pas toutes les transactions pour reconstruire les candles à chaque affichage.
|
||
|
||
## Vertical slices Program
|
||
|
||
RAW et STRUCTURAL sont développés horizontalement.
|
||
|
||
À partir de DECODED, la progression est verticale par groupe :
|
||
|
||
```text
|
||
wire
|
||
-> decode
|
||
-> generic materialization / D3
|
||
-> specialized projection / D4 si utile
|
||
-> execution preparation
|
||
-> execution policy
|
||
-> execution
|
||
-> Devnet scenarios / validation
|
||
```
|
||
|
||
Un groupe doit atteindre une cohérence verticale suffisante avant que le groupe suivant devienne prioritaire.
|
||
|
||
Les composants satellites nécessaires à un protocole appartiennent à son groupe : Pump fees avec Pump, Meteora vaults avec Meteora, etc.
|
||
|
||
## `ksp-store-api`
|
||
|
||
`ksp-store-api` est backend-agnostic et porte les contrats nécessaires aux consommateurs.
|
||
|
||
La première implementation `0.3.1` est volontairement **RAW-only** : elle ne crée pas prématurément les contrats physiques D2/D3/D4.
|
||
|
||
Les surfaces STRUCTURAL/DECODED/DOMAIN sont ajoutées quand leurs couches sont réellement ouvertes.
|
||
|
||
## `ksp-store-lib` et backends physiques
|
||
|
||
`ksp-store-lib` est la façade/runtime Store commune derrière `ksp-store-api`. Il sélectionne uniquement les backends compilés par ses features, convertit ses settings KSP-owned vers le backend retenu, expose le lifecycle commun et masque les objets physiques du moteur.
|
||
|
||
Il possède notamment :
|
||
|
||
- identité et sélection de backend côté façade ;
|
||
- settings Store publics KSP-owned indépendants de Config ;
|
||
- lifecycle commun `open` / `close` ;
|
||
- dispatch vers le backend compilé ;
|
||
- mapping des diagnostics/health backend vers une projection portable lorsque cette surface est justifiée ;
|
||
- réexport de la surface `ksp-store-api` utile aux consumers.
|
||
|
||
Le backend PostgreSQL officiel appartient à `ksp-store-postgres-lib`. Cette crate dépend de `ksp-store-api`, ne dépend jamais de `ksp-store-lib` et possède seule :
|
||
|
||
- `tokio-postgres` et le pool PostgreSQL ;
|
||
- le connecteur TLS PostgreSQL ;
|
||
- SQL et statements physiques ;
|
||
- transactions PostgreSQL ;
|
||
- migrations/bootstrap et metadata de schéma ;
|
||
- mapping rows/backend ;
|
||
- détails de cursorisation physique ;
|
||
- diagnostics PostgreSQL internes.
|
||
|
||
`ksp-store-lib` et les crates backend ne possèdent pas :
|
||
|
||
- transport réseau d’acquisition ;
|
||
- decoder Program ;
|
||
- materializer ;
|
||
- orchestration de worker/job ;
|
||
- politique de batch, backlog, priorité ou retry de processing.
|
||
|
||
La pagination/cursorisation reste un contrat de navigation Store. Une limite demandée par l’appelant peut être validée pour sa forme/sécurité, mais aucun plafond métier global arbitraire ni batch-size de worker n’est introduit par le runtime Store.
|
||
|
||
### Navigation machine et inspection humaine
|
||
|
||
Deux contrats complémentaires coexistent sans collision :
|
||
|
||
```text
|
||
workers / jobs / replay -> RawPageRequest + RawPageCursor -> keyset
|
||
inspection UI -> RawInspectionPageRequest -> offset + limit + counts exacts
|
||
```
|
||
|
||
La voie cursor/keyset reste canonique pour la navigation machine et les replays : le cursor est opaque, lié au contexte de query et ne dépend pas d'un `OFFSET` profond.
|
||
|
||
La voie `RawInspectionPageRequest` est backend-neutral mais conçue pour une inspection humaine random-access. Elle peut demander des counts exacts et un offset absolu ; le backend PostgreSQL peut implémenter cette capability avec `COUNT` et `OFFSET` privés. Ce choix ne réécrit jamais les statements keyset historiques.
|
||
|
||
`ksp-app-store-desk` est le consumer actuel de cette voie d'inspection. DataTables possède l'unique pager visible et l'application ne reçoit aucun cursor Store. Les summaries d'inspection restent sans payload/data RAW ; un détail explicite peut recharger une seule entité avec une projection applicative bornée.
|
||
|
||
## `ksp-materializer-api` et `ksp-materializer-lib`
|
||
|
||
Ils sont introduits seulement lorsque le premier groupe DECODED démontre le contrat réel.
|
||
|
||
`ksp-materializer-api` porte les contrats extensibles ; `ksp-materializer-lib` contient les implementations officielles communes.
|
||
|
||
Une projection très locale/spécifique peut rester dans son groupe si la création d'une implémentation commune séparée n'apporte pas de réutilisation réelle.
|
||
|
||
## Replay
|
||
|
||
Les frontières durables restent replayables indépendamment :
|
||
|
||
```text
|
||
RAW -> STRUCTURAL
|
||
STRUCTURAL -> DECODED
|
||
DECODED -> DOMAIN
|
||
```
|
||
|
||
Un replay d'une couche dérivée ne doit pas refaire arbitrairement les couches précédentes.
|
||
|
||
## Notifications persistées
|
||
|
||
Le Store reste source de vérité du backlog.
|
||
|
||
Les notifications ne sont qu'un wake-up : elles peuvent être perdues ou dupliquées.
|
||
|
||
Ordre :
|
||
|
||
```text
|
||
persist
|
||
commit
|
||
notify
|
||
```
|
||
|
||
Le consumer reconstruit toujours son backlog depuis le Store avec les versions de processor et les marqueurs d'idempotence.
|
||
|
||
## Acquisition live et backfill
|
||
|
||
Live et backfill alimentent la même frontière RAW :
|
||
|
||
```text
|
||
live worker ----\
|
||
+--> RAW persistence
|
||
backfill job ----/
|
||
```
|
||
|
||
Ils ne dupliquent pas le contrat durable.
|
||
|
||
## Stabilité
|
||
|
||
La stabilité cible est différente selon la couche :
|
||
|
||
- RAW : fortement stable après mise en production ;
|
||
- STRUCTURAL : fortement stable après validation de la normalisation Solana générique ;
|
||
- DECODED : extensible par nouveaux Program/versions ;
|
||
- DOMAIN : plus évolutif selon les besoins de query, trading et analytics.
|
||
|
||
## Questions laissées ouvertes
|
||
|
||
- schémas SQL exacts RAW puis STRUCTURAL ;
|
||
- représentation persistable exacte d'un decoded output ;
|
||
- contrat exact du journal D3 ;
|
||
- granularité des projectors DOMAIN ;
|
||
- politique de supersession/versioning des outputs ;
|
||
- fenêtres OHLC initiales ;
|
||
- mécanisme de contexte pour les projections stateful.
|