0.7.48-pre
This commit is contained in:
@@ -87,3 +87,7 @@
|
|||||||
0.7.47-doc-matrix - Révision documentaire : ajout d’une matrice DEX dédiée, ajout explicite des sources Git/IDL à consulter, et redécoupage du plan `0.7.48+` en un DEX/version par tranche afin d’éviter les lots “tous events/tous decoders” trop larges.
|
0.7.47-doc-matrix - Révision documentaire : ajout d’une matrice DEX dédiée, ajout explicite des sources Git/IDL à consulter, et redécoupage du plan `0.7.48+` en un DEX/version par tranche afin d’éviter les lots “tous events/tous decoders” trop larges.
|
||||||
|
|
||||||
0.7.47-doc-event-coverage - Ajout d'une matrice événementielle complémentaire `DEX_EVENT_COVERAGE_MATRIX.md` pour suivre, par DEX/version, les familles `swap`, `pool_create`, `liquidity`, `position`, `fee`, `reward`, `admin/config`, `mint`, `burn`, `transfer`, `account_create/close`, `wrap/unwrap`, `orderbook`, `vault`, `lock/unlock`, `launch` et `migration`; ajout de `DB_EVENT_MODEL_REVIEW.md` pour clarifier que `k_sol_dex_decoded_events` suffit à l'audit-only mais que des tables transversales sont nécessaires pour exploiter transfers, orderbook, vault, launch/migration et coverage upstream en requêtes métier.
|
0.7.47-doc-event-coverage - Ajout d'une matrice événementielle complémentaire `DEX_EVENT_COVERAGE_MATRIX.md` pour suivre, par DEX/version, les familles `swap`, `pool_create`, `liquidity`, `position`, `fee`, `reward`, `admin/config`, `mint`, `burn`, `transfer`, `account_create/close`, `wrap/unwrap`, `orderbook`, `vault`, `lock/unlock`, `launch` et `migration`; ajout de `DB_EVENT_MODEL_REVIEW.md` pour clarifier que `k_sol_dex_decoded_events` suffit à l'audit-only mais que des tables transversales sont nécessaires pour exploiter transfers, orderbook, vault, launch/migration et coverage upstream en requêtes métier.
|
||||||
|
0.7.48-pre-event-coverage-sync - Raccordement de `k_sol_dex_event_coverage_entries` au registre upstream Git : ajout de `DexEventCoverageService`, sync des entrées registry vers SQLite, inférence conservatoire `event_family` / `expected_db_target`, mapping local limité aux events Raydium déjà connus, refresh des compteurs observés/matérialisés depuis `k_sol_dex_decoded_events` et tables non-trade existantes, sans modification des decoders ni de la matérialisation trade/candle.
|
||||||
|
0.7.48-pre-event-coverage-fix-docs - Correction du refresh SQL `k_sol_dex_event_coverage_entries` pour éviter les requêtes dynamiques non compatibles avec `sqlx::query` 0.9 ; mise à jour documentaire README/ROADMAP pour acter `0.7.48-pre` comme checkpoint DB/reporting et réaligner la suite sur l’ordre Raydium avant Meteora (`0.7.48 raydium_cpmm`, `0.7.49 raydium_clmm`, puis Pump/Meteora).
|
||||||
|
0.7.48-pre-event-coverage-report - Clôture du checkpoint `0.7.48-pre` : raccordement des summaries `k_sol_dex_event_coverage_entries` aux diagnostics locaux, ajout des compteurs agrégés de couverture au `LocalPipelineDiagnosticSummaryDto` et au `LocalPipelineValidationReportDto`, ajout du profil `0.7.48-pre_event_coverage_db_checkpoint`, exposition du profil dans Demo Pipeline 2, et maintien des invariants : aucun decoder DEX modifié, aucun trade/candle créé, aucun `program_id` promu sans corpus.
|
||||||
|
0.7.48-pre-event-coverage-validation-scope - Correction du profil `0.7.48-pre_event_coverage_db_checkpoint` : le contrôle bloquant des trade candidates non matérialisés est maintenant borné aux DEX attendus de la tranche Raydium (`raydium_cpmm`, `raydium_clmm`, `raydium_amm_v4`) afin qu’un DEX partiel hors scope, comme `fluxbeam`, reste diagnostiqué sans bloquer le checkpoint DB/event coverage.
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -438,3 +438,28 @@ Voir :
|
|||||||
- `DEX_EVENT_COVERAGE_MATRIX.md` pour les familles d'events à couvrir ;
|
- `DEX_EVENT_COVERAGE_MATRIX.md` pour les familles d'events à couvrir ;
|
||||||
- `DB_EVENT_MODEL_REVIEW.md` pour les ajouts DB à envisager avant `0.7.48+`.
|
- `DB_EVENT_MODEL_REVIEW.md` pour les ajouts DB à envisager avant `0.7.48+`.
|
||||||
|
|
||||||
|
|
||||||
|
## Note 0.7.48-pre — Event coverage DB checkpoint
|
||||||
|
|
||||||
|
La micro-tranche `0.7.48-pre` introduit la persistance de couverture événementielle avant la reprise DEX par DEX.
|
||||||
|
|
||||||
|
Ajouts côté `kb_lib` :
|
||||||
|
|
||||||
|
- table `k_sol_dex_event_coverage_entries` ;
|
||||||
|
- entity, DTO et requêtes dédiées ;
|
||||||
|
- service `DexEventCoverageService` pour synchroniser les entrées du registre upstream Git vers SQLite ;
|
||||||
|
- refresh des compteurs locaux depuis `k_sol_dex_decoded_events` et les tables déjà existantes de matérialisation non-trade / trade ;
|
||||||
|
- exposition des summaries de coverage dans les diagnostics locaux ;
|
||||||
|
- ajout du profil de validation `0.7.48-pre_event_coverage_db_checkpoint`, qui synchronise le registre upstream avant validation ;
|
||||||
|
- le profil `0.7.48-pre` garde les invariants globaux de non-régression, mais borne le contrôle bloquant des trade candidates non matérialisés aux DEX Raydium attendus pour éviter qu’un DEX partiel hors scope bloque le checkpoint DB ;
|
||||||
|
- sélection du profil `0.7.48-pre` dans Demo Pipeline 2.
|
||||||
|
|
||||||
|
Cette tranche ne modifie pas les decoders DEX, ne crée aucun trade/candle, et ne promeut aucun `program_id` comme vérifié. Elle sert uniquement à objectiver la couverture : `listed`, `decoded/audit`, `observed`, `materialized`, `trade_count` et statut de preuve.
|
||||||
|
|
||||||
|
La suite fonctionnelle reprend par Raydium avant Meteora :
|
||||||
|
|
||||||
|
1. `0.7.48` — `raydium_cpmm` ;
|
||||||
|
2. `0.7.49` — `raydium_clmm` ;
|
||||||
|
3. `0.7.50` — `pump_swap` ;
|
||||||
|
4. `0.7.51` — `pump_fun` ;
|
||||||
|
5. `0.7.52+` — Meteora puis les autres DEX/surfaces.
|
||||||
|
|||||||
156
ROADMAP.md
156
ROADMAP.md
@@ -1242,101 +1242,112 @@ Aucun de ces programmes ne doit être marqué `verified_by_corpus` uniquement pa
|
|||||||
|
|
||||||
Objectif : éviter de limiter la matrice aux DEX/versions et imposer une couverture événementielle exhaustive avant la reprise DEX par DEX.
|
Objectif : éviter de limiter la matrice aux DEX/versions et imposer une couverture événementielle exhaustive avant la reprise DEX par DEX.
|
||||||
|
|
||||||
À faire :
|
Statut : implémenté en micro-tranche DB/reporting, sans modifier les decoders ni la matérialisation marché.
|
||||||
|
|
||||||
- maintenir `DEX_EVENT_COVERAGE_MATRIX.md` en plus de `DEX_DECODER_MATRIX.md` ;
|
Fait :
|
||||||
- lister pour chaque DEX/version tous les events/instructions/logs connus depuis Carbon, fnzero, IDL, Pinax, HODL Warden, OpenBook, Phoenix et Vybe ;
|
|
||||||
|
- maintien de `DEX_EVENT_COVERAGE_MATRIX.md` en plus de `DEX_DECODER_MATRIX.md` ;
|
||||||
|
- ajout de `k_sol_dex_event_coverage_entries` dans `kb_lib/src/db/schema.rs` ;
|
||||||
|
- ajout des entity/DTO/queries/re-exports associés ;
|
||||||
|
- ajout de `DexEventCoverageService` pour synchroniser les entrées du registre upstream Git vers la table de coverage ;
|
||||||
|
- refresh des compteurs `observed_count`, `materialized_count`, `trade_count`, `first_signature` et `last_signature` depuis les events décodés et les tables métier existantes ;
|
||||||
|
- inférence conservatoire de `event_family`, `expected_db_target` et `local_event_kind`, sans promotion de `program_id` ni validation métier automatique ;
|
||||||
|
- correction du refresh SQL pour rester compatible avec `sqlx::query` en SQL statique ;
|
||||||
|
- exposition des summaries de coverage dans `LocalPipelineDiagnosticSummaryDto` et `LocalPipelineValidationReportDto` ;
|
||||||
|
- ajout du profil de validation `0.7.48-pre_event_coverage_db_checkpoint`, avec synchronisation upstream préalable ;
|
||||||
|
- contrôle bloquant des trade candidates non matérialisés borné aux DEX Raydium attendus dans ce profil, afin que les DEX partiels hors scope restent diagnostiqués sans bloquer le checkpoint DB/reporting ;
|
||||||
|
- sélection du profil `0.7.48-pre` dans Demo Pipeline 2.
|
||||||
|
|
||||||
|
Reste à faire dans les tranches DEX :
|
||||||
|
|
||||||
|
- compléter la liste exhaustive des events/instructions/logs par DEX depuis Carbon, fnzero, IDL, Pinax, HODL Warden, OpenBook, Phoenix et Vybe ;
|
||||||
- inclure explicitement les familles non-trade : `burn`, `mint`, `transfer`, `account_create`, `account_close`, `wrap_sol`, `unwrap_sol`, `lock`, `unlock`, `vault_deposit`, `vault_withdraw`, `admin/config`, `fee`, `reward`, `launch`, `migration` ;
|
- inclure explicitement les familles non-trade : `burn`, `mint`, `transfer`, `account_create`, `account_close`, `wrap_sol`, `unwrap_sol`, `lock`, `unlock`, `vault_deposit`, `vault_withdraw`, `admin/config`, `fee`, `reward`, `launch`, `migration` ;
|
||||||
- vérifier si la DB actuelle suffit ou si une table transversale doit être ajoutée ;
|
- ajouter plus tard `k_sol_token_transfer_events` et `k_sol_orderbook_events` quand le besoin métier est prouvé par plusieurs DEX ;
|
||||||
- prioriser `k_sol_dex_event_coverage_entries`, puis `k_sol_token_transfer_events` et `k_sol_orderbook_events` ;
|
|
||||||
- ne pas créer de trade/candle depuis ces nouveaux chemins sans validation économique et corpus.
|
- ne pas créer de trade/candle depuis ces nouveaux chemins sans validation économique et corpus.
|
||||||
|
|
||||||
|
|
||||||
### 6.080. Version `0.7.48` — `meteora_damm_v2` séparé
|
### 6.080. Version `0.7.48` — `raydium_cpmm` event coverage
|
||||||
Objectif : reprendre `meteora_damm_v2` comme DEX effectif séparé après disponibilité du registre upstream Git.
|
Objectif : reprendre `raydium_cpmm` en premier, avant Meteora, avec une couverture complète des events listés depuis Carbon/fnzero/IDL.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
|
|
||||||
- utiliser le registre `0.7.47` comme source d’indices, pas comme preuve ;
|
- utiliser `k_sol_dex_event_coverage_entries` comme ledger de couverture attendu/observé/matérialisé ;
|
||||||
- vérifier `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` dans le corpus local avant de le marquer `verified_by_corpus` ;
|
- lister tous les discriminants/instructions/events CPMM depuis les sources upstream ;
|
||||||
- consolider `create_pool`, swaps exploitables, configs dynamiques, fees/admin et events lifecycle ;
|
- comparer avec les events déjà connus localement : swap, initialize, withdraw, collect_creator_fee et audits restants ;
|
||||||
- conserver les swaps sans payload montant/prix fiables comme `non_actionable_trade` ;
|
- conserver les swaps matérialisés uniquement si les montants et le sens économique restent validés ;
|
||||||
- ne promouvoir aucun event depuis `instruction_audit` sans signature de validation.
|
- compléter les events non-trade CPMM en audit ou matérialisation existante uniquement avec corpus local ;
|
||||||
|
- vérifier par SQL que les non-trades ne produisent aucun trade/candle.
|
||||||
|
|
||||||
### 6.081. Version `0.7.49` — `meteora_dbc` séparé
|
### 6.081. Version `0.7.49` — `raydium_clmm` event coverage
|
||||||
Objectif : séparer proprement bonding/launch, swap effectif, migration et attribution d’origine dans `meteora_dbc`.
|
Objectif : reprendre `raydium_clmm` après CPMM, avec couverture des swaps, positions, liquidité, rewards, fees, protocol fees et cas Token-2022.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
|
|
||||||
- distinguer les events de bonding curve / launch des events de DEX effectif ;
|
- lister tous les events/instructions CLMM depuis Carbon/fnzero/IDL ;
|
||||||
- vérifier swaps exploitables, migration, lifecycle, mint/burn éventuels et launch attribution ;
|
- consolider `swap`, `swap_v2`, open/close position, increase/decrease liquidity, reward/fee/admin ;
|
||||||
- éviter toute candle artificielle sur events de bonding/launch non pricés ;
|
- classer les events non observés en `upstream_git_mapped_unverified` ;
|
||||||
- documenter les signatures/corpus avant toute promotion.
|
- matérialiser uniquement les events prouvés par corpus ;
|
||||||
|
- vérifier absence de faux trades/candles.
|
||||||
|
|
||||||
### 6.082. Version `0.7.50` — `orca_whirlpools` séparé
|
### 6.082. Version `0.7.50` — `pump_swap` event coverage
|
||||||
Objectif : revalider Orca Whirlpools par corpus dédié avant toute promotion au même niveau que Raydium/Meteora.
|
Objectif : compléter `pump_swap` au-delà de `buy/sell`.
|
||||||
|
|
||||||
À faire :
|
À faire : couvrir fees, cashback, volume accumulator, admin/config et autres events upstream disponibles, tout en maintenant l’invariant non-trade = zéro trade/candle.
|
||||||
|
|
||||||
- revalider create_pool, swap, liquidité, positions, mints et montants fiables ;
|
### 6.083. Version `0.7.51` — `pump_fun` launch/bonding/migration
|
||||||
- traiter les swaps Orca partiels comme non-actionnables tant que les montants ne sont pas reconstruits ;
|
Objectif : séparer launch/bonding de DEX effectif et valider migration vers PumpSwap ou autre surface tradable.
|
||||||
- matérialiser uniquement les events prouvés ;
|
|
||||||
- ajouter des diagnostics par event kind.
|
|
||||||
|
|
||||||
### 6.083. Version `0.7.51` — `fluxbeam` séparé
|
À faire : traiter create, buy/sell bonding, update/config, mint/burn éventuels, migration/graduate et rattachement au pool tradable.
|
||||||
Objectif : vérifier FluxBeam comme DEX effectif distinct.
|
|
||||||
|
|
||||||
À faire : constituer un corpus local, vérifier `program_id`, comptes, préfixes `data_json`, swaps, pools, liquidity et events non-trade prouvés.
|
### 6.084. Version `0.7.52` — `meteora_dbc` séparé
|
||||||
|
Objectif : reprendre Meteora après les tranches Raydium et Pump, en séparant bonding/launch, swap effectif, migration et attribution d’origine.
|
||||||
|
|
||||||
### 6.084. Version `0.7.52` — `dexlab` / OpenBook relation
|
À faire : vérifier swaps exploitables, migration, lifecycle, mint/burn éventuels, launch attribution, fees/admin, sans candle artificielle sur events non pricés.
|
||||||
Objectif : vérifier DexLab comme DEX effectif distinct sans le confondre avec OpenBook ou une couche de marché associée.
|
|
||||||
|
|
||||||
À faire : constituer un corpus local, vérifier `program_id`, comptes, préfixes `data_json`, swaps, pools et éventuels liens de market/pool.
|
### 6.085. Version `0.7.53` — `meteora_dlmm` parité upstream finale
|
||||||
|
Objectif : comparer la couverture locale DLMM déjà avancée avec toutes les sources Git/IDL et documenter ou fermer les audits résiduels.
|
||||||
|
|
||||||
### 6.085. Version `0.7.53` — Lifinity / Phoenix / OpenBook / Stabble
|
À faire : revalider swaps, liquidity, positions, lifecycle, fees/rewards/admin, et garder les discriminants non mappés en audit documenté.
|
||||||
Objectif : traiter les DEX/orderbooks supplémentaires identifiés par le registre upstream Git.
|
|
||||||
|
|
||||||
À faire : valider séparément `lifinity_amm_v2`, `phoenix_v1`, `openbook_v2`, `stabble_stable_swap` et `stabble_weighted_swap`, sans matérialiser de trade avant preuve de montants exploitables.
|
### 6.086. Version `0.7.54` — `meteora_damm_v1` parité upstream finale
|
||||||
|
Objectif : compléter la tranche DAMM v1 déjà engagée, résoudre les surfaces non observées et améliorer le rattachement pool/pair quand possible.
|
||||||
|
|
||||||
### 6.086. Version `0.7.54` — BonkSwap / Boop / Moonshot / Heaven / Wavebreak / Vertigo / Virtuals / Pancake / OKX DEX
|
À faire : vérifier toutes les instructions upstream restantes, matérialiser uniquement les events prouvés et documenter les cas sans pool/pair local.
|
||||||
Objectif : vérifier les surfaces de swap/launch hybrides ou candidates découvertes via registre et corpus.
|
|
||||||
|
|
||||||
À faire : séparer DEX effectif, launch surface, routeur/agrégateur et simple candidat ; ne promouvoir aucun `program_id` sans corpus local.
|
### 6.087. Version `0.7.55` — `meteora_damm_v2` séparé
|
||||||
|
Objectif : reprendre DAMM v2 comme DEX effectif séparé après disponibilité du ledger de coverage.
|
||||||
|
|
||||||
### 6.087. Version `0.7.55` — Raydium surfaces complémentaires
|
À faire : consolider create_pool, swaps exploitables, configs dynamiques, liquidity, fees/admin, lifecycle ; conserver les swaps sans payload montant/prix fiable comme `non_actionable_trade`.
|
||||||
Objectif : traiter `raydium_launchpad`, `raydium_liquidity_locking`, `raydium_stable_swap` et éventuelles surfaces Raydium non couvertes par CPMM/CLMM/AMM v4.
|
|
||||||
|
|
||||||
À faire : distinguer launch, lock, stable AMM et AMM legacy ; garder les events non prouvés en audit.
|
### 6.088. Version `0.7.56` — `phoenix_v1` audit-only complet
|
||||||
|
Objectif : finir tous les events Git disponibles en audit, sans activer de trade/candle.
|
||||||
|
|
||||||
### 6.088. Version `0.7.56` — Aggregators, limit orders, perps et lending
|
À faire : couvrir `Fill`, `FillSummary`, `Fee`, `Evict`, `ExpiredOrder` et autres logs/events disponibles ; préparer le futur modèle orderbook sans matérialisation marché par défaut.
|
||||||
Objectif : intégrer les programmes utiles au routage, aux ordres, aux perps ou au lending sans les confondre avec les DEX effectifs.
|
|
||||||
|
|
||||||
À faire : classifier `jupiter_*`, `kamino_*`, `drift_v2`, `marginfi_v2`, `dflow_aggregator_v4`, `zeta` comme contexte/routing/ordres tant qu’ils ne produisent pas directement une surface DEX matérialisable.
|
### 6.089. Version `0.7.57` — `openbook_v2` audit-only complet
|
||||||
|
Objectif : finir les layouts logs/events OpenBook v2 et définir les conditions futures de matérialisation orderbook/trade.
|
||||||
|
|
||||||
### 6.089. Version `0.7.57` — Couverture événementielle DEX consolidée
|
À faire : vérifier fills, settle, consume events, open orders create/close, maker/taker, lots/decimals et sens économique avant toute promotion.
|
||||||
Objectif : s’assurer que chaque DEX effectif supporté expose les événements utiles au scoring et au risque sans polluer les trades/candles.
|
|
||||||
|
|
||||||
À faire : vérifier par DEX `swap`, liquidité, lifecycle, fees, rewards, admin/config, burns/mints utiles, et matérialiser uniquement les événements prouvés.
|
### 6.090. Version `0.7.58` — `orca_whirlpools` event coverage
|
||||||
|
Objectif : reprendre Whirlpools depuis IDL/source avec corpus dédié.
|
||||||
|
|
||||||
### 6.090. Version `0.7.58` — `kb_demo_app` Demo4 : DEX Screener et sources externes de découverte
|
À faire : swaps, pools, positions, liquidity, fees/rewards, tick arrays, mint/burn/Token-2022 si applicable.
|
||||||
Objectif : utiliser des sources externes comme aides à la découverte de corpus sans les traiter comme vérité métier.
|
|
||||||
|
|
||||||
À faire : rechercher des paires par token mint, chain, DEX name, pool address ou program id lorsque disponible, comparer avec la base locale, copier les signatures/adresses candidates pour backfill, sans promotion automatique.
|
### 6.091. Version `0.7.59` — Launch surfaces
|
||||||
|
Objectif : traiter les surfaces de lancement après les DEX effectifs prioritaires.
|
||||||
|
|
||||||
### 6.091. Version `0.7.59` — Démos spécialisées launch surfaces après DEX effectifs
|
À faire : Raydium LaunchLab/Launchpad, PumpFun migration, Moonshot/Moonit, Boop, Heaven, Bags, LetsBonk, avec séparation stricte launch origin / pool origin / DEX effectif.
|
||||||
Objectif : préparer des vues spécialisées pour les launch surfaces après stabilisation des DEX effectifs.
|
|
||||||
|
|
||||||
À faire : couvrir `pump_fun`, `raydium_launchpad`, `believe`, `bags`, `moonshot` / `moonit`, `boop_fun`, `letsbonk` / `bonk_fun`, `heaven`, avec séparation stricte entre launch origin, pool origin, DEX effectif et migration.
|
### 6.092. Version `0.7.60` — DEX historiques / candidats
|
||||||
|
Objectif : valider les DEX ou surfaces candidates par corpus, sans promotion automatique depuis les sources externes.
|
||||||
|
|
||||||
### 6.092. Version `0.7.60` — `kb_demo_app` Demo10 : watcher WebSocket live DEX
|
À faire : FluxBeam, DexLab, Lifinity, Stabble, BonkSwap, GooseFX, Obric, SolFi et autres entrées Vybe/registry.
|
||||||
Objectif : valider le passage du replay/backfill vers l’observation temps réel contrôlée.
|
|
||||||
|
|
||||||
À faire : sélectionner endpoints WS/HTTP et DEX/program ids à souscrire, utiliser le pipeline existant, afficher compteurs live, erreurs, subscriptions actives et derniers objets persistés.
|
### 6.093. Version `0.7.61` — Validation consolidée
|
||||||
|
Objectif : rejouer une base neuve multi-DEX et valider les invariants du pipeline complet.
|
||||||
|
|
||||||
### 6.093. Version `0.7.61` — Validation DEX v1 consolidée
|
À faire : rapport coverage par DEX/event, zéro faux trade/candle, corpus documentés, matrices cohérentes, diagnostics bloquants à zéro.
|
||||||
Objectif : rejouer tous les DEX effectifs supportés et valider les invariants du pipeline complet avant de revenir aux launch surfaces ou à l’analyse `0.8.x`.
|
|
||||||
|
|
||||||
À faire : bases neuves, compteurs globaux et par DEX, diagnostics bloquants, samples d’anomalie, corpus documentés et matrice de support par DEX/variante/instruction/event.
|
|
||||||
|
|
||||||
### 6.091. Version `0.8.x` — Analyse et filtrage
|
### 6.091. Version `0.8.x` — Analyse et filtrage
|
||||||
Objectif : transformer les événements bruts en signaux exploitables.
|
Objectif : transformer les événements bruts en signaux exploitables.
|
||||||
@@ -1525,18 +1536,23 @@ Préconditions considérées acquises avant cette reprise :
|
|||||||
|
|
||||||
Ordre de travail recommandé pour la suite :
|
Ordre de travail recommandé pour la suite :
|
||||||
|
|
||||||
1. `0.7.44` : ledger de décodage/replay et skip sûr ;
|
1. `0.7.44` : ledger de décodage/replay et skip sûr — acquis ;
|
||||||
2. `0.7.45` : `meteora_dlmm` — clos ;
|
2. `0.7.45` : `meteora_dlmm` — clos ;
|
||||||
3. `0.7.46` : `meteora_damm_v1` ;
|
3. `0.7.46` : `meteora_damm_v1` — clos côté corpus local ;
|
||||||
4. `0.7.47` : `meteora_damm_v2` ;
|
4. `0.7.47` : Upstream Git Registry / DEX discovery preparation — acquis ;
|
||||||
5. `0.7.48` : `meteora_dbc` ;
|
5. `0.7.48-pre` : event coverage + DB model checkpoint — clos après table, sync upstream, refresh counts, diagnostics et profil validation ;
|
||||||
6. `0.7.49` : `orca_whirlpools` ;
|
6. `0.7.48` : `raydium_cpmm` ;
|
||||||
7. `0.7.50` : `fluxbeam` ;
|
7. `0.7.49` : `raydium_clmm` ;
|
||||||
8. `0.7.51` : `dexlab` ;
|
8. `0.7.50` : `pump_swap` ;
|
||||||
9. `0.7.52` : `metaDAO` candidat DEX ;
|
9. `0.7.51` : `pump_fun` ;
|
||||||
10. `0.7.53` : `printr` candidat DEX ;
|
10. `0.7.52` : `meteora_dbc` ;
|
||||||
11. `0.7.54` : couverture événementielle DEX consolidée ;
|
11. `0.7.53` : `meteora_dlmm` parité upstream finale ;
|
||||||
12. `0.7.55+` : sources externes de découverte, launch surfaces, watcher live et validation consolidée.
|
12. `0.7.54` : `meteora_damm_v1` parité upstream finale ;
|
||||||
|
13. `0.7.55` : `meteora_damm_v2` ;
|
||||||
|
14. `0.7.56` : `phoenix_v1` audit-only complet ;
|
||||||
|
15. `0.7.57` : `openbook_v2` audit-only complet ;
|
||||||
|
16. `0.7.58` : `orca_whirlpools` ;
|
||||||
|
17. `0.7.59+` : launch surfaces, DEX candidats/historiques et validation consolidée.
|
||||||
|
|
||||||
Garde-fous constants :
|
Garde-fous constants :
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,8 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
|
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
|
||||||
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
|
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
|
||||||
<option value="0.7.43_meteora_effective_surfaces" selected>0.7.43 — Meteora family event coverage</option>
|
<option value="0.7.48-pre_event_coverage_db_checkpoint" selected>0.7.48-pre — Event coverage DB checkpoint</option>
|
||||||
|
<option value="0.7.43_meteora_effective_surfaces">0.7.43 — Meteora family event coverage</option>
|
||||||
<option value="0.7.42_raydium_family_event_coverage">0.7.42 — Raydium family event coverage</option>
|
<option value="0.7.42_raydium_family_event_coverage">0.7.42 — Raydium family event coverage</option>
|
||||||
<option value="0.7.41_raydium_amm_v4_swap_decoder">0.7.41 — Raydium AMM v4 swap decoder</option>
|
<option value="0.7.41_raydium_amm_v4_swap_decoder">0.7.41 — Raydium AMM v4 swap decoder</option>
|
||||||
<option value="0.7.40_raydium_effective_surfaces">0.7.40 — Raydium effective surfaces</option>
|
<option value="0.7.40_raydium_effective_surfaces">0.7.40 — Raydium effective surfaces</option>
|
||||||
|
|||||||
@@ -75,6 +75,26 @@ rewardEventCount: number,
|
|||||||
* Total persisted pool administration events.
|
* Total persisted pool administration events.
|
||||||
*/
|
*/
|
||||||
poolAdminEventCount: number,
|
poolAdminEventCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries listed from upstream registry sources.
|
||||||
|
*/
|
||||||
|
eventCoverageListedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries mapped to local decoder event kinds.
|
||||||
|
*/
|
||||||
|
eventCoverageDecodedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries observed in local corpus.
|
||||||
|
*/
|
||||||
|
eventCoverageObservedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries materialized into target tables.
|
||||||
|
*/
|
||||||
|
eventCoverageMaterializedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Coverage rows whose mapped events are not locally observed yet.
|
||||||
|
*/
|
||||||
|
eventCoverageUpstreamGitMappedUnverifiedEntryCount: number,
|
||||||
/**
|
/**
|
||||||
* Whether the local persisted pipeline has no blocking diagnostic issue.
|
* Whether the local persisted pipeline has no blocking diagnostic issue.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -62,6 +62,26 @@ rewardEventCount: number,
|
|||||||
* Total persisted pool administration events.
|
* Total persisted pool administration events.
|
||||||
*/
|
*/
|
||||||
poolAdminEventCount: number,
|
poolAdminEventCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries listed from upstream registry sources.
|
||||||
|
*/
|
||||||
|
eventCoverageListedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries mapped to local decoder event kinds.
|
||||||
|
*/
|
||||||
|
eventCoverageDecodedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries observed in local corpus.
|
||||||
|
*/
|
||||||
|
eventCoverageObservedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Event coverage entries materialized into target tables.
|
||||||
|
*/
|
||||||
|
eventCoverageMaterializedEntryCount: number,
|
||||||
|
/**
|
||||||
|
* Coverage rows whose mapped events are not locally observed yet.
|
||||||
|
*/
|
||||||
|
eventCoverageUpstreamGitMappedUnverifiedEntryCount: number,
|
||||||
/**
|
/**
|
||||||
* Total known tokens.
|
* Total known tokens.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -176,6 +176,21 @@ pub(crate) struct DemoPipeline2LocalPipelineValidationReport {
|
|||||||
/// Total persisted pool administration events.
|
/// Total persisted pool administration events.
|
||||||
#[ts(type = "number")]
|
#[ts(type = "number")]
|
||||||
pub pool_admin_event_count: i64,
|
pub pool_admin_event_count: i64,
|
||||||
|
/// Event coverage entries listed from upstream registry sources.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_listed_entry_count: u64,
|
||||||
|
/// Event coverage entries mapped to local decoder event kinds.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_decoded_entry_count: u64,
|
||||||
|
/// Event coverage entries observed in local corpus.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_observed_entry_count: u64,
|
||||||
|
/// Event coverage entries materialized into target tables.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_materialized_entry_count: u64,
|
||||||
|
/// Coverage rows whose mapped events are not locally observed yet.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_upstream_git_mapped_unverified_entry_count: u64,
|
||||||
/// Total known tokens.
|
/// Total known tokens.
|
||||||
#[ts(type = "number")]
|
#[ts(type = "number")]
|
||||||
pub token_count: i64,
|
pub token_count: i64,
|
||||||
@@ -327,6 +342,21 @@ pub(crate) struct DemoPipeline2LocalPipelineDiagnosticSummary {
|
|||||||
/// Total persisted pool administration events.
|
/// Total persisted pool administration events.
|
||||||
#[ts(type = "number")]
|
#[ts(type = "number")]
|
||||||
pub pool_admin_event_count: i64,
|
pub pool_admin_event_count: i64,
|
||||||
|
/// Event coverage entries listed from upstream registry sources.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_listed_entry_count: u64,
|
||||||
|
/// Event coverage entries mapped to local decoder event kinds.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_decoded_entry_count: u64,
|
||||||
|
/// Event coverage entries observed in local corpus.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_observed_entry_count: u64,
|
||||||
|
/// Event coverage entries materialized into target tables.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_materialized_entry_count: u64,
|
||||||
|
/// Coverage rows whose mapped events are not locally observed yet.
|
||||||
|
#[ts(type = "number")]
|
||||||
|
pub event_coverage_upstream_git_mapped_unverified_entry_count: u64,
|
||||||
/// Whether the local persisted pipeline has no blocking diagnostic issue.
|
/// Whether the local persisted pipeline has no blocking diagnostic issue.
|
||||||
pub diagnostics_clean: bool,
|
pub diagnostics_clean: bool,
|
||||||
/// Number of blocking diagnostic issues.
|
/// Number of blocking diagnostic issues.
|
||||||
@@ -1279,7 +1309,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
|
|||||||
let service = kb_lib::LocalPipelineValidationService::new(database.clone());
|
let service = kb_lib::LocalPipelineValidationService::new(database.clone());
|
||||||
let profile_code = match request {
|
let profile_code = match request {
|
||||||
Some(request) => request.profile_code,
|
Some(request) => request.profile_code,
|
||||||
None => "0.7.43_meteora_effective_surfaces".to_string(),
|
None => "0.7.48-pre_event_coverage_db_checkpoint".to_string(),
|
||||||
};
|
};
|
||||||
let run_result = match profile_code.as_str() {
|
let run_result = match profile_code.as_str() {
|
||||||
"0.7.27" | "0.7.27_dexes_non_regression" => {
|
"0.7.27" | "0.7.27_dexes_non_regression" => {
|
||||||
@@ -1335,6 +1365,9 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
|
|||||||
"0.7.43" | "0.7.43_meteora_effective_surfaces" => {
|
"0.7.43" | "0.7.43_meteora_effective_surfaces" => {
|
||||||
service.validate_v0_7_43_current_database().await
|
service.validate_v0_7_43_current_database().await
|
||||||
},
|
},
|
||||||
|
"0.7.48-pre" | "0.7.48-pre_event_coverage_db_checkpoint" => {
|
||||||
|
service.validate_v0_7_48_pre_current_database().await
|
||||||
|
},
|
||||||
other => Err(kb_lib::Error::InvalidState(format!(
|
other => Err(kb_lib::Error::InvalidState(format!(
|
||||||
"unsupported local pipeline validation profile: {other}"
|
"unsupported local pipeline validation profile: {other}"
|
||||||
))),
|
))),
|
||||||
@@ -1821,6 +1854,12 @@ fn demo_pipeline2_map_local_validation_report(
|
|||||||
fee_event_count: report.fee_event_count,
|
fee_event_count: report.fee_event_count,
|
||||||
reward_event_count: report.reward_event_count,
|
reward_event_count: report.reward_event_count,
|
||||||
pool_admin_event_count: report.pool_admin_event_count,
|
pool_admin_event_count: report.pool_admin_event_count,
|
||||||
|
event_coverage_listed_entry_count: report.event_coverage_listed_entry_count,
|
||||||
|
event_coverage_decoded_entry_count: report.event_coverage_decoded_entry_count,
|
||||||
|
event_coverage_observed_entry_count: report.event_coverage_observed_entry_count,
|
||||||
|
event_coverage_materialized_entry_count: report.event_coverage_materialized_entry_count,
|
||||||
|
event_coverage_upstream_git_mapped_unverified_entry_count: report
|
||||||
|
.event_coverage_upstream_git_mapped_unverified_entry_count,
|
||||||
token_count: report.token_count,
|
token_count: report.token_count,
|
||||||
token_metadata_missing_count: report.token_metadata_missing_count,
|
token_metadata_missing_count: report.token_metadata_missing_count,
|
||||||
tradable_token_metadata_missing_count: report.tradable_token_metadata_missing_count,
|
tradable_token_metadata_missing_count: report.tradable_token_metadata_missing_count,
|
||||||
@@ -1973,6 +2012,12 @@ fn demo_pipeline2_map_local_diagnostics_summary(
|
|||||||
fee_event_count: summary.fee_event_count,
|
fee_event_count: summary.fee_event_count,
|
||||||
reward_event_count: summary.reward_event_count,
|
reward_event_count: summary.reward_event_count,
|
||||||
pool_admin_event_count: summary.pool_admin_event_count,
|
pool_admin_event_count: summary.pool_admin_event_count,
|
||||||
|
event_coverage_listed_entry_count: summary.event_coverage_listed_entry_count,
|
||||||
|
event_coverage_decoded_entry_count: summary.event_coverage_decoded_entry_count,
|
||||||
|
event_coverage_observed_entry_count: summary.event_coverage_observed_entry_count,
|
||||||
|
event_coverage_materialized_entry_count: summary.event_coverage_materialized_entry_count,
|
||||||
|
event_coverage_upstream_git_mapped_unverified_entry_count: summary
|
||||||
|
.event_coverage_upstream_git_mapped_unverified_entry_count,
|
||||||
diagnostics_clean: summary.diagnostics_clean,
|
diagnostics_clean: summary.diagnostics_clean,
|
||||||
blocking_issue_count: summary.blocking_issue_count,
|
blocking_issue_count: summary.blocking_issue_count,
|
||||||
missing_trade_event_count: summary.missing_trade_event_count,
|
missing_trade_event_count: summary.missing_trade_event_count,
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ pub use dtos::DbRuntimeEventDto;
|
|||||||
pub use dtos::DexDecodeReplayLedgerDto;
|
pub use dtos::DexDecodeReplayLedgerDto;
|
||||||
pub use dtos::DexDecodedEventDto;
|
pub use dtos::DexDecodedEventDto;
|
||||||
pub use dtos::DexDto;
|
pub use dtos::DexDto;
|
||||||
|
pub use dtos::DexEventCoverageEntryDto;
|
||||||
|
pub use dtos::DexEventCoverageSummaryDto;
|
||||||
pub use dtos::FeeEventDto;
|
pub use dtos::FeeEventDto;
|
||||||
pub use dtos::KnownHttpEndpointDto;
|
pub use dtos::KnownHttpEndpointDto;
|
||||||
pub use dtos::KnownWsEndpointDto;
|
pub use dtos::KnownWsEndpointDto;
|
||||||
@@ -91,6 +93,8 @@ pub use entities::DbRuntimeEventEntity;
|
|||||||
pub use entities::DexDecodeReplayLedgerEntity;
|
pub use entities::DexDecodeReplayLedgerEntity;
|
||||||
pub use entities::DexDecodedEventEntity;
|
pub use entities::DexDecodedEventEntity;
|
||||||
pub use entities::DexEntity;
|
pub use entities::DexEntity;
|
||||||
|
pub use entities::DexEventCoverageEntryEntity;
|
||||||
|
pub use entities::DexEventCoverageSummaryEntity;
|
||||||
pub use entities::FeeEventEntity;
|
pub use entities::FeeEventEntity;
|
||||||
pub use entities::KnownHttpEndpointEntity;
|
pub use entities::KnownHttpEndpointEntity;
|
||||||
pub use entities::KnownWsEndpointEntity;
|
pub use entities::KnownWsEndpointEntity;
|
||||||
@@ -152,6 +156,12 @@ pub use queries::query_dex_decoded_events_get_by_key;
|
|||||||
pub use queries::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
pub use queries::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
||||||
pub use queries::query_dex_decoded_events_list_by_transaction_id;
|
pub use queries::query_dex_decoded_events_list_by_transaction_id;
|
||||||
pub use queries::query_dex_decoded_events_upsert;
|
pub use queries::query_dex_decoded_events_upsert;
|
||||||
|
pub use queries::query_dex_event_coverage_entries_delete_by_decoder;
|
||||||
|
pub use queries::query_dex_event_coverage_entries_list_by_decoder;
|
||||||
|
pub use queries::query_dex_event_coverage_entries_list_summary_by_decoder;
|
||||||
|
pub use queries::query_dex_event_coverage_entries_refresh_local_counts;
|
||||||
|
pub use queries::query_dex_event_coverage_entries_refresh_local_counts_by_decoder;
|
||||||
|
pub use queries::query_dex_event_coverage_entries_upsert;
|
||||||
pub use queries::query_dexs_get_by_code;
|
pub use queries::query_dexs_get_by_code;
|
||||||
pub use queries::query_dexs_list;
|
pub use queries::query_dexs_list;
|
||||||
pub use queries::query_dexs_upsert;
|
pub use queries::query_dexs_upsert;
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ mod chain_transaction;
|
|||||||
mod db_metadata;
|
mod db_metadata;
|
||||||
mod db_runtime_event;
|
mod db_runtime_event;
|
||||||
mod dex;
|
mod dex;
|
||||||
mod dex_decoded_event;
|
|
||||||
mod dex_decode_replay_ledger;
|
mod dex_decode_replay_ledger;
|
||||||
|
mod dex_decoded_event;
|
||||||
|
mod dex_event_coverage_entry;
|
||||||
mod fee_event;
|
mod fee_event;
|
||||||
mod known_http_endpoint;
|
mod known_http_endpoint;
|
||||||
mod known_ws_endpoint;
|
mod known_ws_endpoint;
|
||||||
@@ -76,8 +77,10 @@ pub use chain_transaction::ChainTransactionDto;
|
|||||||
pub use db_metadata::DbMetadataDto;
|
pub use db_metadata::DbMetadataDto;
|
||||||
pub use db_runtime_event::DbRuntimeEventDto;
|
pub use db_runtime_event::DbRuntimeEventDto;
|
||||||
pub use dex::DexDto;
|
pub use dex::DexDto;
|
||||||
pub use dex_decoded_event::DexDecodedEventDto;
|
|
||||||
pub use dex_decode_replay_ledger::DexDecodeReplayLedgerDto;
|
pub use dex_decode_replay_ledger::DexDecodeReplayLedgerDto;
|
||||||
|
pub use dex_decoded_event::DexDecodedEventDto;
|
||||||
|
pub use dex_event_coverage_entry::DexEventCoverageEntryDto;
|
||||||
|
pub use dex_event_coverage_entry::DexEventCoverageSummaryDto;
|
||||||
pub use fee_event::FeeEventDto;
|
pub use fee_event::FeeEventDto;
|
||||||
pub use known_http_endpoint::KnownHttpEndpointDto;
|
pub use known_http_endpoint::KnownHttpEndpointDto;
|
||||||
pub use known_ws_endpoint::KnownWsEndpointDto;
|
pub use known_ws_endpoint::KnownWsEndpointDto;
|
||||||
|
|||||||
407
kb_lib/src/db/dtos/dex_event_coverage_entry.rs
Normal file
407
kb_lib/src/db/dtos/dex_event_coverage_entry.rs
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
// file: kb_lib/src/db/dtos/dex_event_coverage_entry.rs
|
||||||
|
|
||||||
|
//! Application-facing DTOs for DEX event coverage.
|
||||||
|
|
||||||
|
/// Application-facing coverage row for one upstream/local event entry.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct DexEventCoverageEntryDto {
|
||||||
|
/// Optional numeric primary key.
|
||||||
|
pub id: std::option::Option<i64>,
|
||||||
|
/// Stable unique key used for idempotent upserts.
|
||||||
|
pub coverage_key: std::string::String,
|
||||||
|
/// Internal decoder code.
|
||||||
|
pub decoder_code: std::string::String,
|
||||||
|
/// Optional Solana program id associated with the source entry.
|
||||||
|
pub program_id: std::option::Option<std::string::String>,
|
||||||
|
/// Program family.
|
||||||
|
pub program_family: std::string::String,
|
||||||
|
/// Surface kind such as AMM, CLMM, launch, orderbook or aggregator.
|
||||||
|
pub surface_kind: std::string::String,
|
||||||
|
/// Source repository or registry source.
|
||||||
|
pub source_repo: std::option::Option<std::string::String>,
|
||||||
|
/// Source path inside the repository or registry source.
|
||||||
|
pub source_path: std::option::Option<std::string::String>,
|
||||||
|
/// Entry kind such as instruction, event, account, log or program_data.
|
||||||
|
pub entry_kind: std::string::String,
|
||||||
|
/// Source entry name.
|
||||||
|
pub entry_name: std::string::String,
|
||||||
|
/// Optional discriminator encoded as lowercase hexadecimal.
|
||||||
|
pub discriminator_hex: std::option::Option<std::string::String>,
|
||||||
|
/// Optional discriminator byte length.
|
||||||
|
pub discriminator_len: std::option::Option<i64>,
|
||||||
|
/// Normalized event family.
|
||||||
|
pub event_family: std::option::Option<std::string::String>,
|
||||||
|
/// Expected DB target table or audit-only target.
|
||||||
|
pub expected_db_target: std::option::Option<std::string::String>,
|
||||||
|
/// Proof status for this entry.
|
||||||
|
pub proof_status: std::string::String,
|
||||||
|
/// Local event kind produced by the decoder, when any.
|
||||||
|
pub local_event_kind: std::option::Option<std::string::String>,
|
||||||
|
/// Number of observed decoded/audit rows in the local corpus.
|
||||||
|
pub observed_count: i64,
|
||||||
|
/// Number of rows materialized into specialized business tables.
|
||||||
|
pub materialized_count: i64,
|
||||||
|
/// Number of trade rows produced from this entry.
|
||||||
|
pub trade_count: i64,
|
||||||
|
/// First local signature observed for this entry.
|
||||||
|
pub first_signature: std::option::Option<std::string::String>,
|
||||||
|
/// Last local signature observed for this entry.
|
||||||
|
pub last_signature: std::option::Option<std::string::String>,
|
||||||
|
/// Notes preserving uncertainty, mapping decisions or DB gaps.
|
||||||
|
pub notes: std::option::Option<std::string::String>,
|
||||||
|
/// Creation timestamp.
|
||||||
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
/// Update timestamp.
|
||||||
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DexEventCoverageEntryDto {
|
||||||
|
/// Audit-only target for decoded events that should not leave `k_sol_dex_decoded_events`.
|
||||||
|
pub const DB_TARGET_DECODED_EVENTS_ONLY: &'static str = "k_sol_dex_decoded_events_only";
|
||||||
|
/// Target table for validated AMM/CLOB trade rows.
|
||||||
|
pub const DB_TARGET_TRADE_EVENTS: &'static str = "k_sol_trade_events";
|
||||||
|
/// Target table for validated liquidity add/remove rows.
|
||||||
|
pub const DB_TARGET_LIQUIDITY_EVENTS: &'static str = "k_sol_liquidity_events";
|
||||||
|
/// Target table for validated pool lifecycle and position lifecycle rows.
|
||||||
|
pub const DB_TARGET_POOL_LIFECYCLE_EVENTS: &'static str = "k_sol_pool_lifecycle_events";
|
||||||
|
/// Target table for validated fee rows.
|
||||||
|
pub const DB_TARGET_FEE_EVENTS: &'static str = "k_sol_fee_events";
|
||||||
|
/// Target table for validated reward rows.
|
||||||
|
pub const DB_TARGET_REWARD_EVENTS: &'static str = "k_sol_reward_events";
|
||||||
|
/// Target table for validated admin/config rows.
|
||||||
|
pub const DB_TARGET_POOL_ADMIN_EVENTS: &'static str = "k_sol_pool_admin_events";
|
||||||
|
/// Target table for validated token mint rows.
|
||||||
|
pub const DB_TARGET_TOKEN_MINT_EVENTS: &'static str = "k_sol_token_mint_events";
|
||||||
|
/// Target table for validated token burn rows.
|
||||||
|
pub const DB_TARGET_TOKEN_BURN_EVENTS: &'static str = "k_sol_token_burn_events";
|
||||||
|
/// Planned target table for transfer rows.
|
||||||
|
pub const DB_TARGET_TOKEN_TRANSFER_EVENTS: &'static str = "k_sol_token_transfer_events";
|
||||||
|
/// Planned target table for token account lifecycle rows.
|
||||||
|
pub const DB_TARGET_TOKEN_ACCOUNT_EVENTS: &'static str = "k_sol_token_account_events";
|
||||||
|
/// Planned target table for orderbook rows.
|
||||||
|
pub const DB_TARGET_ORDERBOOK_EVENTS: &'static str = "k_sol_orderbook_events";
|
||||||
|
/// Planned target table for vault rows.
|
||||||
|
pub const DB_TARGET_VAULT_EVENTS: &'static str = "k_sol_vault_events";
|
||||||
|
/// Planned target table for launch and migration rows.
|
||||||
|
pub const DB_TARGET_LAUNCH_EVENTS: &'static str = "k_sol_launch_events";
|
||||||
|
/// Planned target table for liquidity lock rows.
|
||||||
|
pub const DB_TARGET_LIQUIDITY_LOCK_EVENTS: &'static str = "k_sol_liquidity_lock_events";
|
||||||
|
|
||||||
|
/// Builds a stable coverage key from source identity fields.
|
||||||
|
pub fn build_coverage_key(
|
||||||
|
decoder_code: &str,
|
||||||
|
program_id: &std::option::Option<std::string::String>,
|
||||||
|
source_repo: &std::option::Option<std::string::String>,
|
||||||
|
source_path: &std::option::Option<std::string::String>,
|
||||||
|
entry_kind: &str,
|
||||||
|
entry_name: &str,
|
||||||
|
discriminator_hex: &std::option::Option<std::string::String>,
|
||||||
|
) -> std::string::String {
|
||||||
|
return format!(
|
||||||
|
"{}|{}|{}|{}|{}|{}|{}",
|
||||||
|
decoder_code,
|
||||||
|
program_id.clone().unwrap_or_default(),
|
||||||
|
source_repo.clone().unwrap_or_default(),
|
||||||
|
source_path.clone().unwrap_or_default(),
|
||||||
|
entry_kind,
|
||||||
|
entry_name,
|
||||||
|
discriminator_hex.clone().unwrap_or_default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new event coverage entry DTO.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn new(
|
||||||
|
decoder_code: std::string::String,
|
||||||
|
program_id: std::option::Option<std::string::String>,
|
||||||
|
program_family: std::string::String,
|
||||||
|
surface_kind: std::string::String,
|
||||||
|
source_repo: std::option::Option<std::string::String>,
|
||||||
|
source_path: std::option::Option<std::string::String>,
|
||||||
|
entry_kind: std::string::String,
|
||||||
|
entry_name: std::string::String,
|
||||||
|
discriminator_hex: std::option::Option<std::string::String>,
|
||||||
|
discriminator_len: std::option::Option<i64>,
|
||||||
|
event_family: std::option::Option<std::string::String>,
|
||||||
|
expected_db_target: std::option::Option<std::string::String>,
|
||||||
|
proof_status: std::string::String,
|
||||||
|
local_event_kind: std::option::Option<std::string::String>,
|
||||||
|
observed_count: i64,
|
||||||
|
materialized_count: i64,
|
||||||
|
trade_count: i64,
|
||||||
|
first_signature: std::option::Option<std::string::String>,
|
||||||
|
last_signature: std::option::Option<std::string::String>,
|
||||||
|
notes: std::option::Option<std::string::String>,
|
||||||
|
) -> Self {
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
let coverage_key = Self::build_coverage_key(
|
||||||
|
decoder_code.as_str(),
|
||||||
|
&program_id,
|
||||||
|
&source_repo,
|
||||||
|
&source_path,
|
||||||
|
entry_kind.as_str(),
|
||||||
|
entry_name.as_str(),
|
||||||
|
&discriminator_hex,
|
||||||
|
);
|
||||||
|
return Self {
|
||||||
|
id: None,
|
||||||
|
coverage_key,
|
||||||
|
decoder_code,
|
||||||
|
program_id,
|
||||||
|
program_family,
|
||||||
|
surface_kind,
|
||||||
|
source_repo,
|
||||||
|
source_path,
|
||||||
|
entry_kind,
|
||||||
|
entry_name,
|
||||||
|
discriminator_hex,
|
||||||
|
discriminator_len,
|
||||||
|
event_family,
|
||||||
|
expected_db_target,
|
||||||
|
proof_status,
|
||||||
|
local_event_kind,
|
||||||
|
observed_count,
|
||||||
|
materialized_count,
|
||||||
|
trade_count,
|
||||||
|
first_signature,
|
||||||
|
last_signature,
|
||||||
|
notes,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a coverage row from an upstream registry entry without claiming local proof.
|
||||||
|
pub fn from_upstream_registry_entry(
|
||||||
|
entry: &crate::UpstreamRegistryEntryDto,
|
||||||
|
event_family: std::option::Option<std::string::String>,
|
||||||
|
expected_db_target: std::option::Option<std::string::String>,
|
||||||
|
local_event_kind: std::option::Option<std::string::String>,
|
||||||
|
) -> Self {
|
||||||
|
let discriminator_len = entry.discriminator_len.map(i64::from);
|
||||||
|
return Self::new(
|
||||||
|
entry.decoder_code.clone(),
|
||||||
|
entry.program_id.clone(),
|
||||||
|
entry.program_family.clone(),
|
||||||
|
entry.surface_kind.clone(),
|
||||||
|
entry.source_repo.clone(),
|
||||||
|
entry.source_path.clone(),
|
||||||
|
entry.entry_kind.clone(),
|
||||||
|
entry.entry_name.clone(),
|
||||||
|
entry.discriminator_hex.clone(),
|
||||||
|
discriminator_len,
|
||||||
|
event_family,
|
||||||
|
expected_db_target,
|
||||||
|
entry.proof_status.clone(),
|
||||||
|
local_event_kind,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(entry.notes.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<crate::DexEventCoverageEntryEntity> for DexEventCoverageEntryDto {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(entity: crate::DexEventCoverageEntryEntity) -> Result<Self, Self::Error> {
|
||||||
|
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
|
||||||
|
let created_at = match created_at_result {
|
||||||
|
Ok(created_at) => created_at.with_timezone(&chrono::Utc),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot parse dex event coverage created_at '{}': {}",
|
||||||
|
entity.created_at, error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
|
||||||
|
let updated_at = match updated_at_result {
|
||||||
|
Ok(updated_at) => updated_at.with_timezone(&chrono::Utc),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot parse dex event coverage updated_at '{}': {}",
|
||||||
|
entity.updated_at, error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return Ok(Self {
|
||||||
|
id: Some(entity.id),
|
||||||
|
coverage_key: entity.coverage_key,
|
||||||
|
decoder_code: entity.decoder_code,
|
||||||
|
program_id: entity.program_id,
|
||||||
|
program_family: entity.program_family,
|
||||||
|
surface_kind: entity.surface_kind,
|
||||||
|
source_repo: entity.source_repo,
|
||||||
|
source_path: entity.source_path,
|
||||||
|
entry_kind: entity.entry_kind,
|
||||||
|
entry_name: entity.entry_name,
|
||||||
|
discriminator_hex: entity.discriminator_hex,
|
||||||
|
discriminator_len: entity.discriminator_len,
|
||||||
|
event_family: entity.event_family,
|
||||||
|
expected_db_target: entity.expected_db_target,
|
||||||
|
proof_status: entity.proof_status,
|
||||||
|
local_event_kind: entity.local_event_kind,
|
||||||
|
observed_count: entity.observed_count,
|
||||||
|
materialized_count: entity.materialized_count,
|
||||||
|
trade_count: entity.trade_count,
|
||||||
|
first_signature: entity.first_signature,
|
||||||
|
last_signature: entity.last_signature,
|
||||||
|
notes: entity.notes,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Application-facing event coverage summary grouped by decoder.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct DexEventCoverageSummaryDto {
|
||||||
|
/// Internal decoder code.
|
||||||
|
pub decoder_code: std::string::String,
|
||||||
|
/// Number of listed coverage entries.
|
||||||
|
pub listed_entry_count: u64,
|
||||||
|
/// Number of entries wired to a local event kind.
|
||||||
|
pub decoded_entry_count: u64,
|
||||||
|
/// Number of entries observed at least once in local corpus counts.
|
||||||
|
pub observed_entry_count: u64,
|
||||||
|
/// Number of entries materialized at least once.
|
||||||
|
pub materialized_entry_count: u64,
|
||||||
|
/// Sum of local observed counts.
|
||||||
|
pub total_observed_count: u64,
|
||||||
|
/// Sum of local materialized counts.
|
||||||
|
pub total_materialized_count: u64,
|
||||||
|
/// Sum of trade counts produced by these entries.
|
||||||
|
pub trade_count: u64,
|
||||||
|
/// Number of decoded entries that remain audit-only.
|
||||||
|
pub audit_only_entry_count: u64,
|
||||||
|
/// Number of entries without an expected DB target.
|
||||||
|
pub missing_db_target_entry_count: u64,
|
||||||
|
/// Number of entries still upstream Git unverified.
|
||||||
|
pub upstream_git_unverified_entry_count: u64,
|
||||||
|
/// Number of entries mapped but not locally observed.
|
||||||
|
pub upstream_git_mapped_unverified_entry_count: u64,
|
||||||
|
/// Number of entries observed in local corpus.
|
||||||
|
pub upstream_git_local_corpus_observed_entry_count: u64,
|
||||||
|
/// Number of entries materialized from local corpus.
|
||||||
|
pub upstream_git_local_corpus_materialized_entry_count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DexEventCoverageSummaryDto {
|
||||||
|
fn convert_count(value: i64, field_name: &str) -> Result<u64, crate::Error> {
|
||||||
|
let result = u64::try_from(value);
|
||||||
|
match result {
|
||||||
|
Ok(converted) => return Ok(converted),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot convert dex event coverage summary {} '{}' to u64: {}",
|
||||||
|
field_name, value, error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<crate::DexEventCoverageSummaryEntity> for DexEventCoverageSummaryDto {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(entity: crate::DexEventCoverageSummaryEntity) -> Result<Self, Self::Error> {
|
||||||
|
let listed_entry_count =
|
||||||
|
match Self::convert_count(entity.listed_entry_count, "listed_entry_count") {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let decoded_entry_count =
|
||||||
|
match Self::convert_count(entity.decoded_entry_count, "decoded_entry_count") {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let observed_entry_count =
|
||||||
|
match Self::convert_count(entity.observed_entry_count, "observed_entry_count") {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let materialized_entry_count = match Self::convert_count(
|
||||||
|
entity.materialized_entry_count,
|
||||||
|
"materialized_entry_count",
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let total_observed_count =
|
||||||
|
match Self::convert_count(entity.total_observed_count, "total_observed_count") {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let total_materialized_count = match Self::convert_count(
|
||||||
|
entity.total_materialized_count,
|
||||||
|
"total_materialized_count",
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let trade_count = match Self::convert_count(entity.trade_count, "trade_count") {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let audit_only_entry_count =
|
||||||
|
match Self::convert_count(entity.audit_only_entry_count, "audit_only_entry_count") {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let missing_db_target_entry_count = match Self::convert_count(
|
||||||
|
entity.missing_db_target_entry_count,
|
||||||
|
"missing_db_target_entry_count",
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let upstream_git_unverified_entry_count = match Self::convert_count(
|
||||||
|
entity.upstream_git_unverified_entry_count,
|
||||||
|
"upstream_git_unverified_entry_count",
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let upstream_git_mapped_unverified_entry_count = match Self::convert_count(
|
||||||
|
entity.upstream_git_mapped_unverified_entry_count,
|
||||||
|
"upstream_git_mapped_unverified_entry_count",
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let upstream_git_local_corpus_observed_entry_count = match Self::convert_count(
|
||||||
|
entity.upstream_git_local_corpus_observed_entry_count,
|
||||||
|
"upstream_git_local_corpus_observed_entry_count",
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let upstream_git_local_corpus_materialized_entry_count = match Self::convert_count(
|
||||||
|
entity.upstream_git_local_corpus_materialized_entry_count,
|
||||||
|
"upstream_git_local_corpus_materialized_entry_count",
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
return Ok(Self {
|
||||||
|
decoder_code: entity.decoder_code,
|
||||||
|
listed_entry_count,
|
||||||
|
decoded_entry_count,
|
||||||
|
observed_entry_count,
|
||||||
|
materialized_entry_count,
|
||||||
|
total_observed_count,
|
||||||
|
total_materialized_count,
|
||||||
|
trade_count,
|
||||||
|
audit_only_entry_count,
|
||||||
|
missing_db_target_entry_count,
|
||||||
|
upstream_git_unverified_entry_count,
|
||||||
|
upstream_git_mapped_unverified_entry_count,
|
||||||
|
upstream_git_local_corpus_observed_entry_count,
|
||||||
|
upstream_git_local_corpus_materialized_entry_count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,34 @@ pub struct LocalPipelineDiagnosticSummaryDto {
|
|||||||
pub reward_event_count: i64,
|
pub reward_event_count: i64,
|
||||||
/// Total persisted pool administration events.
|
/// Total persisted pool administration events.
|
||||||
pub pool_admin_event_count: i64,
|
pub pool_admin_event_count: i64,
|
||||||
|
/// Event coverage entries listed from upstream registry sources.
|
||||||
|
pub event_coverage_listed_entry_count: u64,
|
||||||
|
/// Event coverage entries that have a local decoder event kind mapping.
|
||||||
|
pub event_coverage_decoded_entry_count: u64,
|
||||||
|
/// Event coverage entries observed at least once in the local corpus.
|
||||||
|
pub event_coverage_observed_entry_count: u64,
|
||||||
|
/// Event coverage entries materialized at least once into a DB target.
|
||||||
|
pub event_coverage_materialized_entry_count: u64,
|
||||||
|
/// Sum of decoded-event observations across coverage entries.
|
||||||
|
pub event_coverage_total_observed_count: u64,
|
||||||
|
/// Sum of materialized rows across coverage entries.
|
||||||
|
pub event_coverage_total_materialized_count: u64,
|
||||||
|
/// Sum of trade rows linked to coverage entries.
|
||||||
|
pub event_coverage_trade_count: u64,
|
||||||
|
/// Coverage entries intentionally expected to remain audit-only.
|
||||||
|
pub event_coverage_audit_only_entry_count: u64,
|
||||||
|
/// Coverage entries whose DB target is still missing or undecided.
|
||||||
|
pub event_coverage_missing_db_target_entry_count: u64,
|
||||||
|
/// Coverage entries still marked as upstream Git unverified.
|
||||||
|
pub event_coverage_upstream_git_unverified_entry_count: u64,
|
||||||
|
/// Coverage entries mapped to local semantics but not observed locally yet.
|
||||||
|
pub event_coverage_upstream_git_mapped_unverified_entry_count: u64,
|
||||||
|
/// Coverage entries observed in local corpus but not necessarily materialized.
|
||||||
|
pub event_coverage_upstream_git_local_corpus_observed_entry_count: u64,
|
||||||
|
/// Coverage entries observed and materialized from local corpus.
|
||||||
|
pub event_coverage_upstream_git_local_corpus_materialized_entry_count: u64,
|
||||||
|
/// Event coverage summaries grouped by decoder.
|
||||||
|
pub event_coverage_summaries: std::vec::Vec<crate::DexEventCoverageSummaryDto>,
|
||||||
/// Whether the local persisted pipeline has no blocking diagnostic issue.
|
/// Whether the local persisted pipeline has no blocking diagnostic issue.
|
||||||
pub diagnostics_clean: bool,
|
pub diagnostics_clean: bool,
|
||||||
/// Number of blocking diagnostic issues.
|
/// Number of blocking diagnostic issues.
|
||||||
@@ -123,8 +151,7 @@ pub struct LocalPipelineDiagnosticSummaryDto {
|
|||||||
/// Diagnostics grouped by DEX.
|
/// Diagnostics grouped by DEX.
|
||||||
pub dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>,
|
pub dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>,
|
||||||
/// Raydium surface diagnostics derived from the matrix and observed instructions.
|
/// Raydium surface diagnostics derived from the matrix and observed instructions.
|
||||||
pub raydium_surface_summaries:
|
pub raydium_surface_summaries: std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto>,
|
||||||
std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto>,
|
|
||||||
/// Diagnostics grouped by pair.
|
/// Diagnostics grouped by pair.
|
||||||
pub pair_summaries: std::vec::Vec<crate::LocalPairDiagnosticSummaryDto>,
|
pub pair_summaries: std::vec::Vec<crate::LocalPairDiagnosticSummaryDto>,
|
||||||
/// Diagnostics grouped by pair materialization/actionability class.
|
/// Diagnostics grouped by pair materialization/actionability class.
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ mod chain_transaction;
|
|||||||
mod db_metadata;
|
mod db_metadata;
|
||||||
mod db_runtime_event;
|
mod db_runtime_event;
|
||||||
mod dex;
|
mod dex;
|
||||||
mod dex_decoded_event;
|
|
||||||
mod dex_decode_replay_ledger;
|
mod dex_decode_replay_ledger;
|
||||||
|
mod dex_decoded_event;
|
||||||
|
mod dex_event_coverage_entry;
|
||||||
mod fee_event;
|
mod fee_event;
|
||||||
mod known_http_endpoint;
|
mod known_http_endpoint;
|
||||||
mod known_ws_endpoint;
|
mod known_ws_endpoint;
|
||||||
@@ -54,8 +55,10 @@ pub use chain_transaction::ChainTransactionEntity;
|
|||||||
pub use db_metadata::DbMetadataEntity;
|
pub use db_metadata::DbMetadataEntity;
|
||||||
pub use db_runtime_event::DbRuntimeEventEntity;
|
pub use db_runtime_event::DbRuntimeEventEntity;
|
||||||
pub use dex::DexEntity;
|
pub use dex::DexEntity;
|
||||||
pub use dex_decoded_event::DexDecodedEventEntity;
|
|
||||||
pub use dex_decode_replay_ledger::DexDecodeReplayLedgerEntity;
|
pub use dex_decode_replay_ledger::DexDecodeReplayLedgerEntity;
|
||||||
|
pub use dex_decoded_event::DexDecodedEventEntity;
|
||||||
|
pub use dex_event_coverage_entry::DexEventCoverageEntryEntity;
|
||||||
|
pub use dex_event_coverage_entry::DexEventCoverageSummaryEntity;
|
||||||
pub use fee_event::FeeEventEntity;
|
pub use fee_event::FeeEventEntity;
|
||||||
pub use known_http_endpoint::KnownHttpEndpointEntity;
|
pub use known_http_endpoint::KnownHttpEndpointEntity;
|
||||||
pub use known_ws_endpoint::KnownWsEndpointEntity;
|
pub use known_ws_endpoint::KnownWsEndpointEntity;
|
||||||
|
|||||||
89
kb_lib/src/db/entities/dex_event_coverage_entry.rs
Normal file
89
kb_lib/src/db/entities/dex_event_coverage_entry.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// file: kb_lib/src/db/entities/dex_event_coverage_entry.rs
|
||||||
|
|
||||||
|
//! Database entities for DEX event coverage rows.
|
||||||
|
|
||||||
|
/// Persisted coverage row for one listed upstream/local event entry.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||||
|
pub struct DexEventCoverageEntryEntity {
|
||||||
|
/// Internal row id.
|
||||||
|
pub id: i64,
|
||||||
|
/// Stable unique key used for idempotent upserts.
|
||||||
|
pub coverage_key: std::string::String,
|
||||||
|
/// Internal decoder code.
|
||||||
|
pub decoder_code: std::string::String,
|
||||||
|
/// Optional Solana program id associated with the source entry.
|
||||||
|
pub program_id: std::option::Option<std::string::String>,
|
||||||
|
/// Program family.
|
||||||
|
pub program_family: std::string::String,
|
||||||
|
/// Surface kind such as AMM, CLMM, launch, orderbook or aggregator.
|
||||||
|
pub surface_kind: std::string::String,
|
||||||
|
/// Source repository or registry source.
|
||||||
|
pub source_repo: std::option::Option<std::string::String>,
|
||||||
|
/// Source path inside the repository or registry source.
|
||||||
|
pub source_path: std::option::Option<std::string::String>,
|
||||||
|
/// Entry kind such as instruction, event, account, log or program_data.
|
||||||
|
pub entry_kind: std::string::String,
|
||||||
|
/// Source entry name.
|
||||||
|
pub entry_name: std::string::String,
|
||||||
|
/// Optional discriminator encoded as lowercase hexadecimal.
|
||||||
|
pub discriminator_hex: std::option::Option<std::string::String>,
|
||||||
|
/// Optional discriminator byte length.
|
||||||
|
pub discriminator_len: std::option::Option<i64>,
|
||||||
|
/// Normalized event family.
|
||||||
|
pub event_family: std::option::Option<std::string::String>,
|
||||||
|
/// Expected DB target table or audit-only target.
|
||||||
|
pub expected_db_target: std::option::Option<std::string::String>,
|
||||||
|
/// Proof status for this entry.
|
||||||
|
pub proof_status: std::string::String,
|
||||||
|
/// Local event kind produced by the decoder, when any.
|
||||||
|
pub local_event_kind: std::option::Option<std::string::String>,
|
||||||
|
/// Number of observed decoded/audit rows in the local corpus.
|
||||||
|
pub observed_count: i64,
|
||||||
|
/// Number of rows materialized into specialized business tables.
|
||||||
|
pub materialized_count: i64,
|
||||||
|
/// Number of trade rows produced from this entry.
|
||||||
|
pub trade_count: i64,
|
||||||
|
/// First local signature observed for this entry.
|
||||||
|
pub first_signature: std::option::Option<std::string::String>,
|
||||||
|
/// Last local signature observed for this entry.
|
||||||
|
pub last_signature: std::option::Option<std::string::String>,
|
||||||
|
/// Notes preserving uncertainty, mapping decisions or DB gaps.
|
||||||
|
pub notes: std::option::Option<std::string::String>,
|
||||||
|
/// Creation timestamp.
|
||||||
|
pub created_at: std::string::String,
|
||||||
|
/// Update timestamp.
|
||||||
|
pub updated_at: std::string::String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Aggregated event coverage row grouped by decoder.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||||
|
pub struct DexEventCoverageSummaryEntity {
|
||||||
|
/// Internal decoder code.
|
||||||
|
pub decoder_code: std::string::String,
|
||||||
|
/// Number of listed coverage entries.
|
||||||
|
pub listed_entry_count: i64,
|
||||||
|
/// Number of entries wired to a local event kind.
|
||||||
|
pub decoded_entry_count: i64,
|
||||||
|
/// Number of entries observed at least once in local corpus counts.
|
||||||
|
pub observed_entry_count: i64,
|
||||||
|
/// Number of entries materialized at least once.
|
||||||
|
pub materialized_entry_count: i64,
|
||||||
|
/// Sum of local observed counts.
|
||||||
|
pub total_observed_count: i64,
|
||||||
|
/// Sum of local materialized counts.
|
||||||
|
pub total_materialized_count: i64,
|
||||||
|
/// Sum of trade counts produced by these entries.
|
||||||
|
pub trade_count: i64,
|
||||||
|
/// Number of decoded entries that remain audit-only.
|
||||||
|
pub audit_only_entry_count: i64,
|
||||||
|
/// Number of entries without an expected DB target.
|
||||||
|
pub missing_db_target_entry_count: i64,
|
||||||
|
/// Number of entries still upstream Git unverified.
|
||||||
|
pub upstream_git_unverified_entry_count: i64,
|
||||||
|
/// Number of entries mapped but not locally observed.
|
||||||
|
pub upstream_git_mapped_unverified_entry_count: i64,
|
||||||
|
/// Number of entries observed in local corpus.
|
||||||
|
pub upstream_git_local_corpus_observed_entry_count: i64,
|
||||||
|
/// Number of entries materialized from local corpus.
|
||||||
|
pub upstream_git_local_corpus_materialized_entry_count: i64,
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ mod db_runtime_event;
|
|||||||
mod dex;
|
mod dex;
|
||||||
mod dex_decode_replay_ledger;
|
mod dex_decode_replay_ledger;
|
||||||
mod dex_decoded_event;
|
mod dex_decoded_event;
|
||||||
|
mod dex_event_coverage_entry;
|
||||||
mod fee_event;
|
mod fee_event;
|
||||||
mod known_http_endpoint;
|
mod known_http_endpoint;
|
||||||
mod known_ws_endpoint;
|
mod known_ws_endpoint;
|
||||||
@@ -76,6 +77,12 @@ pub use dex_decoded_event::query_dex_decoded_events_get_by_key;
|
|||||||
pub use dex_decoded_event::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
pub use dex_decoded_event::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
||||||
pub use dex_decoded_event::query_dex_decoded_events_list_by_transaction_id;
|
pub use dex_decoded_event::query_dex_decoded_events_list_by_transaction_id;
|
||||||
pub use dex_decoded_event::query_dex_decoded_events_upsert;
|
pub use dex_decoded_event::query_dex_decoded_events_upsert;
|
||||||
|
pub use dex_event_coverage_entry::query_dex_event_coverage_entries_delete_by_decoder;
|
||||||
|
pub use dex_event_coverage_entry::query_dex_event_coverage_entries_list_by_decoder;
|
||||||
|
pub use dex_event_coverage_entry::query_dex_event_coverage_entries_list_summary_by_decoder;
|
||||||
|
pub use dex_event_coverage_entry::query_dex_event_coverage_entries_refresh_local_counts;
|
||||||
|
pub use dex_event_coverage_entry::query_dex_event_coverage_entries_refresh_local_counts_by_decoder;
|
||||||
|
pub use dex_event_coverage_entry::query_dex_event_coverage_entries_upsert;
|
||||||
pub use fee_event::query_fee_events_get_by_decoded_event_id;
|
pub use fee_event::query_fee_events_get_by_decoded_event_id;
|
||||||
pub use fee_event::query_fee_events_list_recent;
|
pub use fee_event::query_fee_events_list_recent;
|
||||||
pub use fee_event::query_fee_events_upsert;
|
pub use fee_event::query_fee_events_upsert;
|
||||||
|
|||||||
778
kb_lib/src/db/queries/dex_event_coverage_entry.rs
Normal file
778
kb_lib/src/db/queries/dex_event_coverage_entry.rs
Normal file
@@ -0,0 +1,778 @@
|
|||||||
|
// file: kb_lib/src/db/queries/dex_event_coverage_entry.rs
|
||||||
|
|
||||||
|
//! Queries for `k_sol_dex_event_coverage_entries`.
|
||||||
|
|
||||||
|
/// Inserts or updates one DEX event coverage entry.
|
||||||
|
pub async fn query_dex_event_coverage_entries_upsert(
|
||||||
|
database: &crate::Database,
|
||||||
|
dto: &crate::DexEventCoverageEntryDto,
|
||||||
|
) -> Result<i64, crate::Error> {
|
||||||
|
match database.connection() {
|
||||||
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
|
let query_result = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO k_sol_dex_event_coverage_entries (
|
||||||
|
coverage_key,
|
||||||
|
decoder_code,
|
||||||
|
program_id,
|
||||||
|
program_family,
|
||||||
|
surface_kind,
|
||||||
|
source_repo,
|
||||||
|
source_path,
|
||||||
|
entry_kind,
|
||||||
|
entry_name,
|
||||||
|
discriminator_hex,
|
||||||
|
discriminator_len,
|
||||||
|
event_family,
|
||||||
|
expected_db_target,
|
||||||
|
proof_status,
|
||||||
|
local_event_kind,
|
||||||
|
observed_count,
|
||||||
|
materialized_count,
|
||||||
|
trade_count,
|
||||||
|
first_signature,
|
||||||
|
last_signature,
|
||||||
|
notes,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(coverage_key) DO UPDATE SET
|
||||||
|
decoder_code = excluded.decoder_code,
|
||||||
|
program_id = excluded.program_id,
|
||||||
|
program_family = excluded.program_family,
|
||||||
|
surface_kind = excluded.surface_kind,
|
||||||
|
source_repo = excluded.source_repo,
|
||||||
|
source_path = excluded.source_path,
|
||||||
|
entry_kind = excluded.entry_kind,
|
||||||
|
entry_name = excluded.entry_name,
|
||||||
|
discriminator_hex = excluded.discriminator_hex,
|
||||||
|
discriminator_len = excluded.discriminator_len,
|
||||||
|
event_family = excluded.event_family,
|
||||||
|
expected_db_target = excluded.expected_db_target,
|
||||||
|
proof_status = excluded.proof_status,
|
||||||
|
local_event_kind = excluded.local_event_kind,
|
||||||
|
observed_count = excluded.observed_count,
|
||||||
|
materialized_count = excluded.materialized_count,
|
||||||
|
trade_count = excluded.trade_count,
|
||||||
|
first_signature = excluded.first_signature,
|
||||||
|
last_signature = excluded.last_signature,
|
||||||
|
notes = excluded.notes,
|
||||||
|
updated_at = excluded.updated_at
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(dto.coverage_key.clone())
|
||||||
|
.bind(dto.decoder_code.clone())
|
||||||
|
.bind(dto.program_id.clone())
|
||||||
|
.bind(dto.program_family.clone())
|
||||||
|
.bind(dto.surface_kind.clone())
|
||||||
|
.bind(dto.source_repo.clone())
|
||||||
|
.bind(dto.source_path.clone())
|
||||||
|
.bind(dto.entry_kind.clone())
|
||||||
|
.bind(dto.entry_name.clone())
|
||||||
|
.bind(dto.discriminator_hex.clone())
|
||||||
|
.bind(dto.discriminator_len)
|
||||||
|
.bind(dto.event_family.clone())
|
||||||
|
.bind(dto.expected_db_target.clone())
|
||||||
|
.bind(dto.proof_status.clone())
|
||||||
|
.bind(dto.local_event_kind.clone())
|
||||||
|
.bind(dto.observed_count)
|
||||||
|
.bind(dto.materialized_count)
|
||||||
|
.bind(dto.trade_count)
|
||||||
|
.bind(dto.first_signature.clone())
|
||||||
|
.bind(dto.last_signature.clone())
|
||||||
|
.bind(dto.notes.clone())
|
||||||
|
.bind(dto.created_at.to_rfc3339())
|
||||||
|
.bind(dto.updated_at.to_rfc3339())
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
if let Err(error) = query_result {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot upsert k_sol_dex_event_coverage_entries on sqlite: {}",
|
||||||
|
error
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||||
|
r#"
|
||||||
|
SELECT id
|
||||||
|
FROM k_sol_dex_event_coverage_entries
|
||||||
|
WHERE coverage_key = ?
|
||||||
|
LIMIT 1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(dto.coverage_key.clone())
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await;
|
||||||
|
match id_result {
|
||||||
|
Ok(id) => return Ok(id),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot fetch k_sol_dex_event_coverage_entries id for coverage_key '{}' on sqlite: {}",
|
||||||
|
dto.coverage_key, error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all event coverage rows for one decoder.
|
||||||
|
pub async fn query_dex_event_coverage_entries_delete_by_decoder(
|
||||||
|
database: &crate::Database,
|
||||||
|
decoder_code: &str,
|
||||||
|
) -> Result<u64, crate::Error> {
|
||||||
|
match database.connection() {
|
||||||
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
|
let query_result = sqlx::query(
|
||||||
|
r#"
|
||||||
|
DELETE FROM k_sol_dex_event_coverage_entries
|
||||||
|
WHERE decoder_code = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(decoder_code.to_string())
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
match query_result {
|
||||||
|
Ok(query_result) => return Ok(query_result.rows_affected()),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot delete k_sol_dex_event_coverage_entries for decoder_code '{}' on sqlite: {}",
|
||||||
|
decoder_code, error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists event coverage rows for one decoder.
|
||||||
|
pub async fn query_dex_event_coverage_entries_list_by_decoder(
|
||||||
|
database: &crate::Database,
|
||||||
|
decoder_code: &str,
|
||||||
|
) -> Result<std::vec::Vec<crate::DexEventCoverageEntryDto>, crate::Error> {
|
||||||
|
match database.connection() {
|
||||||
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
|
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::DexEventCoverageEntryEntity>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
coverage_key,
|
||||||
|
decoder_code,
|
||||||
|
program_id,
|
||||||
|
program_family,
|
||||||
|
surface_kind,
|
||||||
|
source_repo,
|
||||||
|
source_path,
|
||||||
|
entry_kind,
|
||||||
|
entry_name,
|
||||||
|
discriminator_hex,
|
||||||
|
discriminator_len,
|
||||||
|
event_family,
|
||||||
|
expected_db_target,
|
||||||
|
proof_status,
|
||||||
|
local_event_kind,
|
||||||
|
observed_count,
|
||||||
|
materialized_count,
|
||||||
|
trade_count,
|
||||||
|
first_signature,
|
||||||
|
last_signature,
|
||||||
|
notes,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM k_sol_dex_event_coverage_entries
|
||||||
|
WHERE decoder_code = ?
|
||||||
|
ORDER BY entry_kind ASC, entry_name ASC, discriminator_hex ASC, id ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(decoder_code.to_string())
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await;
|
||||||
|
let entities = match query_result {
|
||||||
|
Ok(entities) => entities,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot list k_sol_dex_event_coverage_entries for decoder_code '{}' on sqlite: {}",
|
||||||
|
decoder_code, error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return dex_event_coverage_entry_entities_to_dtos(entities);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists event coverage summaries grouped by decoder.
|
||||||
|
pub async fn query_dex_event_coverage_entries_list_summary_by_decoder(
|
||||||
|
database: &crate::Database,
|
||||||
|
) -> Result<std::vec::Vec<crate::DexEventCoverageSummaryDto>, crate::Error> {
|
||||||
|
match database.connection() {
|
||||||
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
|
let query_result =
|
||||||
|
sqlx::query_as::<sqlx::Sqlite, crate::DexEventCoverageSummaryEntity>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
decoder_code,
|
||||||
|
COUNT(*) AS listed_entry_count,
|
||||||
|
SUM(CASE WHEN local_event_kind IS NOT NULL AND local_event_kind <> '' THEN 1 ELSE 0 END) AS decoded_entry_count,
|
||||||
|
SUM(CASE WHEN observed_count > 0 THEN 1 ELSE 0 END) AS observed_entry_count,
|
||||||
|
SUM(CASE WHEN materialized_count > 0 THEN 1 ELSE 0 END) AS materialized_entry_count,
|
||||||
|
COALESCE(SUM(observed_count), 0) AS total_observed_count,
|
||||||
|
COALESCE(SUM(materialized_count), 0) AS total_materialized_count,
|
||||||
|
COALESCE(SUM(trade_count), 0) AS trade_count,
|
||||||
|
SUM(CASE WHEN expected_db_target = 'k_sol_dex_decoded_events_only' THEN 1 ELSE 0 END) AS audit_only_entry_count,
|
||||||
|
SUM(CASE WHEN expected_db_target IS NULL OR expected_db_target = '' THEN 1 ELSE 0 END) AS missing_db_target_entry_count,
|
||||||
|
SUM(CASE WHEN proof_status = 'upstream_git_unverified' THEN 1 ELSE 0 END) AS upstream_git_unverified_entry_count,
|
||||||
|
SUM(CASE WHEN proof_status = 'upstream_git_mapped_unverified' THEN 1 ELSE 0 END) AS upstream_git_mapped_unverified_entry_count,
|
||||||
|
SUM(CASE WHEN proof_status = 'upstream_git_local_corpus_observed' THEN 1 ELSE 0 END) AS upstream_git_local_corpus_observed_entry_count,
|
||||||
|
SUM(CASE WHEN proof_status = 'upstream_git_local_corpus_materialized' THEN 1 ELSE 0 END) AS upstream_git_local_corpus_materialized_entry_count
|
||||||
|
FROM k_sol_dex_event_coverage_entries
|
||||||
|
GROUP BY decoder_code
|
||||||
|
ORDER BY decoder_code ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await;
|
||||||
|
let entities = match query_result {
|
||||||
|
Ok(entities) => entities,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot list k_sol_dex_event_coverage_entries summaries on sqlite: {}",
|
||||||
|
error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut dtos = std::vec::Vec::new();
|
||||||
|
for entity in entities {
|
||||||
|
let dto_result = crate::DexEventCoverageSummaryDto::try_from(entity);
|
||||||
|
let dto = match dto_result {
|
||||||
|
Ok(dto) => dto,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
dtos.push(dto);
|
||||||
|
}
|
||||||
|
return Ok(dtos);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refreshes local observed/materialized counts for every coverage row.
|
||||||
|
pub async fn query_dex_event_coverage_entries_refresh_local_counts(
|
||||||
|
database: &crate::Database,
|
||||||
|
) -> Result<u64, crate::Error> {
|
||||||
|
return query_dex_event_coverage_entries_refresh_local_counts_internal(database, None).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refreshes local observed/materialized counts for one decoder coverage subset.
|
||||||
|
pub async fn query_dex_event_coverage_entries_refresh_local_counts_by_decoder(
|
||||||
|
database: &crate::Database,
|
||||||
|
decoder_code: &str,
|
||||||
|
) -> Result<u64, crate::Error> {
|
||||||
|
return query_dex_event_coverage_entries_refresh_local_counts_internal(
|
||||||
|
database,
|
||||||
|
Some(decoder_code),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn query_dex_event_coverage_entries_refresh_local_counts_internal(
|
||||||
|
database: &crate::Database,
|
||||||
|
decoder_code: std::option::Option<&str>,
|
||||||
|
) -> Result<u64, crate::Error> {
|
||||||
|
match database.connection() {
|
||||||
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
|
let now = chrono::Utc::now().to_rfc3339();
|
||||||
|
let decoder_code_filter: std::option::Option<String> = match decoder_code {
|
||||||
|
Some(value) => Some(value.to_string()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let update_result = sqlx::query(
|
||||||
|
r#"
|
||||||
|
UPDATE k_sol_dex_event_coverage_entries
|
||||||
|
SET
|
||||||
|
observed_count = (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
trade_count = (
|
||||||
|
SELECT COUNT(te.id)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_trade_events te ON te.decoded_event_id = de.id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
materialized_count = CASE
|
||||||
|
WHEN expected_db_target = 'k_sol_trade_events' THEN (
|
||||||
|
SELECT COUNT(te.id)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_trade_events te ON te.decoded_event_id = de.id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHEN expected_db_target = 'k_sol_liquidity_events' THEN (
|
||||||
|
SELECT COUNT(le.id)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHEN expected_db_target = 'k_sol_pool_lifecycle_events' THEN (
|
||||||
|
SELECT COUNT(pe.id)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_pool_lifecycle_events pe ON pe.decoded_event_id = de.id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHEN expected_db_target = 'k_sol_fee_events' THEN (
|
||||||
|
SELECT COUNT(fe.id)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHEN expected_db_target = 'k_sol_reward_events' THEN (
|
||||||
|
SELECT COUNT(re.id)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_reward_events re ON re.decoded_event_id = de.id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHEN expected_db_target = 'k_sol_pool_admin_events' THEN (
|
||||||
|
SELECT COUNT(ae.id)
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ELSE materialized_count
|
||||||
|
END,
|
||||||
|
first_signature = (
|
||||||
|
SELECT tx.signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx ON tx.id = de.transaction_id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY COALESCE(tx.slot, 0) ASC, tx.id ASC
|
||||||
|
LIMIT 1
|
||||||
|
),
|
||||||
|
last_signature = (
|
||||||
|
SELECT tx.signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx ON tx.id = de.transaction_id
|
||||||
|
WHERE (
|
||||||
|
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||||
|
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||||
|
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||||
|
AND (
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY COALESCE(tx.slot, 0) DESC, tx.id DESC
|
||||||
|
LIMIT 1
|
||||||
|
),
|
||||||
|
updated_at = ?
|
||||||
|
WHERE (? IS NULL OR decoder_code = ?)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(now.clone())
|
||||||
|
.bind(decoder_code_filter.clone())
|
||||||
|
.bind(decoder_code_filter.clone())
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
let refreshed_entry_count = match update_result {
|
||||||
|
Ok(result) => result.rows_affected(),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot refresh k_sol_dex_event_coverage_entries local counts on sqlite: {}",
|
||||||
|
error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let proof_result = sqlx::query(
|
||||||
|
r#"
|
||||||
|
UPDATE k_sol_dex_event_coverage_entries
|
||||||
|
SET
|
||||||
|
proof_status = CASE
|
||||||
|
WHEN materialized_count > 0 OR trade_count > 0 THEN 'upstream_git_local_corpus_materialized'
|
||||||
|
WHEN observed_count > 0 THEN 'upstream_git_local_corpus_observed'
|
||||||
|
WHEN local_event_kind IS NOT NULL AND local_event_kind <> '' THEN 'upstream_git_mapped_unverified'
|
||||||
|
ELSE proof_status
|
||||||
|
END,
|
||||||
|
updated_at = ?
|
||||||
|
WHERE (? IS NULL OR decoder_code = ?)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(now)
|
||||||
|
.bind(decoder_code_filter.clone())
|
||||||
|
.bind(decoder_code_filter)
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
if let Err(error) = proof_result {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot refresh k_sol_dex_event_coverage_entries proof statuses on sqlite: {}",
|
||||||
|
error
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
return Ok(refreshed_entry_count);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dex_event_coverage_entry_entities_to_dtos(
|
||||||
|
entities: std::vec::Vec<crate::DexEventCoverageEntryEntity>,
|
||||||
|
) -> Result<std::vec::Vec<crate::DexEventCoverageEntryDto>, crate::Error> {
|
||||||
|
let mut dtos = std::vec::Vec::new();
|
||||||
|
for entity in entities {
|
||||||
|
let dto_result = crate::DexEventCoverageEntryDto::try_from(entity);
|
||||||
|
let dto = match dto_result {
|
||||||
|
Ok(dto) => dto,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
dtos.push(dto);
|
||||||
|
}
|
||||||
|
return Ok(dtos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
async fn make_database() -> crate::Database {
|
||||||
|
let tempdir = tempfile::tempdir().expect("tempdir must succeed");
|
||||||
|
let database_path = tempdir.path().join("dex_event_coverage.sqlite3");
|
||||||
|
let config = crate::DatabaseConfig {
|
||||||
|
enabled: true,
|
||||||
|
backend: crate::DatabaseBackend::Sqlite,
|
||||||
|
sqlite: crate::SqliteDatabaseConfig {
|
||||||
|
path: database_path.to_string_lossy().to_string(),
|
||||||
|
create_if_missing: true,
|
||||||
|
busy_timeout_ms: 5000,
|
||||||
|
max_connections: 1,
|
||||||
|
auto_initialize_schema: true,
|
||||||
|
use_wal: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return crate::Database::connect_and_initialize(&config)
|
||||||
|
.await
|
||||||
|
.expect("database init must succeed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn dex_event_coverage_entry_roundtrip_and_summary_work() {
|
||||||
|
let database = make_database().await;
|
||||||
|
let upstream_service = crate::UpstreamRegistryService::new();
|
||||||
|
let request = crate::UpstreamRegistrySearchRequestDto {
|
||||||
|
decoder_code: Some("raydium-cpmm".to_string()),
|
||||||
|
program_id: None,
|
||||||
|
program_family: None,
|
||||||
|
surface_kind: None,
|
||||||
|
entry_kind: Some(crate::ENTRY_KIND_INSTRUCTION.to_string()),
|
||||||
|
proof_status: None,
|
||||||
|
limit: Some(1),
|
||||||
|
};
|
||||||
|
let upstream_result = upstream_service.search(&request);
|
||||||
|
assert_eq!(upstream_result.entries.len(), 1);
|
||||||
|
let upstream_entry = upstream_result.entries.first().expect("entry must exist");
|
||||||
|
let mut dto = crate::DexEventCoverageEntryDto::from_upstream_registry_entry(
|
||||||
|
upstream_entry,
|
||||||
|
Some("swap".to_string()),
|
||||||
|
Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string()),
|
||||||
|
Some("raydium_cpmm.swap".to_string()),
|
||||||
|
);
|
||||||
|
dto.proof_status = crate::PROOF_STATUS_UPSTREAM_GIT_LOCAL_CORPUS_OBSERVED.to_string();
|
||||||
|
dto.observed_count = 3;
|
||||||
|
let id = crate::query_dex_event_coverage_entries_upsert(&database, &dto)
|
||||||
|
.await
|
||||||
|
.expect("coverage upsert must succeed");
|
||||||
|
assert!(id > 0);
|
||||||
|
let rows =
|
||||||
|
crate::query_dex_event_coverage_entries_list_by_decoder(&database, "raydium-cpmm")
|
||||||
|
.await
|
||||||
|
.expect("coverage list must succeed");
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert_eq!(rows[0].observed_count, 3);
|
||||||
|
let summaries = crate::query_dex_event_coverage_entries_list_summary_by_decoder(&database)
|
||||||
|
.await
|
||||||
|
.expect("coverage summary must succeed");
|
||||||
|
assert_eq!(summaries.len(), 1);
|
||||||
|
assert_eq!(summaries[0].decoder_code, "raydium-cpmm");
|
||||||
|
assert_eq!(summaries[0].listed_entry_count, 1);
|
||||||
|
assert_eq!(summaries[0].decoded_entry_count, 1);
|
||||||
|
assert_eq!(summaries[0].observed_entry_count, 1);
|
||||||
|
assert_eq!(summaries[0].total_observed_count, 3);
|
||||||
|
assert_eq!(summaries[0].audit_only_entry_count, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -246,6 +246,26 @@ pub(crate) async fn ensure_schema(database: &crate::Database) -> Result<(), crat
|
|||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
|
let result = create_tbl_dex_event_coverage_entries(pool).await;
|
||||||
|
if let Err(error) = result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
let result = create_uix_dex_event_coverage_entries_key(pool).await;
|
||||||
|
if let Err(error) = result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
let result = create_idx_dex_event_coverage_entries_decoder(pool).await;
|
||||||
|
if let Err(error) = result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
let result = create_idx_dex_event_coverage_entries_proof_status(pool).await;
|
||||||
|
if let Err(error) = result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
let result = create_idx_dex_event_coverage_entries_event_family(pool).await;
|
||||||
|
if let Err(error) = result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
let result = create_tbl_transaction_classifications(pool).await;
|
let result = create_tbl_transaction_classifications(pool).await;
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
return Err(error);
|
return Err(error);
|
||||||
@@ -1462,9 +1482,7 @@ ON k_sol_dex_decoded_events (transaction_id, instruction_id, event_kind)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates `k_sol_dex_decode_replay_ledger`.
|
/// Creates `k_sol_dex_decode_replay_ledger`.
|
||||||
async fn create_tbl_dex_decode_replay_ledger(
|
async fn create_tbl_dex_decode_replay_ledger(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> {
|
||||||
pool: &sqlx::SqlitePool,
|
|
||||||
) -> Result<(), crate::Error> {
|
|
||||||
return execute_sqlite_schema_statement(
|
return execute_sqlite_schema_statement(
|
||||||
pool,
|
pool,
|
||||||
"create_tbl_dex_decode_replay_ledger",
|
"create_tbl_dex_decode_replay_ledger",
|
||||||
@@ -1535,6 +1553,105 @@ ON k_sol_dex_decode_replay_ledger (decode_status, certainty, force_replay_requir
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates `k_sol_dex_event_coverage_entries`.
|
||||||
|
async fn create_tbl_dex_event_coverage_entries(
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
return execute_sqlite_schema_statement(
|
||||||
|
pool,
|
||||||
|
"create_tbl_dex_event_coverage_entries",
|
||||||
|
r#"
|
||||||
|
CREATE TABLE IF NOT EXISTS k_sol_dex_event_coverage_entries (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
coverage_key TEXT NOT NULL,
|
||||||
|
decoder_code TEXT NOT NULL,
|
||||||
|
program_id TEXT NULL,
|
||||||
|
program_family TEXT NOT NULL,
|
||||||
|
surface_kind TEXT NOT NULL,
|
||||||
|
source_repo TEXT NULL,
|
||||||
|
source_path TEXT NULL,
|
||||||
|
entry_kind TEXT NOT NULL,
|
||||||
|
entry_name TEXT NOT NULL,
|
||||||
|
discriminator_hex TEXT NULL,
|
||||||
|
discriminator_len INTEGER NULL,
|
||||||
|
event_family TEXT NULL,
|
||||||
|
expected_db_target TEXT NULL,
|
||||||
|
proof_status TEXT NOT NULL,
|
||||||
|
local_event_kind TEXT NULL,
|
||||||
|
observed_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
materialized_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trade_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
first_signature TEXT NULL,
|
||||||
|
last_signature TEXT NULL,
|
||||||
|
notes TEXT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates unique index on `k_sol_dex_event_coverage_entries(coverage_key)`.
|
||||||
|
async fn create_uix_dex_event_coverage_entries_key(
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
return execute_sqlite_schema_statement(
|
||||||
|
pool,
|
||||||
|
"create_uix_dex_event_coverage_entries_key",
|
||||||
|
r#"
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uix_dex_event_coverage_entries_key
|
||||||
|
ON k_sol_dex_event_coverage_entries (coverage_key)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates index on `k_sol_dex_event_coverage_entries(decoder_code)`.
|
||||||
|
async fn create_idx_dex_event_coverage_entries_decoder(
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
return execute_sqlite_schema_statement(
|
||||||
|
pool,
|
||||||
|
"create_idx_dex_event_coverage_entries_decoder",
|
||||||
|
r#"
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_dex_event_coverage_entries_decoder
|
||||||
|
ON k_sol_dex_event_coverage_entries (decoder_code, entry_kind, entry_name)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates index on `k_sol_dex_event_coverage_entries(proof_status)`.
|
||||||
|
async fn create_idx_dex_event_coverage_entries_proof_status(
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
return execute_sqlite_schema_statement(
|
||||||
|
pool,
|
||||||
|
"create_idx_dex_event_coverage_entries_proof_status",
|
||||||
|
r#"
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_dex_event_coverage_entries_proof_status
|
||||||
|
ON k_sol_dex_event_coverage_entries (proof_status, decoder_code)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates index on `k_sol_dex_event_coverage_entries(event_family)`.
|
||||||
|
async fn create_idx_dex_event_coverage_entries_event_family(
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
return execute_sqlite_schema_statement(
|
||||||
|
pool,
|
||||||
|
"create_idx_dex_event_coverage_entries_event_family",
|
||||||
|
r#"
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_dex_event_coverage_entries_event_family
|
||||||
|
ON k_sol_dex_event_coverage_entries (event_family, expected_db_target)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn create_tbl_launch_surfaces(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> {
|
async fn create_tbl_launch_surfaces(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> {
|
||||||
return execute_sqlite_schema_statement(
|
return execute_sqlite_schema_statement(
|
||||||
pool,
|
pool,
|
||||||
|
|||||||
473
kb_lib/src/dex_event_coverage.rs
Normal file
473
kb_lib/src/dex_event_coverage.rs
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
// file: kb_lib/src/dex_event_coverage.rs
|
||||||
|
|
||||||
|
//! Event coverage synchronization and reporting service.
|
||||||
|
//!
|
||||||
|
//! This service bridges the read-only upstream registry and the persisted
|
||||||
|
//! coverage table. It does not decode transactions and never materializes
|
||||||
|
//! trades, metrics or candles.
|
||||||
|
|
||||||
|
/// Result of one event coverage synchronization pass.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DexEventCoverageSyncResult {
|
||||||
|
/// Optional decoder filter used for this synchronization pass.
|
||||||
|
pub decoder_code: std::option::Option<std::string::String>,
|
||||||
|
/// Number of upstream registry entries selected by the filter.
|
||||||
|
pub upstream_entry_count: usize,
|
||||||
|
/// Number of coverage rows upserted from the upstream registry.
|
||||||
|
pub upserted_entry_count: usize,
|
||||||
|
/// Number of coverage rows touched by the local observation refresh.
|
||||||
|
pub refreshed_entry_count: u64,
|
||||||
|
/// Aggregated coverage summaries after synchronization.
|
||||||
|
pub summaries: std::vec::Vec<crate::DexEventCoverageSummaryDto>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service used to persist and refresh DEX event coverage rows.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DexEventCoverageService {
|
||||||
|
database: std::sync::Arc<crate::Database>,
|
||||||
|
upstream_registry: crate::UpstreamRegistryService,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DexEventCoverageService {
|
||||||
|
/// Creates a new event coverage service.
|
||||||
|
pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
|
||||||
|
return Self {
|
||||||
|
database,
|
||||||
|
upstream_registry: crate::UpstreamRegistryService::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Synchronizes static upstream registry entries into SQLite coverage rows.
|
||||||
|
///
|
||||||
|
/// The resulting rows are still discovery/audit metadata. A row can become
|
||||||
|
/// observed or materialized only through local corpus replay and explicit
|
||||||
|
/// count refreshes.
|
||||||
|
pub async fn sync_upstream_registry(
|
||||||
|
&self,
|
||||||
|
decoder_code: std::option::Option<std::string::String>,
|
||||||
|
) -> Result<crate::DexEventCoverageSyncResult, crate::Error> {
|
||||||
|
let request = crate::UpstreamRegistrySearchRequestDto {
|
||||||
|
decoder_code: decoder_code.clone(),
|
||||||
|
program_id: None,
|
||||||
|
program_family: None,
|
||||||
|
surface_kind: None,
|
||||||
|
entry_kind: None,
|
||||||
|
proof_status: None,
|
||||||
|
limit: None,
|
||||||
|
};
|
||||||
|
let search_result = self.upstream_registry.search(&request);
|
||||||
|
let mut upserted_entry_count = 0_usize;
|
||||||
|
for entry in &search_result.entries {
|
||||||
|
let coverage_entry = build_coverage_entry_from_upstream(entry);
|
||||||
|
let upsert_result = crate::query_dex_event_coverage_entries_upsert(
|
||||||
|
self.database.as_ref(),
|
||||||
|
&coverage_entry,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match upsert_result {
|
||||||
|
Ok(_) => upserted_entry_count += 1,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let refreshed_entry_count = match &decoder_code {
|
||||||
|
Some(decoder_code) => {
|
||||||
|
let refresh_result =
|
||||||
|
crate::query_dex_event_coverage_entries_refresh_local_counts_by_decoder(
|
||||||
|
self.database.as_ref(),
|
||||||
|
decoder_code.as_str(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match refresh_result {
|
||||||
|
Ok(refreshed_entry_count) => refreshed_entry_count,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let refresh_result = crate::query_dex_event_coverage_entries_refresh_local_counts(
|
||||||
|
self.database.as_ref(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match refresh_result {
|
||||||
|
Ok(refreshed_entry_count) => refreshed_entry_count,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let summaries_result =
|
||||||
|
crate::query_dex_event_coverage_entries_list_summary_by_decoder(self.database.as_ref())
|
||||||
|
.await;
|
||||||
|
let summaries = match summaries_result {
|
||||||
|
Ok(summaries) => summaries,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
return Ok(crate::DexEventCoverageSyncResult {
|
||||||
|
decoder_code,
|
||||||
|
upstream_entry_count: search_result.entries.len(),
|
||||||
|
upserted_entry_count,
|
||||||
|
refreshed_entry_count,
|
||||||
|
summaries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refreshes observed, materialized and proof-status counters from local DB rows.
|
||||||
|
pub async fn refresh_local_counts(
|
||||||
|
&self,
|
||||||
|
decoder_code: std::option::Option<std::string::String>,
|
||||||
|
) -> Result<crate::DexEventCoverageSyncResult, crate::Error> {
|
||||||
|
let refreshed_entry_count = match &decoder_code {
|
||||||
|
Some(decoder_code) => {
|
||||||
|
let refresh_result =
|
||||||
|
crate::query_dex_event_coverage_entries_refresh_local_counts_by_decoder(
|
||||||
|
self.database.as_ref(),
|
||||||
|
decoder_code.as_str(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match refresh_result {
|
||||||
|
Ok(refreshed_entry_count) => refreshed_entry_count,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let refresh_result = crate::query_dex_event_coverage_entries_refresh_local_counts(
|
||||||
|
self.database.as_ref(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match refresh_result {
|
||||||
|
Ok(refreshed_entry_count) => refreshed_entry_count,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let summaries_result =
|
||||||
|
crate::query_dex_event_coverage_entries_list_summary_by_decoder(self.database.as_ref())
|
||||||
|
.await;
|
||||||
|
let summaries = match summaries_result {
|
||||||
|
Ok(summaries) => summaries,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
return Ok(crate::DexEventCoverageSyncResult {
|
||||||
|
decoder_code,
|
||||||
|
upstream_entry_count: 0,
|
||||||
|
upserted_entry_count: 0,
|
||||||
|
refreshed_entry_count,
|
||||||
|
summaries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_coverage_entry_from_upstream(
|
||||||
|
entry: &crate::UpstreamRegistryEntryDto,
|
||||||
|
) -> crate::DexEventCoverageEntryDto {
|
||||||
|
let event_family = infer_event_family(entry.entry_name.as_str(), entry.entry_kind.as_str());
|
||||||
|
let expected_db_target =
|
||||||
|
infer_expected_db_target(event_family.as_deref(), entry.entry_kind.as_str());
|
||||||
|
let local_event_kind =
|
||||||
|
known_local_event_kind(entry.decoder_code.as_str(), entry.entry_name.as_str());
|
||||||
|
let mut coverage_entry = crate::DexEventCoverageEntryDto::from_upstream_registry_entry(
|
||||||
|
entry,
|
||||||
|
event_family,
|
||||||
|
expected_db_target,
|
||||||
|
local_event_kind.clone(),
|
||||||
|
);
|
||||||
|
if local_event_kind.is_some() && coverage_entry.observed_count == 0 {
|
||||||
|
coverage_entry.proof_status =
|
||||||
|
crate::PROOF_STATUS_UPSTREAM_GIT_MAPPED_UNVERIFIED.to_string();
|
||||||
|
}
|
||||||
|
return coverage_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infer_expected_db_target(
|
||||||
|
event_family: std::option::Option<&str>,
|
||||||
|
entry_kind: &str,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
if entry_kind == crate::ENTRY_KIND_PROGRAM || entry_kind == crate::ENTRY_KIND_ACCOUNT {
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||||
|
}
|
||||||
|
let family = match event_family {
|
||||||
|
Some(family) => family,
|
||||||
|
None => {
|
||||||
|
return Some(
|
||||||
|
crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let target = match family {
|
||||||
|
"swap" => crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS,
|
||||||
|
"pool_create" => crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS,
|
||||||
|
"liquidity_add" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS,
|
||||||
|
"liquidity_remove" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS,
|
||||||
|
"position_open" => crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS,
|
||||||
|
"position_close" => crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS,
|
||||||
|
"fee" => crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS,
|
||||||
|
"reward" => crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS,
|
||||||
|
"admin_config" => crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS,
|
||||||
|
"mint" => crate::DexEventCoverageEntryDto::DB_TARGET_TOKEN_MINT_EVENTS,
|
||||||
|
"burn" => crate::DexEventCoverageEntryDto::DB_TARGET_TOKEN_BURN_EVENTS,
|
||||||
|
"transfer" => crate::DexEventCoverageEntryDto::DB_TARGET_TOKEN_TRANSFER_EVENTS,
|
||||||
|
"account_create" => crate::DexEventCoverageEntryDto::DB_TARGET_TOKEN_ACCOUNT_EVENTS,
|
||||||
|
"account_close" => crate::DexEventCoverageEntryDto::DB_TARGET_TOKEN_ACCOUNT_EVENTS,
|
||||||
|
"wrap_sol" => crate::DexEventCoverageEntryDto::DB_TARGET_TOKEN_ACCOUNT_EVENTS,
|
||||||
|
"unwrap_sol" => crate::DexEventCoverageEntryDto::DB_TARGET_TOKEN_ACCOUNT_EVENTS,
|
||||||
|
"order_place" => crate::DexEventCoverageEntryDto::DB_TARGET_ORDERBOOK_EVENTS,
|
||||||
|
"order_cancel" => crate::DexEventCoverageEntryDto::DB_TARGET_ORDERBOOK_EVENTS,
|
||||||
|
"order_fill" => crate::DexEventCoverageEntryDto::DB_TARGET_ORDERBOOK_EVENTS,
|
||||||
|
"consume_events" => crate::DexEventCoverageEntryDto::DB_TARGET_ORDERBOOK_EVENTS,
|
||||||
|
"settle_funds" => crate::DexEventCoverageEntryDto::DB_TARGET_ORDERBOOK_EVENTS,
|
||||||
|
"vault_deposit" => crate::DexEventCoverageEntryDto::DB_TARGET_VAULT_EVENTS,
|
||||||
|
"vault_withdraw" => crate::DexEventCoverageEntryDto::DB_TARGET_VAULT_EVENTS,
|
||||||
|
"lock" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_LOCK_EVENTS,
|
||||||
|
"unlock" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_LOCK_EVENTS,
|
||||||
|
"launch" => crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS,
|
||||||
|
"migration" => crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS,
|
||||||
|
"stake" => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY,
|
||||||
|
"unstake" => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY,
|
||||||
|
_ => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY,
|
||||||
|
};
|
||||||
|
return Some(target.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infer_event_family(
|
||||||
|
entry_name: &str,
|
||||||
|
entry_kind: &str,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
if entry_kind == crate::ENTRY_KIND_PROGRAM {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let normalized = entry_name.to_ascii_lowercase();
|
||||||
|
if contains_any(normalized.as_str(), &["swap", "buy", "sell", "trade"]) {
|
||||||
|
return Some("swap".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["create_pool", "initialize_pool", "initialize2"])
|
||||||
|
|| normalized == "initialize"
|
||||||
|
|| normalized.starts_with("initialize_")
|
||||||
|
{
|
||||||
|
return Some("pool_create".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["add_liquidity", "increase_liquidity", "deposit"])
|
||||||
|
|| normalized.contains("bootstrap_liquidity")
|
||||||
|
{
|
||||||
|
return Some("liquidity_add".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["remove_liquidity", "decrease_liquidity", "withdraw"])
|
||||||
|
&& !normalized.contains("funds")
|
||||||
|
{
|
||||||
|
return Some("liquidity_remove".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(
|
||||||
|
normalized.as_str(),
|
||||||
|
&["open_position", "initialize_position", "position_create"],
|
||||||
|
) {
|
||||||
|
return Some("position_open".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["close_position", "position_close"])
|
||||||
|
|| normalized.contains("close_position_if_empty")
|
||||||
|
{
|
||||||
|
return Some("position_close".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["fee", "collect", "claim_fee"])
|
||||||
|
&& !normalized.contains("reward")
|
||||||
|
{
|
||||||
|
return Some("fee".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("reward") {
|
||||||
|
return Some("reward".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(
|
||||||
|
normalized.as_str(),
|
||||||
|
&["config", "admin", "authority", "permission", "pause", "status", "update_pool"],
|
||||||
|
) {
|
||||||
|
return Some("admin_config".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("mint") {
|
||||||
|
return Some("mint".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("burn") {
|
||||||
|
return Some("burn".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("transfer") {
|
||||||
|
return Some("transfer".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["create_ata", "init_account", "open_orders_create"]) {
|
||||||
|
return Some("account_create".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["close_account", "close_open_orders"])
|
||||||
|
|| normalized.starts_with("close_")
|
||||||
|
{
|
||||||
|
return Some("account_close".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("wrap_sol") {
|
||||||
|
return Some("wrap_sol".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("unwrap_sol") {
|
||||||
|
return Some("unwrap_sol".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("place_order") || normalized.contains("post_order") {
|
||||||
|
return Some("order_place".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("cancel_order") || normalized.contains("cancel_all") {
|
||||||
|
return Some("order_cancel".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("fill") {
|
||||||
|
return Some("order_fill".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("consume_events") {
|
||||||
|
return Some("consume_events".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("settle_funds") {
|
||||||
|
return Some("settle_funds".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("vault") && normalized.contains("deposit") {
|
||||||
|
return Some("vault_deposit".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("vault") && normalized.contains("withdraw") {
|
||||||
|
return Some("vault_withdraw".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["lock_liquidity", "create_lock", "lock"])
|
||||||
|
&& !normalized.contains("unlock")
|
||||||
|
{
|
||||||
|
return Some("lock".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("unlock") {
|
||||||
|
return Some("unlock".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["launch", "create_bonding", "bonding_curve"]) {
|
||||||
|
return Some("launch".to_string());
|
||||||
|
}
|
||||||
|
if contains_any(normalized.as_str(), &["migrate", "migration", "graduate"]) {
|
||||||
|
return Some("migration".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("unstake") {
|
||||||
|
return Some("unstake".to_string());
|
||||||
|
}
|
||||||
|
if normalized.contains("stake") {
|
||||||
|
return Some("stake".to_string());
|
||||||
|
}
|
||||||
|
return Some("unknown".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_any(value: &str, needles: &[&str]) -> bool {
|
||||||
|
for needle in needles {
|
||||||
|
if value.contains(needle) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn known_local_event_kind(
|
||||||
|
decoder_code: &str,
|
||||||
|
entry_name: &str,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
match (decoder_code, entry_name) {
|
||||||
|
("raydium-cpmm", "swap_base_input") => {
|
||||||
|
return Some("raydium_cpmm.swap_base_input".to_string());
|
||||||
|
},
|
||||||
|
("raydium-cpmm", "swap_base_output") => {
|
||||||
|
return Some("raydium_cpmm.swap_base_output".to_string());
|
||||||
|
},
|
||||||
|
("raydium-cpmm", "collect_creator_fee") => {
|
||||||
|
return Some("raydium_cpmm.collect_creator_fee".to_string());
|
||||||
|
},
|
||||||
|
("raydium-cpmm", "withdraw") => return Some("raydium_cpmm.withdraw".to_string()),
|
||||||
|
("raydium-cpmm", "initialize") => return Some("raydium_cpmm.initialize".to_string()),
|
||||||
|
("raydium-clmm", "swap") => return Some("raydium_clmm.swap".to_string()),
|
||||||
|
("raydium-clmm", "swap_v2") => return Some("raydium_clmm.swap_v2".to_string()),
|
||||||
|
("raydium-clmm", "increase_liquidity_v2") => {
|
||||||
|
return Some("raydium_clmm.increase_liquidity_v2".to_string());
|
||||||
|
},
|
||||||
|
("raydium-clmm", "decrease_liquidity_v2") => {
|
||||||
|
return Some("raydium_clmm.decrease_liquidity_v2".to_string());
|
||||||
|
},
|
||||||
|
("raydium-clmm", "open_position_with_token22_nft") => {
|
||||||
|
return Some("raydium_clmm.open_position_with_token22_nft".to_string());
|
||||||
|
},
|
||||||
|
("raydium-clmm", "close_position") => {
|
||||||
|
return Some("raydium_clmm.close_position".to_string());
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
async fn make_database() -> std::sync::Arc<crate::Database> {
|
||||||
|
let tempdir_result = tempfile::tempdir();
|
||||||
|
let tempdir = match tempdir_result {
|
||||||
|
Ok(tempdir) => tempdir,
|
||||||
|
Err(error) => panic!("tempdir must succeed: {}", error),
|
||||||
|
};
|
||||||
|
let database_path = tempdir.path().join("dex_event_coverage.sqlite3");
|
||||||
|
let config = crate::DatabaseConfig {
|
||||||
|
enabled: true,
|
||||||
|
backend: crate::DatabaseBackend::Sqlite,
|
||||||
|
sqlite: crate::SqliteDatabaseConfig {
|
||||||
|
path: database_path.to_string_lossy().to_string(),
|
||||||
|
create_if_missing: true,
|
||||||
|
busy_timeout_ms: 5000,
|
||||||
|
max_connections: 1,
|
||||||
|
auto_initialize_schema: true,
|
||||||
|
use_wal: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let database_result = crate::Database::connect_and_initialize(&config).await;
|
||||||
|
let database = match database_result {
|
||||||
|
Ok(database) => database,
|
||||||
|
Err(error) => panic!("database init must succeed: {}", error),
|
||||||
|
};
|
||||||
|
return std::sync::Arc::new(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_family_inference_covers_raydium_cpmm_core_entries() {
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family("swap_base_input", crate::ENTRY_KIND_INSTRUCTION),
|
||||||
|
Some("swap".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family("initialize", crate::ENTRY_KIND_INSTRUCTION),
|
||||||
|
Some("pool_create".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family("withdraw", crate::ENTRY_KIND_INSTRUCTION),
|
||||||
|
Some("liquidity_remove".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family("collect_creator_fee", crate::ENTRY_KIND_INSTRUCTION),
|
||||||
|
Some("fee".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sync_upstream_registry_persists_raydium_cpmm_coverage_rows() {
|
||||||
|
let database = make_database().await;
|
||||||
|
let service = crate::DexEventCoverageService::new(database.clone());
|
||||||
|
let result = service.sync_upstream_registry(Some("raydium-cpmm".to_string())).await;
|
||||||
|
let result = match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(error) => panic!("coverage sync must succeed: {}", error),
|
||||||
|
};
|
||||||
|
assert!(result.upstream_entry_count > 0);
|
||||||
|
assert_eq!(result.upstream_entry_count, result.upserted_entry_count);
|
||||||
|
let rows_result = crate::query_dex_event_coverage_entries_list_by_decoder(
|
||||||
|
database.as_ref(),
|
||||||
|
"raydium-cpmm",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let rows = match rows_result {
|
||||||
|
Ok(rows) => rows,
|
||||||
|
Err(error) => panic!("coverage rows must load: {}", error),
|
||||||
|
};
|
||||||
|
assert_eq!(rows.len(), result.upstream_entry_count);
|
||||||
|
assert!(rows.iter().any(|row| return {
|
||||||
|
row.entry_name == "swap_base_input"
|
||||||
|
&& row.event_family == Some("swap".to_string())
|
||||||
|
&& row.local_event_kind == Some("raydium_cpmm.swap_base_input".to_string())
|
||||||
|
}));
|
||||||
|
assert!(rows.iter().any(|row| return {
|
||||||
|
row.entry_name == "deposit"
|
||||||
|
&& row.event_family == Some("liquidity_add".to_string())
|
||||||
|
&& row.local_event_kind.is_none()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,8 @@ mod dex_detect;
|
|||||||
mod dex_detection_route;
|
mod dex_detection_route;
|
||||||
/// Shared DEX event classification and decoded-payload enrichment helpers.
|
/// Shared DEX event classification and decoded-payload enrichment helpers.
|
||||||
mod dex_event_classification;
|
mod dex_event_classification;
|
||||||
|
/// Event coverage synchronization and reporting.
|
||||||
|
mod dex_event_coverage;
|
||||||
/// Shared DEX pool materialization helpers.
|
/// Shared DEX pool materialization helpers.
|
||||||
mod dex_pool_materialization;
|
mod dex_pool_materialization;
|
||||||
/// Shared DEX support matrix.
|
/// Shared DEX support matrix.
|
||||||
@@ -477,6 +479,14 @@ pub use db::DexDecodedEventEntity;
|
|||||||
pub use db::DexDto;
|
pub use db::DexDto;
|
||||||
/// Persisted normalized DEX row.
|
/// Persisted normalized DEX row.
|
||||||
pub use db::DexEntity;
|
pub use db::DexEntity;
|
||||||
|
/// Application-facing DEX event coverage entry DTO.
|
||||||
|
pub use db::DexEventCoverageEntryDto;
|
||||||
|
/// Persisted DEX event coverage entry row.
|
||||||
|
pub use db::DexEventCoverageEntryEntity;
|
||||||
|
/// Application-facing DEX event coverage summary DTO.
|
||||||
|
pub use db::DexEventCoverageSummaryDto;
|
||||||
|
/// Aggregated DEX event coverage summary row.
|
||||||
|
pub use db::DexEventCoverageSummaryEntity;
|
||||||
/// Normalized fee event persisted from useful non-trade DEX events.
|
/// Normalized fee event persisted from useful non-trade DEX events.
|
||||||
pub use db::FeeEventDto;
|
pub use db::FeeEventDto;
|
||||||
/// Persisted fee event row.
|
/// Persisted fee event row.
|
||||||
@@ -733,6 +743,18 @@ pub use db::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
|||||||
pub use db::query_dex_decoded_events_list_by_transaction_id;
|
pub use db::query_dex_decoded_events_list_by_transaction_id;
|
||||||
/// Inserts or updates one decoded DEX event row.
|
/// Inserts or updates one decoded DEX event row.
|
||||||
pub use db::query_dex_decoded_events_upsert;
|
pub use db::query_dex_decoded_events_upsert;
|
||||||
|
/// Deletes DEX event coverage entries for one decoder.
|
||||||
|
pub use db::query_dex_event_coverage_entries_delete_by_decoder;
|
||||||
|
/// Lists DEX event coverage entries for one decoder.
|
||||||
|
pub use db::query_dex_event_coverage_entries_list_by_decoder;
|
||||||
|
/// Lists DEX event coverage summaries grouped by decoder.
|
||||||
|
pub use db::query_dex_event_coverage_entries_list_summary_by_decoder;
|
||||||
|
/// Refreshes local DEX event coverage counts for every decoder.
|
||||||
|
pub use db::query_dex_event_coverage_entries_refresh_local_counts;
|
||||||
|
/// Refreshes local DEX event coverage counts for one decoder.
|
||||||
|
pub use db::query_dex_event_coverage_entries_refresh_local_counts_by_decoder;
|
||||||
|
/// Inserts or updates one DEX event coverage entry.
|
||||||
|
pub use db::query_dex_event_coverage_entries_upsert;
|
||||||
/// Reads one normalized DEX row by code.
|
/// Reads one normalized DEX row by code.
|
||||||
pub use db::query_dexs_get_by_code;
|
pub use db::query_dexs_get_by_code;
|
||||||
/// Lists normalized DEX rows.
|
/// Lists normalized DEX rows.
|
||||||
@@ -1196,6 +1218,10 @@ pub use dex_event_classification::is_dex_token_burn_event_kind;
|
|||||||
pub use dex_event_classification::is_dex_token_mint_event_kind;
|
pub use dex_event_classification::is_dex_token_mint_event_kind;
|
||||||
/// Returns true for swap-like DEX events.
|
/// Returns true for swap-like DEX events.
|
||||||
pub use dex_event_classification::is_dex_trade_event_kind;
|
pub use dex_event_classification::is_dex_trade_event_kind;
|
||||||
|
/// Service that syncs upstream registry entries into event coverage rows.
|
||||||
|
pub use dex_event_coverage::DexEventCoverageService;
|
||||||
|
/// Result of one event coverage sync or refresh pass.
|
||||||
|
pub use dex_event_coverage::DexEventCoverageSyncResult;
|
||||||
/// Static DEX support matrix entry.
|
/// Static DEX support matrix entry.
|
||||||
pub use dex_support_matrix::DexSupportMatrixEntry;
|
pub use dex_support_matrix::DexSupportMatrixEntry;
|
||||||
/// Owned DEX support matrix entry DTO.
|
/// Owned DEX support matrix entry DTO.
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ impl LocalPipelineDiagnosticsService {
|
|||||||
Ok(summaries) => summaries,
|
Ok(summaries) => summaries,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
|
let event_coverage_summaries_result =
|
||||||
|
load_event_coverage_summaries(self.database.as_ref()).await;
|
||||||
|
let event_coverage_summaries = match event_coverage_summaries_result {
|
||||||
|
Ok(summaries) => summaries,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
let blocking_issue_count = counters.actionable_missing_trade_event_count
|
let blocking_issue_count = counters.actionable_missing_trade_event_count
|
||||||
+ counters.invalid_trade_event_count
|
+ counters.invalid_trade_event_count
|
||||||
+ counters.duplicate_decoded_event_trade_count
|
+ counters.duplicate_decoded_event_trade_count
|
||||||
@@ -77,6 +83,7 @@ impl LocalPipelineDiagnosticsService {
|
|||||||
raydium_surface_summaries,
|
raydium_surface_summaries,
|
||||||
decoded_event_summaries,
|
decoded_event_summaries,
|
||||||
event_classification_summaries,
|
event_classification_summaries,
|
||||||
|
event_coverage_summaries,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +152,12 @@ impl LocalPipelineDiagnosticsService {
|
|||||||
Ok(summaries) => summaries,
|
Ok(summaries) => summaries,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
|
let event_coverage_summaries_result =
|
||||||
|
load_event_coverage_summaries(self.database.as_ref()).await;
|
||||||
|
let event_coverage_summaries = match event_coverage_summaries_result {
|
||||||
|
Ok(summaries) => summaries,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
let missing_trade_event_reason_summaries_result =
|
let missing_trade_event_reason_summaries_result =
|
||||||
crate::query_local_missing_trade_event_reason_list_summaries(self.database.as_ref())
|
crate::query_local_missing_trade_event_reason_list_summaries(self.database.as_ref())
|
||||||
.await;
|
.await;
|
||||||
@@ -248,6 +261,8 @@ impl LocalPipelineDiagnosticsService {
|
|||||||
+ counters.duplicate_decoded_event_trade_count
|
+ counters.duplicate_decoded_event_trade_count
|
||||||
+ counters.duplicate_candle_bucket_count;
|
+ counters.duplicate_candle_bucket_count;
|
||||||
let diagnostics_clean = blocking_issue_count == 0;
|
let diagnostics_clean = blocking_issue_count == 0;
|
||||||
|
let event_coverage_aggregate =
|
||||||
|
aggregate_event_coverage_summaries(&event_coverage_summaries);
|
||||||
return Ok(crate::LocalPipelineDiagnosticSummaryDto {
|
return Ok(crate::LocalPipelineDiagnosticSummaryDto {
|
||||||
transaction_count: counters.transaction_count,
|
transaction_count: counters.transaction_count,
|
||||||
ok_transaction_count: counters.ok_transaction_count,
|
ok_transaction_count: counters.ok_transaction_count,
|
||||||
@@ -264,6 +279,27 @@ impl LocalPipelineDiagnosticsService {
|
|||||||
fee_event_count: counters.fee_event_count,
|
fee_event_count: counters.fee_event_count,
|
||||||
reward_event_count: counters.reward_event_count,
|
reward_event_count: counters.reward_event_count,
|
||||||
pool_admin_event_count: counters.pool_admin_event_count,
|
pool_admin_event_count: counters.pool_admin_event_count,
|
||||||
|
event_coverage_listed_entry_count: event_coverage_aggregate.listed_entry_count,
|
||||||
|
event_coverage_decoded_entry_count: event_coverage_aggregate.decoded_entry_count,
|
||||||
|
event_coverage_observed_entry_count: event_coverage_aggregate.observed_entry_count,
|
||||||
|
event_coverage_materialized_entry_count: event_coverage_aggregate
|
||||||
|
.materialized_entry_count,
|
||||||
|
event_coverage_total_observed_count: event_coverage_aggregate.total_observed_count,
|
||||||
|
event_coverage_total_materialized_count: event_coverage_aggregate
|
||||||
|
.total_materialized_count,
|
||||||
|
event_coverage_trade_count: event_coverage_aggregate.trade_count,
|
||||||
|
event_coverage_audit_only_entry_count: event_coverage_aggregate.audit_only_entry_count,
|
||||||
|
event_coverage_missing_db_target_entry_count: event_coverage_aggregate
|
||||||
|
.missing_db_target_entry_count,
|
||||||
|
event_coverage_upstream_git_unverified_entry_count: event_coverage_aggregate
|
||||||
|
.upstream_git_unverified_entry_count,
|
||||||
|
event_coverage_upstream_git_mapped_unverified_entry_count: event_coverage_aggregate
|
||||||
|
.upstream_git_mapped_unverified_entry_count,
|
||||||
|
event_coverage_upstream_git_local_corpus_observed_entry_count: event_coverage_aggregate
|
||||||
|
.upstream_git_local_corpus_observed_entry_count,
|
||||||
|
event_coverage_upstream_git_local_corpus_materialized_entry_count:
|
||||||
|
event_coverage_aggregate.upstream_git_local_corpus_materialized_entry_count,
|
||||||
|
event_coverage_summaries,
|
||||||
diagnostics_clean,
|
diagnostics_clean,
|
||||||
blocking_issue_count,
|
blocking_issue_count,
|
||||||
missing_trade_event_count: counters.missing_trade_event_count,
|
missing_trade_event_count: counters.missing_trade_event_count,
|
||||||
@@ -334,28 +370,48 @@ async fn query_lightweight_validation_counters(
|
|||||||
match database.connection() {
|
match database.connection() {
|
||||||
crate::DatabaseConnection::Sqlite(pool) => {
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
let transaction_count = {
|
let transaction_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_chain_transactions", "transaction_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_chain_transactions",
|
||||||
|
"transaction_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let ok_transaction_count = {
|
let ok_transaction_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_chain_transactions WHERE err_json IS NULL", "ok_transaction_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_chain_transactions WHERE err_json IS NULL",
|
||||||
|
"ok_transaction_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let failed_transaction_count = {
|
let failed_transaction_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_chain_transactions WHERE err_json IS NOT NULL", "failed_transaction_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_chain_transactions WHERE err_json IS NOT NULL",
|
||||||
|
"failed_transaction_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let decoded_event_count = {
|
let decoded_event_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_dex_decoded_events", "decoded_event_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_dex_decoded_events",
|
||||||
|
"decoded_event_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -397,36 +453,60 @@ async fn query_lightweight_validation_counters(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let liquidity_event_count = {
|
let liquidity_event_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_liquidity_events", "liquidity_event_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_liquidity_events",
|
||||||
|
"liquidity_event_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let pool_lifecycle_event_count = {
|
let pool_lifecycle_event_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_pool_lifecycle_events", "pool_lifecycle_event_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_pool_lifecycle_events",
|
||||||
|
"pool_lifecycle_event_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let fee_event_count =
|
let fee_event_count = {
|
||||||
{
|
let counter_result = query_validation_i64(
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_fee_events", "fee_event_count").await;
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_fee_events",
|
||||||
|
"fee_event_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let reward_event_count = {
|
let reward_event_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_reward_events", "reward_event_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_reward_events",
|
||||||
|
"reward_event_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let pool_admin_event_count = {
|
let pool_admin_event_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_pool_admin_events", "pool_admin_event_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_pool_admin_events",
|
||||||
|
"pool_admin_event_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -466,7 +546,12 @@ async fn query_lightweight_validation_counters(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let trade_event_count = {
|
let trade_event_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_trade_events", "trade_event_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_trade_events",
|
||||||
|
"trade_event_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -480,7 +565,12 @@ async fn query_lightweight_validation_counters(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let pair_candle_count = {
|
let pair_candle_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_pair_candles", "pair_candle_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(*) FROM k_sol_pair_candles",
|
||||||
|
"pair_candle_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -507,9 +597,10 @@ async fn query_lightweight_validation_counters(
|
|||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let token_count =
|
let token_count = {
|
||||||
{
|
let counter_result =
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_tokens", "token_count").await;
|
query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_tokens", "token_count")
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -564,17 +655,19 @@ async fn query_lightweight_validation_counters(
|
|||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let pool_count =
|
let pool_count = {
|
||||||
{
|
let counter_result =
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_pools", "pool_count").await;
|
query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_pools", "pool_count")
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let pair_count =
|
let pair_count = {
|
||||||
{
|
let counter_result =
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_pairs", "pair_count").await;
|
query_validation_i64(pool, "SELECT COUNT(*) FROM k_sol_pairs", "pair_count")
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -595,14 +688,24 @@ async fn query_lightweight_validation_counters(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let trade_materialized_pair_count = {
|
let trade_materialized_pair_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(DISTINCT pair_id) FROM k_sol_trade_events", "trade_materialized_pair_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(DISTINCT pair_id) FROM k_sol_trade_events",
|
||||||
|
"trade_materialized_pair_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let candle_materialized_pair_count = {
|
let candle_materialized_pair_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(DISTINCT pair_id) FROM k_sol_pair_candles", "candle_materialized_pair_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(DISTINCT pair_id) FROM k_sol_pair_candles",
|
||||||
|
"candle_materialized_pair_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -616,7 +719,12 @@ async fn query_lightweight_validation_counters(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let candle_bucket_timeframe_count = {
|
let candle_bucket_timeframe_count = {
|
||||||
let counter_result = query_validation_i64(pool, "SELECT COUNT(DISTINCT timeframe_seconds) FROM k_sol_pair_candles", "candle_bucket_timeframe_count").await;
|
let counter_result = query_validation_i64(
|
||||||
|
pool,
|
||||||
|
"SELECT COUNT(DISTINCT timeframe_seconds) FROM k_sol_pair_candles",
|
||||||
|
"candle_bucket_timeframe_count",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match counter_result {
|
match counter_result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -714,6 +822,79 @@ async fn query_validation_i64(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_event_coverage_summaries(
|
||||||
|
database: &crate::Database,
|
||||||
|
) -> Result<std::vec::Vec<crate::DexEventCoverageSummaryDto>, crate::Error> {
|
||||||
|
let refresh_result =
|
||||||
|
crate::query_dex_event_coverage_entries_refresh_local_counts(database).await;
|
||||||
|
if let Err(error) = refresh_result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
let summaries_result =
|
||||||
|
crate::query_dex_event_coverage_entries_list_summary_by_decoder(database).await;
|
||||||
|
match summaries_result {
|
||||||
|
Ok(summaries) => return Ok(summaries),
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct EventCoverageDiagnosticAggregate {
|
||||||
|
listed_entry_count: u64,
|
||||||
|
decoded_entry_count: u64,
|
||||||
|
observed_entry_count: u64,
|
||||||
|
materialized_entry_count: u64,
|
||||||
|
total_observed_count: u64,
|
||||||
|
total_materialized_count: u64,
|
||||||
|
trade_count: u64,
|
||||||
|
audit_only_entry_count: u64,
|
||||||
|
missing_db_target_entry_count: u64,
|
||||||
|
upstream_git_unverified_entry_count: u64,
|
||||||
|
upstream_git_mapped_unverified_entry_count: u64,
|
||||||
|
upstream_git_local_corpus_observed_entry_count: u64,
|
||||||
|
upstream_git_local_corpus_materialized_entry_count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aggregate_event_coverage_summaries(
|
||||||
|
summaries: &[crate::DexEventCoverageSummaryDto],
|
||||||
|
) -> EventCoverageDiagnosticAggregate {
|
||||||
|
let mut aggregate = EventCoverageDiagnosticAggregate {
|
||||||
|
listed_entry_count: 0,
|
||||||
|
decoded_entry_count: 0,
|
||||||
|
observed_entry_count: 0,
|
||||||
|
materialized_entry_count: 0,
|
||||||
|
total_observed_count: 0,
|
||||||
|
total_materialized_count: 0,
|
||||||
|
trade_count: 0,
|
||||||
|
audit_only_entry_count: 0,
|
||||||
|
missing_db_target_entry_count: 0,
|
||||||
|
upstream_git_unverified_entry_count: 0,
|
||||||
|
upstream_git_mapped_unverified_entry_count: 0,
|
||||||
|
upstream_git_local_corpus_observed_entry_count: 0,
|
||||||
|
upstream_git_local_corpus_materialized_entry_count: 0,
|
||||||
|
};
|
||||||
|
for summary in summaries {
|
||||||
|
aggregate.listed_entry_count += summary.listed_entry_count;
|
||||||
|
aggregate.decoded_entry_count += summary.decoded_entry_count;
|
||||||
|
aggregate.observed_entry_count += summary.observed_entry_count;
|
||||||
|
aggregate.materialized_entry_count += summary.materialized_entry_count;
|
||||||
|
aggregate.total_observed_count += summary.total_observed_count;
|
||||||
|
aggregate.total_materialized_count += summary.total_materialized_count;
|
||||||
|
aggregate.trade_count += summary.trade_count;
|
||||||
|
aggregate.audit_only_entry_count += summary.audit_only_entry_count;
|
||||||
|
aggregate.missing_db_target_entry_count += summary.missing_db_target_entry_count;
|
||||||
|
aggregate.upstream_git_unverified_entry_count +=
|
||||||
|
summary.upstream_git_unverified_entry_count;
|
||||||
|
aggregate.upstream_git_mapped_unverified_entry_count +=
|
||||||
|
summary.upstream_git_mapped_unverified_entry_count;
|
||||||
|
aggregate.upstream_git_local_corpus_observed_entry_count +=
|
||||||
|
summary.upstream_git_local_corpus_observed_entry_count;
|
||||||
|
aggregate.upstream_git_local_corpus_materialized_entry_count +=
|
||||||
|
summary.upstream_git_local_corpus_materialized_entry_count;
|
||||||
|
}
|
||||||
|
return aggregate;
|
||||||
|
}
|
||||||
|
|
||||||
fn build_lightweight_diagnostic_summary(
|
fn build_lightweight_diagnostic_summary(
|
||||||
counters: crate::LocalPipelineDiagnosticCountersDto,
|
counters: crate::LocalPipelineDiagnosticCountersDto,
|
||||||
diagnostics_clean: bool,
|
diagnostics_clean: bool,
|
||||||
@@ -724,7 +905,9 @@ fn build_lightweight_diagnostic_summary(
|
|||||||
event_classification_summaries: std::vec::Vec<
|
event_classification_summaries: std::vec::Vec<
|
||||||
crate::LocalEventClassificationDiagnosticSummaryDto,
|
crate::LocalEventClassificationDiagnosticSummaryDto,
|
||||||
>,
|
>,
|
||||||
|
event_coverage_summaries: std::vec::Vec<crate::DexEventCoverageSummaryDto>,
|
||||||
) -> crate::LocalPipelineDiagnosticSummaryDto {
|
) -> crate::LocalPipelineDiagnosticSummaryDto {
|
||||||
|
let event_coverage_aggregate = aggregate_event_coverage_summaries(&event_coverage_summaries);
|
||||||
return crate::LocalPipelineDiagnosticSummaryDto {
|
return crate::LocalPipelineDiagnosticSummaryDto {
|
||||||
transaction_count: counters.transaction_count,
|
transaction_count: counters.transaction_count,
|
||||||
ok_transaction_count: counters.ok_transaction_count,
|
ok_transaction_count: counters.ok_transaction_count,
|
||||||
@@ -740,6 +923,25 @@ fn build_lightweight_diagnostic_summary(
|
|||||||
fee_event_count: counters.fee_event_count,
|
fee_event_count: counters.fee_event_count,
|
||||||
reward_event_count: counters.reward_event_count,
|
reward_event_count: counters.reward_event_count,
|
||||||
pool_admin_event_count: counters.pool_admin_event_count,
|
pool_admin_event_count: counters.pool_admin_event_count,
|
||||||
|
event_coverage_listed_entry_count: event_coverage_aggregate.listed_entry_count,
|
||||||
|
event_coverage_decoded_entry_count: event_coverage_aggregate.decoded_entry_count,
|
||||||
|
event_coverage_observed_entry_count: event_coverage_aggregate.observed_entry_count,
|
||||||
|
event_coverage_materialized_entry_count: event_coverage_aggregate.materialized_entry_count,
|
||||||
|
event_coverage_total_observed_count: event_coverage_aggregate.total_observed_count,
|
||||||
|
event_coverage_total_materialized_count: event_coverage_aggregate.total_materialized_count,
|
||||||
|
event_coverage_trade_count: event_coverage_aggregate.trade_count,
|
||||||
|
event_coverage_audit_only_entry_count: event_coverage_aggregate.audit_only_entry_count,
|
||||||
|
event_coverage_missing_db_target_entry_count: event_coverage_aggregate
|
||||||
|
.missing_db_target_entry_count,
|
||||||
|
event_coverage_upstream_git_unverified_entry_count: event_coverage_aggregate
|
||||||
|
.upstream_git_unverified_entry_count,
|
||||||
|
event_coverage_upstream_git_mapped_unverified_entry_count: event_coverage_aggregate
|
||||||
|
.upstream_git_mapped_unverified_entry_count,
|
||||||
|
event_coverage_upstream_git_local_corpus_observed_entry_count: event_coverage_aggregate
|
||||||
|
.upstream_git_local_corpus_observed_entry_count,
|
||||||
|
event_coverage_upstream_git_local_corpus_materialized_entry_count: event_coverage_aggregate
|
||||||
|
.upstream_git_local_corpus_materialized_entry_count,
|
||||||
|
event_coverage_summaries,
|
||||||
diagnostics_clean,
|
diagnostics_clean,
|
||||||
blocking_issue_count,
|
blocking_issue_count,
|
||||||
missing_trade_event_count: counters.missing_trade_event_count,
|
missing_trade_event_count: counters.missing_trade_event_count,
|
||||||
|
|||||||
@@ -368,6 +368,31 @@ impl LocalPipelineValidationConfig {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds the `0.7.48-pre` event coverage DB checkpoint validation config.
|
||||||
|
///
|
||||||
|
/// This profile closes the DB/reporting checkpoint before `0.7.48` opens
|
||||||
|
/// the Raydium CPMM event-coverage tranche. It exposes coverage summaries,
|
||||||
|
/// keeps core trade/candle invariants, and scopes the successful
|
||||||
|
/// trade-candidate materialization check to the expected Raydium DEXes so
|
||||||
|
/// unrelated partial/candidate DEXes remain inspectable without blocking
|
||||||
|
/// the DB checkpoint.
|
||||||
|
pub fn v0_7_48_pre_event_coverage_db_checkpoint() -> Self {
|
||||||
|
let mut config = Self::v0_7_42_raydium_family_event_coverage();
|
||||||
|
config.profile_code = "0.7.48-pre_event_coverage_db_checkpoint".to_string();
|
||||||
|
config.expected_dex_codes = vec![
|
||||||
|
"raydium_cpmm".to_string(),
|
||||||
|
"raydium_clmm".to_string(),
|
||||||
|
"raydium_amm_v4".to_string(),
|
||||||
|
];
|
||||||
|
config.require_all_expected_dexes = false;
|
||||||
|
config.allow_unexpected_dexes = true;
|
||||||
|
config.require_ok_trade_candidates_fully_materialized = false;
|
||||||
|
config.require_trade_events_per_dex = false;
|
||||||
|
config.require_candles_per_dex = false;
|
||||||
|
config.require_pair_trading_readiness_semantics = false;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds the legacy `0.7.39` launch-surface validation alias.
|
/// Builds the legacy `0.7.39` launch-surface validation alias.
|
||||||
///
|
///
|
||||||
/// The implementation now delegates to the DEX-first profile so callers that
|
/// The implementation now delegates to the DEX-first profile so callers that
|
||||||
@@ -424,6 +449,34 @@ pub struct LocalPipelineValidationReportDto {
|
|||||||
pub reward_event_count: i64,
|
pub reward_event_count: i64,
|
||||||
/// Total persisted pool administration events.
|
/// Total persisted pool administration events.
|
||||||
pub pool_admin_event_count: i64,
|
pub pool_admin_event_count: i64,
|
||||||
|
/// Event coverage entries listed from upstream registry sources.
|
||||||
|
pub event_coverage_listed_entry_count: u64,
|
||||||
|
/// Event coverage entries wired to local decoder event kinds.
|
||||||
|
pub event_coverage_decoded_entry_count: u64,
|
||||||
|
/// Event coverage entries observed in local corpus.
|
||||||
|
pub event_coverage_observed_entry_count: u64,
|
||||||
|
/// Event coverage entries materialized into target tables.
|
||||||
|
pub event_coverage_materialized_entry_count: u64,
|
||||||
|
/// Sum of local observations across coverage entries.
|
||||||
|
pub event_coverage_total_observed_count: u64,
|
||||||
|
/// Sum of local materialized rows across coverage entries.
|
||||||
|
pub event_coverage_total_materialized_count: u64,
|
||||||
|
/// Sum of linked trades across coverage entries.
|
||||||
|
pub event_coverage_trade_count: u64,
|
||||||
|
/// Audit-only coverage entries.
|
||||||
|
pub event_coverage_audit_only_entry_count: u64,
|
||||||
|
/// Coverage entries without an expected DB target.
|
||||||
|
pub event_coverage_missing_db_target_entry_count: u64,
|
||||||
|
/// Coverage entries still upstream Git unverified.
|
||||||
|
pub event_coverage_upstream_git_unverified_entry_count: u64,
|
||||||
|
/// Coverage entries mapped but not observed locally yet.
|
||||||
|
pub event_coverage_upstream_git_mapped_unverified_entry_count: u64,
|
||||||
|
/// Coverage entries observed locally.
|
||||||
|
pub event_coverage_upstream_git_local_corpus_observed_entry_count: u64,
|
||||||
|
/// Coverage entries observed and materialized locally.
|
||||||
|
pub event_coverage_upstream_git_local_corpus_materialized_entry_count: u64,
|
||||||
|
/// Event coverage summaries grouped by decoder.
|
||||||
|
pub event_coverage_summaries: std::vec::Vec<crate::DexEventCoverageSummaryDto>,
|
||||||
/// Total known tokens.
|
/// Total known tokens.
|
||||||
pub token_count: i64,
|
pub token_count: i64,
|
||||||
/// Total tokens missing symbol or name.
|
/// Total tokens missing symbol or name.
|
||||||
@@ -500,6 +553,7 @@ impl LocalPipelineValidationService {
|
|||||||
crate::LocalPipelineDiagnosticsService::new(self.database.clone());
|
crate::LocalPipelineDiagnosticsService::new(self.database.clone());
|
||||||
let summary_result = if config.profile_code == "0.7.42_raydium_family_event_coverage"
|
let summary_result = if config.profile_code == "0.7.42_raydium_family_event_coverage"
|
||||||
|| config.profile_code == "0.7.43_meteora_effective_surfaces"
|
|| config.profile_code == "0.7.43_meteora_effective_surfaces"
|
||||||
|
|| config.profile_code == "0.7.48-pre_event_coverage_db_checkpoint"
|
||||||
{
|
{
|
||||||
diagnostics_service.diagnose_for_validation().await
|
diagnostics_service.diagnose_for_validation().await
|
||||||
} else {
|
} else {
|
||||||
@@ -651,6 +705,20 @@ impl LocalPipelineValidationService {
|
|||||||
let config = crate::LocalPipelineValidationConfig::v0_7_43_meteora_effective_surfaces();
|
let config = crate::LocalPipelineValidationConfig::v0_7_43_meteora_effective_surfaces();
|
||||||
return self.validate_current_database(&config).await;
|
return self.validate_current_database(&config).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Diagnoses the current database with the `0.7.48-pre` event coverage checkpoint profile.
|
||||||
|
pub async fn validate_v0_7_48_pre_current_database(
|
||||||
|
&self,
|
||||||
|
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
|
||||||
|
let coverage_service = crate::DexEventCoverageService::new(self.database.clone());
|
||||||
|
let sync_result = coverage_service.sync_upstream_registry(None).await;
|
||||||
|
if let Err(error) = sync_result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
let config =
|
||||||
|
crate::LocalPipelineValidationConfig::v0_7_48_pre_event_coverage_db_checkpoint();
|
||||||
|
return self.validate_current_database(&config).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates a diagnostics summary without performing database access.
|
/// Validates a diagnostics summary without performing database access.
|
||||||
@@ -762,6 +830,13 @@ pub fn validate_local_pipeline_diagnostics_summary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if config.profile_code == "0.7.48-pre_event_coverage_db_checkpoint" {
|
||||||
|
validate_expected_dex_trade_candidates_materialized_for_checkpoint(
|
||||||
|
&mut issues,
|
||||||
|
summary,
|
||||||
|
&expected_dex_codes,
|
||||||
|
);
|
||||||
|
}
|
||||||
if config.require_dex_support_matrix_semantics {
|
if config.require_dex_support_matrix_semantics {
|
||||||
validate_dex_support_matrix_semantics(&mut issues);
|
validate_dex_support_matrix_semantics(&mut issues);
|
||||||
}
|
}
|
||||||
@@ -778,7 +853,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
|
|||||||
|| config.profile_code == "0.7.39_launch_surface_origin_baseline"
|
|| config.profile_code == "0.7.39_launch_surface_origin_baseline"
|
||||||
|| config.profile_code == "0.7.40_raydium_effective_surfaces"
|
|| config.profile_code == "0.7.40_raydium_effective_surfaces"
|
||||||
|| config.profile_code == "0.7.41_raydium_amm_v4_swap_decoder"
|
|| config.profile_code == "0.7.41_raydium_amm_v4_swap_decoder"
|
||||||
|| config.profile_code == "0.7.43_meteora_effective_surfaces";
|
|| config.profile_code == "0.7.43_meteora_effective_surfaces"
|
||||||
|
|| config.profile_code == "0.7.48-pre_event_coverage_db_checkpoint";
|
||||||
if config.require_all_expected_dexes || missing_expected_dex_is_warning {
|
if config.require_all_expected_dexes || missing_expected_dex_is_warning {
|
||||||
for expected_dex_code in &expected_dex_codes {
|
for expected_dex_code in &expected_dex_codes {
|
||||||
if !observed_dex_codes.contains(expected_dex_code) {
|
if !observed_dex_codes.contains(expected_dex_code) {
|
||||||
@@ -853,6 +929,25 @@ pub fn validate_local_pipeline_diagnostics_summary(
|
|||||||
fee_event_count: summary.fee_event_count,
|
fee_event_count: summary.fee_event_count,
|
||||||
reward_event_count: summary.reward_event_count,
|
reward_event_count: summary.reward_event_count,
|
||||||
pool_admin_event_count: summary.pool_admin_event_count,
|
pool_admin_event_count: summary.pool_admin_event_count,
|
||||||
|
event_coverage_listed_entry_count: summary.event_coverage_listed_entry_count,
|
||||||
|
event_coverage_decoded_entry_count: summary.event_coverage_decoded_entry_count,
|
||||||
|
event_coverage_observed_entry_count: summary.event_coverage_observed_entry_count,
|
||||||
|
event_coverage_materialized_entry_count: summary.event_coverage_materialized_entry_count,
|
||||||
|
event_coverage_total_observed_count: summary.event_coverage_total_observed_count,
|
||||||
|
event_coverage_total_materialized_count: summary.event_coverage_total_materialized_count,
|
||||||
|
event_coverage_trade_count: summary.event_coverage_trade_count,
|
||||||
|
event_coverage_audit_only_entry_count: summary.event_coverage_audit_only_entry_count,
|
||||||
|
event_coverage_missing_db_target_entry_count: summary
|
||||||
|
.event_coverage_missing_db_target_entry_count,
|
||||||
|
event_coverage_upstream_git_unverified_entry_count: summary
|
||||||
|
.event_coverage_upstream_git_unverified_entry_count,
|
||||||
|
event_coverage_upstream_git_mapped_unverified_entry_count: summary
|
||||||
|
.event_coverage_upstream_git_mapped_unverified_entry_count,
|
||||||
|
event_coverage_upstream_git_local_corpus_observed_entry_count: summary
|
||||||
|
.event_coverage_upstream_git_local_corpus_observed_entry_count,
|
||||||
|
event_coverage_upstream_git_local_corpus_materialized_entry_count: summary
|
||||||
|
.event_coverage_upstream_git_local_corpus_materialized_entry_count,
|
||||||
|
event_coverage_summaries: summary.event_coverage_summaries.clone(),
|
||||||
token_count: summary.token_count,
|
token_count: summary.token_count,
|
||||||
token_metadata_missing_count: summary.token_metadata_missing_count,
|
token_metadata_missing_count: summary.token_metadata_missing_count,
|
||||||
tradable_token_metadata_missing_count: summary.tradable_token_metadata_missing_count,
|
tradable_token_metadata_missing_count: summary.tradable_token_metadata_missing_count,
|
||||||
@@ -1104,6 +1199,48 @@ fn collect_observed_dex_codes(
|
|||||||
return observed_dex_codes;
|
return observed_dex_codes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_expected_dex_trade_candidates_materialized_for_checkpoint(
|
||||||
|
issues: &mut std::vec::Vec<crate::LocalPipelineValidationIssueDto>,
|
||||||
|
summary: &crate::LocalPipelineDiagnosticSummaryDto,
|
||||||
|
expected_dex_codes: &[std::string::String],
|
||||||
|
) {
|
||||||
|
for decoded_summary in &summary.decoded_event_summaries {
|
||||||
|
if !expected_dex_codes.contains(&decoded_summary.protocol_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let actionability = match &decoded_summary.event_actionability {
|
||||||
|
Some(value) => value.as_str(),
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
let is_trade_candidate = match decoded_summary.trade_candidate {
|
||||||
|
Some(value) => value,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
if actionability != "trade_candidate" && !is_trade_candidate {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let missing_trade_event_count =
|
||||||
|
decoded_summary.event_count - decoded_summary.trade_event_count;
|
||||||
|
if missing_trade_event_count <= 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
issues.push(crate::LocalPipelineValidationIssueDto {
|
||||||
|
code: "expected_dex_trade_candidates_without_trade_events".to_string(),
|
||||||
|
message: format!(
|
||||||
|
"expected DEX '{}' event '{}' has {} trade candidate(s) without persisted trade event",
|
||||||
|
decoded_summary.protocol_name,
|
||||||
|
decoded_summary.event_kind,
|
||||||
|
missing_trade_event_count
|
||||||
|
),
|
||||||
|
subject: Some(format!(
|
||||||
|
"{}:{}",
|
||||||
|
decoded_summary.protocol_name, decoded_summary.event_kind
|
||||||
|
)),
|
||||||
|
blocking: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
fn make_clean_summary() -> crate::LocalPipelineDiagnosticSummaryDto {
|
fn make_clean_summary() -> crate::LocalPipelineDiagnosticSummaryDto {
|
||||||
@@ -1122,6 +1259,20 @@ mod tests {
|
|||||||
fee_event_count: 0,
|
fee_event_count: 0,
|
||||||
reward_event_count: 0,
|
reward_event_count: 0,
|
||||||
pool_admin_event_count: 0,
|
pool_admin_event_count: 0,
|
||||||
|
event_coverage_listed_entry_count: 0,
|
||||||
|
event_coverage_decoded_entry_count: 0,
|
||||||
|
event_coverage_observed_entry_count: 0,
|
||||||
|
event_coverage_materialized_entry_count: 0,
|
||||||
|
event_coverage_total_observed_count: 0,
|
||||||
|
event_coverage_total_materialized_count: 0,
|
||||||
|
event_coverage_trade_count: 0,
|
||||||
|
event_coverage_audit_only_entry_count: 0,
|
||||||
|
event_coverage_missing_db_target_entry_count: 0,
|
||||||
|
event_coverage_upstream_git_unverified_entry_count: 0,
|
||||||
|
event_coverage_upstream_git_mapped_unverified_entry_count: 0,
|
||||||
|
event_coverage_upstream_git_local_corpus_observed_entry_count: 0,
|
||||||
|
event_coverage_upstream_git_local_corpus_materialized_entry_count: 0,
|
||||||
|
event_coverage_summaries: vec![],
|
||||||
diagnostics_clean: true,
|
diagnostics_clean: true,
|
||||||
blocking_issue_count: 0,
|
blocking_issue_count: 0,
|
||||||
missing_trade_event_count: 6,
|
missing_trade_event_count: 6,
|
||||||
@@ -1658,6 +1809,98 @@ mod tests {
|
|||||||
assert_eq!(report.fee_event_count, 2);
|
assert_eq!(report.fee_event_count, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validation_accepts_0_7_48_pre_event_coverage_checkpoint() {
|
||||||
|
let mut summary = make_0_7_28_summary_with_meteora();
|
||||||
|
summary.event_coverage_listed_entry_count = 4;
|
||||||
|
summary.event_coverage_decoded_entry_count = 3;
|
||||||
|
summary.event_coverage_observed_entry_count = 2;
|
||||||
|
summary.event_coverage_materialized_entry_count = 1;
|
||||||
|
summary.event_coverage_total_observed_count = 8;
|
||||||
|
summary.event_coverage_total_materialized_count = 5;
|
||||||
|
summary.event_coverage_trade_count = 4;
|
||||||
|
summary.event_coverage_audit_only_entry_count = 1;
|
||||||
|
summary.event_coverage_upstream_git_mapped_unverified_entry_count = 1;
|
||||||
|
summary.event_coverage_upstream_git_local_corpus_observed_entry_count = 1;
|
||||||
|
summary.event_coverage_upstream_git_local_corpus_materialized_entry_count = 1;
|
||||||
|
summary.event_coverage_summaries.push(crate::DexEventCoverageSummaryDto {
|
||||||
|
decoder_code: "raydium-cpmm".to_string(),
|
||||||
|
listed_entry_count: 4,
|
||||||
|
decoded_entry_count: 3,
|
||||||
|
observed_entry_count: 2,
|
||||||
|
materialized_entry_count: 1,
|
||||||
|
total_observed_count: 8,
|
||||||
|
total_materialized_count: 5,
|
||||||
|
trade_count: 4,
|
||||||
|
audit_only_entry_count: 1,
|
||||||
|
missing_db_target_entry_count: 0,
|
||||||
|
upstream_git_unverified_entry_count: 0,
|
||||||
|
upstream_git_mapped_unverified_entry_count: 1,
|
||||||
|
upstream_git_local_corpus_observed_entry_count: 1,
|
||||||
|
upstream_git_local_corpus_materialized_entry_count: 1,
|
||||||
|
});
|
||||||
|
let config =
|
||||||
|
crate::LocalPipelineValidationConfig::v0_7_48_pre_event_coverage_db_checkpoint();
|
||||||
|
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
|
||||||
|
assert!(report.validation_passed);
|
||||||
|
assert_eq!(report.validation_profile_code, "0.7.48-pre_event_coverage_db_checkpoint");
|
||||||
|
assert_eq!(report.event_coverage_listed_entry_count, 4);
|
||||||
|
assert_eq!(report.event_coverage_summaries.len(), 1);
|
||||||
|
assert!(report.expected_dex_codes.contains(&"raydium_cpmm".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validation_accepts_0_7_48_pre_with_unrelated_partial_dex_gap() {
|
||||||
|
let mut summary = make_0_7_28_summary_with_meteora();
|
||||||
|
summary.actionable_missing_trade_event_count = 2;
|
||||||
|
summary.decoded_trade_candidate_without_trade_event_on_ok_transaction_count = 2;
|
||||||
|
summary.decoded_trade_candidate_without_trade_event_count = 2;
|
||||||
|
summary.decoded_trade_candidate_without_amount_payload_count = 2;
|
||||||
|
summary
|
||||||
|
.decoded_event_summaries
|
||||||
|
.push(crate::LocalDecodedEventDiagnosticSummaryDto {
|
||||||
|
protocol_name: "fluxbeam".to_string(),
|
||||||
|
event_kind: "fluxbeam.swap".to_string(),
|
||||||
|
event_category: Some("trade".to_string()),
|
||||||
|
event_lifecycle_kind: Some("trade_swap".to_string()),
|
||||||
|
event_actionability: Some("trade_candidate".to_string()),
|
||||||
|
non_trade_useful: Some(false),
|
||||||
|
trade_candidate: Some(true),
|
||||||
|
candle_candidate: Some(true),
|
||||||
|
event_count: 2,
|
||||||
|
trade_event_count: 0,
|
||||||
|
});
|
||||||
|
let config =
|
||||||
|
crate::LocalPipelineValidationConfig::v0_7_48_pre_event_coverage_db_checkpoint();
|
||||||
|
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
|
||||||
|
assert!(report.validation_passed);
|
||||||
|
assert_eq!(report.blocking_issue_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validation_rejects_0_7_48_pre_expected_dex_trade_gap() {
|
||||||
|
let mut summary = make_0_7_28_summary_with_meteora();
|
||||||
|
summary
|
||||||
|
.decoded_event_summaries
|
||||||
|
.push(crate::LocalDecodedEventDiagnosticSummaryDto {
|
||||||
|
protocol_name: "raydium_cpmm".to_string(),
|
||||||
|
event_kind: "raydium_cpmm.swap_base_input".to_string(),
|
||||||
|
event_category: Some("trade".to_string()),
|
||||||
|
event_lifecycle_kind: Some("trade_swap".to_string()),
|
||||||
|
event_actionability: Some("trade_candidate".to_string()),
|
||||||
|
non_trade_useful: Some(false),
|
||||||
|
trade_candidate: Some(true),
|
||||||
|
candle_candidate: Some(true),
|
||||||
|
event_count: 7,
|
||||||
|
trade_event_count: 6,
|
||||||
|
});
|
||||||
|
let config =
|
||||||
|
crate::LocalPipelineValidationConfig::v0_7_48_pre_event_coverage_db_checkpoint();
|
||||||
|
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
|
||||||
|
assert!(!report.validation_passed);
|
||||||
|
assert_eq!(report.issues[0].code, "expected_dex_trade_candidates_without_trade_events");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
|
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
|
||||||
let mut summary = make_0_7_28_summary_with_meteora();
|
let mut summary = make_0_7_28_summary_with_meteora();
|
||||||
|
|||||||
Reference in New Issue
Block a user