Données on-chain et SQL · Leçon 6 sur 9
Qualité des données, normalisation et provenance
11 min de lecture
La plupart des chiffres erronés dans l’analyse des blockchains ne proviennent pas de mauvaises heuristiques, mais d’erreurs de données ordinaires : un montant erroné d’un facteur de 10^12, une limite de journée définie dans le mauvais fuseau horaire, un transfert compté deux fois, une transaction Polygon incluse dans les totaux d’Ethereum. Ces erreurs sont faciles à commettre et difficiles à repérer dans un graphique finalisé. Cette leçon présente les règles de normalisation qui permettent de les éviter et la fiche de provenance qui permet à quiconque de vérifier votre travail.
Unités : conservez les entiers jusqu’à la fin
Les blockchains stockent les montants sous forme de nombres entiers exprimés dans la plus petite unité. Les séparateurs décimaux sont une convention d’affichage.
| Actif | Unité de base | Conversion |
|---|---|---|
| Bitcoin | satoshi | 1 BTC = 100 000 000 sat (10^8) |
| Ether | wei | 1 ETH = 10^18 wei ; 1 gwei = 10^9 wei |
| Jetons ERC-20 | unités brutes | Divisez par 10^decimals, dont la valeur est lue dans le contrat du jeton |
Le nombre de décimales des jetons varie : 18 est courant, mais USD Coin en utilise 6 et Wrapped Bitcoin en utilise 8. La valeur decimals est facultative dans la norme ERC-20 et purement informative : les soldes du contrat sont toujours des entiers bruts. Lisez-la dans chaque contrat sur chaque chaîne plutôt que de la supposer, car une version d’un jeton transférée par un pont peut différer de l’original. Une hypothèse erronée peut avoir de lourdes conséquences : un montant brut de 2 500 000 000 correspond à 2 500 jetons avec 6 décimales, mais à 0,0000000025 avec 18 décimales, soit un facteur de 10^12.
Deux pièges en découlent. Le premier est la virgule flottante. Bitcoin Core renvoie les montants en BTC sous forme de nombres JSON tels que 0.00485900, et un analyseur peu rigoureux les transforme en valeurs binaires à virgule flottante qui ne peuvent pas représenter exactement la plupart des fractions décimales ; les sommes présentent alors de légers écarts et les rapprochements échouent. Analysez les montants comme des chaînes décimales et convertissez-les immédiatement en nombres entiers de satoshis. Le second est le dépassement de capacité. Les montants Ethereum sont des entiers non signés de 256 bits, pouvant atteindre environ 1,16 × 10^77. Un BIGINT signé de 64 bits plafonne à 9 223 372 036 854 775 807, ce qui ne représente, en wei, qu’environ 9,22 ETH, et même un DECIMAL de 38 chiffres ne peut pas contenir toutes les valeurs possibles. Stockez les montants bruts sous forme de texte ou dans un type numérique de grande capacité, et ne les convertissez qu’à la dernière étape :
-- Les montants bruts restent exacts ; la conversion sert uniquement à l’affichage
SELECT tt.transaction_hash,
tt.log_index,
tt.amount_raw, -- entier exact conservé sous forme de texte
CAST(tt.amount_raw AS DECIMAL(38, 0))
/ POWER(10, tk.decimals) AS amount_display -- approximation
FROM token_transfers tt
JOIN tokens tk
ON tk.chain_id = tt.chain_id
AND tk.token_address = tt.token_address;
La jointure sur chain_id et sur l’adresse du contrat sélectionne la bonne valeur de decimals pour chaque jeton sur chaque chaîne. La conversion de type échoue pour les valeurs dépassant 38 chiffres, ce qui signale les jetons indésirables dont les quantités émises sont absurdes, au lieu d’altérer silencieusement les totaux ; certains moteurs proposent à la place des décimaux de 76 chiffres ou des types natifs de 256 bits. POWER renvoie un nombre à virgule flottante dans la plupart des moteurs ; amount_display est donc une approximation, acceptable uniquement pour l’affichage ; les sommes et les rapprochements utilisent amount_raw. Ne divisez pas plutôt par un DECIMAL(38, 0) : certains moteurs attribuent au résultat l’échelle des opérandes, ici zéro, et le tronquent silencieusement en un nombre entier.
Temps : UTC et horodatage des blocs
Les horodatages des blocs sont exprimés en temps Unix : le nombre de secondes écoulées depuis le 1er janvier 1970, en UTC. Conservez chaque horodatage en UTC, regroupez les jours et les mois selon UTC et précisez-le dans le rapport. La conversion vers un fuseau horaire local fait passer des transactions d’un jour à l’autre, et les changements d’heure créent des heures qui se produisent deux fois ou pas du tout. Une transaction minée à 23:30 UTC le 31 mars appartient au mois de mars dans un rapport en UTC, mais au 1er avril pour un analyste dont les outils affichent silencieusement UTC+2 ; si deux équipes comparent des totaux mensuels établis de cette façon, elles obtiendront des résultats différents sans comprendre pourquoi. Stockez les horodatages sous forme de valeurs UTC (ou de secondes Unix brutes) et ne les convertissez que dans la couche de présentation, en indiquant le fuseau utilisé.
Gardez à l’esprit ce que signifie l’horodatage. Il correspond à l’heure du bloc, et non au moment de la signature ou de la diffusion ; sur Bitcoin, il peut être antérieur à celui du bloc précédent. Ordonnez les événements par hauteur de bloc (et par position au sein du bloc), et non par horodatage. Lorsque vous rapprochez des activités sur plusieurs chaînes, tenez compte des différences d’intervalles entre les blocs et de règles d’horodatage.
Identifiants et formats
Un même identifiant peut s’écrire de plusieurs façons, et les jointures échouent silencieusement lorsque les formats diffèrent.
- Les adresses EVM apparaissent sous la forme à casse mixte avec somme de contrôle EIP-55 (
0xA1b2…9F) dans les portefeuilles et les explorateurs, mais en minuscules dans les journaux et la plupart des jeux de données. Convertissez toutes les adresses et empreintes hexadécimales en minuscules pour le stockage et les jointures ; ne conservez la forme avec somme de contrôle que pour l’affichage. - Valeurs complétées par remplissage : une adresse dans un topic de journal occupe 32 octets ; l’adresse correspond aux 20 derniers.
- Les txid Bitcoin sont affichés dans l’ordre inverse de celui de leurs octets en interne ; stockez la forme affichée.
- Adresses Bitcoin : les adresses bech32 et bech32m (
bc1…) ne sont pas sensibles à la casse et s’écrivent conventionnellement en minuscules, mais les adresses Base58 (1…,3…) sont sensibles à la casse. Convertir une adresse Base58 en minuscules la rend inutilisable. - Les quantités hexadécimales provenant de l’interface JSON-RPC d’Ethereum (
"0x1406f52") doivent être interprétées en base 16 ; les lire comme des chaînes décimales ou supprimer le préfixe produit des résultats incohérents.
Identifiants de chaîne
La même adresse peut exister sur des dizaines de chaînes EVM, contrôlée par la même clé mais détenant des soldes sans rapport entre eux, et les empreintes de transaction ont partout le même format. Chaque ligne doit comporter un identifiant de chaîne. Les chaînes EVM utilisent le chain ID, introduit par EIP-155 pour la protection contre le rejeu et renvoyé par eth_chainId :
| Chaîne | Chain ID |
|---|---|
| Réseau principal Ethereum | 1 |
| OP Mainnet | 10 |
| BNB Smart Chain | 56 |
| Gnosis | 100 |
| Polygon PoS | 137 |
| Base | 8453 |
| Arbitrum One | 42161 |
| Avalanche C-Chain | 43114 |
| Sepolia (réseau de test) | 11155111 |
Un registre des identifiants de chaîne maintenu par la communauté sert généralement de référence pour les autres chaînes. Utilisez eth_chainId plutôt que net_version, dont l’identifiant de réseau peut différer de l’identifiant de chaîne. Excluez explicitement les réseaux de test et traitez les jetons transférés par des ponts comme des actifs distincts, dotés de leurs propres adresses de contrat sur chaque chaîne ; la leçon Ponts, couches 2, chaînes latérales et autres architectures de chaînes explique comment mettre en correspondance les flux entre eux. Bitcoin n’a pas d’identifiant de chaîne, mais ses réseaux de test utilisent des préfixes d’adresse différents (tb1… au lieu de bc1…).
Déduplication et clés naturelles
Les doublons apparaissent lors de chargements historiques qui se chevauchent, de nouvelles tentatives de requêtes, de réorganisations (la même transaction étant enregistrée dans deux blocs) et de fusions d’exports. Définir une clé naturelle pour chaque type d’enregistrement permet de les détecter :
| Enregistrement | Clé naturelle |
|---|---|
| Bloc | block_hash |
| Transaction Bitcoin | block_hash, txid |
| Sortie Bitcoin | txid, vout |
| Transaction EVM | chain_id, hash |
| Journal EVM | chain_id, block_hash, log_index |
| Trace EVM | chain_id, transaction_hash, trace_address |
Pourquoi une transaction Bitcoin a-t-elle besoin de l’empreinte du bloc ? Parce que l’unicité des txid n’est pas garantie : deux anciennes transactions coinbase ont été répétées à l’identique dans les blocs 91 842 et 91 880, que BIP 30 a admis comme exceptions ; BIP 34 a ensuite rendu cela impossible en exigeant la hauteur du bloc dans chaque transaction coinbase. La requête ci-dessous conserve une seule copie de chaque journal, et uniquement les journaux provenant de blocs canoniques :
-- Une ligne par journal, provenant uniquement de blocs canoniques
WITH ranked AS (
SELECT l.*,
ROW_NUMBER() OVER (
PARTITION BY l.chain_id, l.block_hash, l.log_index
ORDER BY l.ingested_at DESC) AS rn
FROM raw_logs l
)
SELECT *
FROM ranked
WHERE rn = 1
AND block_hash IN (SELECT block_hash FROM canonical_blocks);
ROW_NUMBER numérote les copies de chaque clé et conserve la dernière ingérée ; le filtre final supprime les journaux des blocs remplacés par une réorganisation. Les doublons logiques comptent également : comptabiliser un journal Transfer et la trace du même appel de jeton, ou de l’ETH natif et du WETH, revient à compter deux fois la valeur.
Enregistrement de la provenance
La provenance consigne l’origine d’un jeu de données et la manière dont il a été constitué. Joignez une fiche de provenance à chaque extraction qui étaye une constatation :
{
"dataset": "token_transfers_to_target",
"chain_id": 1,
"block_range": [21000000, 21000999],
"anchor_block_hash": "0x5e1c…a7d0",
"finality": "finalized",
"source": "own archive node",
"client_version": "Geth/v1.16.x",
"method": "eth_getLogs",
"query_file": "queries/transfers_to_target.sql",
"retrieved_at_utc": "2026-10-28T14:05:00Z",
"row_count": 37,
"output_sha256": "b94d…e9f1"
}
La plage de blocs et l’empreinte du bloc d’ancrage fixent les données ; le statut de finalité montre qu’elles ne peuvent plus changer ; la source, la version du client et la méthode indiquent comment elles ont été obtenues ; le fichier de requête consigne la transformation ; l’heure de récupération en UTC les date ; enfin, le nombre de lignes et l’empreinte SHA-256 du fichier de sortie permettent à quiconque de confirmer qu’il détient les mêmes données. Un vérificateur disposant d’un nœud peut réexécuter la requête et comparer les résultats. La leçon Outils, reproductibilité et validation étend cette démarche aux requêtes versionnées dans un entrepôt de données.
Erreurs de données courantes
| Erreur | Symptôme | Prévention |
|---|---|---|
| Montants à virgule flottante | Écarts dans les derniers chiffres des totaux | Unités de base entières, analyse décimale |
| Nombre de décimales erroné | Montants erronés d’un facteur de 10^12 (6 traitées comme 18) | Lire decimals pour chaque contrat et chaque chaîne |
| Fuseau horaire local | Totaux quotidiens décalés d’un jour à l’autre | UTC à toutes les étapes |
| Identifiant de chaîne manquant | Activité d’une chaîne latérale incluse dans les totaux du réseau principal | chain_id sur chaque ligne et dans chaque jointure |
| Transactions échouées comptabilisées | Valeur qui n’a jamais été déplacée | Filtrer sur status = 1 et sur les erreurs de trace |
| Lignes issues de blocs réorganisés conservées | Transactions absentes de la chaîne finale | Joindre les blocs canoniques par leur empreinte |
| Identifiants altérés par un changement de casse | Lignes omises par les jointures ; adresses Base58 inutilisables | Convertir en minuscules uniquement l’hexadécimal |
Un contrôle final peu coûteux permet de détecter plusieurs de ces erreurs à la fois : sélectionnez les trois à cinq lignes les plus importantes pour une constatation et vérifiez chacune auprès d’un nœud ou d’une seconde source, en comparant l’empreinte du bloc, le montant en unités de base et l’horodatage. Si une seule ligne présente un écart, arrêtez-vous et trouvez-en la cause avant de vous fier à l’agrégat.
Points clés pour l’examen : satoshi, wei, gwei et nombre de décimales des jetons, et raisons de conserver les montants sous forme d’entiers ; risques liés à la virgule flottante et au dépassement de capacité sur 64 bits (BIGINT ne contient qu’environ 9,22 ETH en wei) ; horodatages UTC et classement par hauteur de bloc ; sommes de contrôle EIP-55 par opposition au stockage en minuscules et sensibilité à la casse de Base58 ; identifiants de chaîne (EIP-155, eth_chainId) et raisons pour lesquelles chaque ligne doit en comporter un ; clés naturelles pour la déduplication et txid dupliqués de BIP 30 ; champs d’une fiche de provenance ; erreurs de données courantes et moyens de les éviter.Questions d’entraînement
Vérifiez votre compréhension. Ces questions ne comptent pas pour l’examen.
Connectez-vous pour enregistrer votre progression et reprendre là où vous vous êtes arrêté. C’est gratuit. Se connecter