On-Chain-Daten und SQL · Lektion 6 von 9
Datenqualität, Normalisierung und Provenienz
9 Min. Lesezeit
Die meisten falschen Zahlen in der Blockchain-Analyse entstehen nicht durch schlechte Heuristiken, sondern durch alltägliche Datenfehler: ein um den Faktor 10^12 falscher Betrag, eine Tagesgrenze in der falschen Zeitzone, ein doppelt gezählter Transfer, eine Polygon-Transaktion, die in Ethereum-Gesamtsummen einfließt. Solche Fehler passieren leicht und sind in einem fertigen Diagramm schwer zu erkennen. Diese Lektion erläutert die Normalisierungsregeln, die solche Fehler verhindern, sowie den Provenienznachweis, mit dem jeder Ihre Arbeit überprüfen kann.
Einheiten: Ganzzahlen bis zum Schluss beibehalten
Blockchains speichern Beträge als Ganzzahlen in der kleinsten Einheit. Dezimaltrennzeichen sind eine Darstellungskonvention.
| Vermögenswert | Basiseinheit | Umrechnung |
|---|---|---|
| Bitcoin | satoshi | 1 BTC = 100.000.000 sat (10^8) |
| Ether | wei | 1 ETH = 10^18 wei; 1 gwei = 10^9 wei |
| ERC-20-Token | Roheinheiten | Durch 10^decimals dividieren; den Wert aus dem Token-Vertrag auslesen |
Token-Dezimalstellen variieren: 18 sind üblich, aber USD Coin verwendet 6 und Wrapped Bitcoin 8. Der Wert decimals ist im ERC-20-Standard optional und dient ausschließlich der Information: Die Kontostände des Vertrags sind immer rohe Ganzzahlen. Lesen Sie ihn für jeden Vertrag auf jeder Chain aus, statt Annahmen zu treffen, denn eine über eine Bridge übertragene Version eines Tokens kann vom Original abweichen. Eine falsche Annahme hat erhebliche Folgen: Ein Rohbetrag von 2.500.000.000 entspricht bei 6 Dezimalstellen 2.500 Token, bei 18 Dezimalstellen jedoch 0,0000000025 – ein Faktor von 10^12.
Daraus ergeben sich zwei Fallstricke. Erstens: Gleitkommazahlen. Bitcoin Core gibt Beträge in BTC als JSON-Zahlen wie 0.00485900 zurück. Ein unbedacht eingesetzter Parser wandelt sie in binäre Gleitkommawerte um, die die meisten Dezimalbrüche nicht exakt darstellen können. Dadurch weichen Summen um winzige Beträge ab und Abgleiche schlagen fehl. Parsen Sie Beträge als Dezimalzeichenfolgen und wandeln Sie sie sofort in ganzzahlige satoshi um. Zweitens: Überlauf. Ethereum-Beträge sind vorzeichenlose 256-Bit-Ganzzahlen mit einem Höchstwert von etwa 1,16 × 10^77. Ein vorzeichenbehafteter 64-Bit-BIGINT hat einen Höchstwert von 9.223.372.036.854.775.807, was in wei nur etwa 9,22 ETH entspricht. Selbst ein DECIMAL mit 38 Stellen kann nicht jeden möglichen Wert aufnehmen. Speichern Sie Rohbeträge als Text oder in einem numerischen Datentyp mit großem Wertebereich und rechnen Sie erst im letzten Schritt um:
-- Rohbeträge bleiben exakt; die Umrechnung erfolgt nur für die Anzeige
SELECT tt.transaction_hash,
tt.log_index,
tt.amount_raw, -- exakte Ganzzahl als Text gespeichert
CAST(tt.amount_raw AS DECIMAL(38, 0))
/ POWER(10, tk.decimals) AS amount_display -- Näherungswert
FROM token_transfers tt
JOIN tokens tk
ON tk.chain_id = tt.chain_id
AND tk.token_address = tt.token_address;
Der Join über chain_id und Vertragsadresse wählt für jeden Token auf jeder Chain den richtigen decimals-Wert aus. Die Typumwandlung schlägt bei Werten mit mehr als 38 Stellen fehl. Dadurch werden Spam-Token mit absurden Angebotsmengen kenntlich gemacht, statt Gesamtsummen unbemerkt zu verfälschen; einige Engines bieten stattdessen Dezimaltypen mit 76 Stellen oder native 256-Bit-Typen an. POWER liefert in den meisten Engines eine Gleitkommazahl zurück. Daher ist amount_display ein Näherungswert, der nur für die Anzeige geeignet ist; Summen und Abgleiche verwenden amount_raw. Dividieren Sie stattdessen nicht durch einen Wert vom Typ DECIMAL(38, 0): Einige Engines übernehmen für das Ergebnis die Anzahl der Nachkommastellen der Operanden, hier null, und schneiden es stillschweigend auf eine Ganzzahl ab.
Zeit: UTC und Blockzeit
Blockzeitstempel sind Unix-Zeit: Sekunden seit dem 1. Januar 1970, UTC. Halten Sie alle Zeitstempel in UTC, gruppieren Sie Tage und Monate nach UTC und geben Sie dies im Bericht an. Die Umrechnung in eine lokale Zeitzone verschiebt Transaktionen über Tagesgrenzen hinweg, und durch die Zeitumstellung entstehen Stunden, die doppelt oder gar nicht vorkommen. Eine Transaktion, die am 31. März um 23:30 UTC in einen Block aufgenommen wurde, gehört in einem UTC-Bericht zum März, für einen Analysten, dessen Werkzeuge stillschweigend UTC+2 anzeigen, jedoch zum 1. April. Vergleichen zwei Teams derart erstellte Monatssummen, werden diese voneinander abweichen, ohne dass eines der Teams den Grund bemerkt. Speichern Sie Zeitstempel als UTC-Werte oder als rohe Unix-Sekunden und rechnen Sie sie erst in der Darstellungsschicht um. Kennzeichnen Sie dabei die verwendete Zeitzone.
Beachten Sie, was der Zeitstempel bedeutet. Er gibt die Zeit des Blocks an, nicht den Zeitpunkt der Signierung oder Verbreitung, und bei Bitcoin kann er vor dem Zeitstempel des vorherigen Blocks liegen. Ordnen Sie Ereignisse nach Blockhöhe und Position innerhalb des Blocks, nicht nach Zeitstempel. Berücksichtigen Sie beim Abgleich von Aktivitäten über mehrere Chains hinweg unterschiedliche Blockintervalle und Zeitstempelregeln.
Kennungen und ihre Formate
Dieselbe Kennung kann auf verschiedene Arten geschrieben werden. Bei unterschiedlichen Formaten schlagen Joins unbemerkt fehl.
- EVM-Adressen erscheinen in Wallets und Explorern in der EIP-55-Prüfsummenschreibweise mit gemischter Groß- und Kleinschreibung (
0xA1b2…9F), in Logs und den meisten Datensätzen jedoch in Kleinbuchstaben. Wandeln Sie für die Speicherung und für Joins alle Hex-Adressen und Hashes in Kleinbuchstaben um; behalten Sie die Prüfsummenschreibweise nur für die Anzeige bei. - Aufgefüllte Werte: Eine Adresse in einem Log-Topic ist 32 Bytes lang; die Adresse besteht aus den letzten 20 Bytes.
- Bitcoin-txids werden in der umgekehrten Reihenfolge ihrer internen Byte-Reihenfolge angezeigt; speichern Sie die Anzeigeform.
- Bitcoin-Adressen: Bei bech32- und bech32m-Adressen (
bc1…) wird nicht zwischen Groß- und Kleinschreibung unterschieden; üblicherweise werden sie kleingeschrieben. Bei Base58-Adressen (1…,3…) wird dagegen zwischen Groß- und Kleinschreibung unterschieden. Die Umwandlung einer Base58-Adresse in Kleinbuchstaben zerstört sie. - Hexadezimale Mengenangaben aus Ethereum-JSON-RPC (
"0x1406f52") müssen zur Basis 16 geparst werden. Werden sie als Dezimalzeichenfolgen gelesen oder wird das Präfix entfernt, entstehen unsinnige Ergebnisse.
Chain-Kennungen
Dieselbe Adresse kann auf Dutzenden EVM-Chains existieren, durch denselben Schlüssel kontrolliert werden und dennoch voneinander unabhängige Guthaben halten. Auch Transaktions-Hashes haben überall dasselbe Format. Jede Zeile benötigt eine Chain-Kennung. EVM-Chains verwenden die Chain-ID, die mit EIP-155 zum Schutz vor Replay-Angriffen eingeführt wurde und von eth_chainId zurückgegeben wird:
| Chain | Chain-ID |
|---|---|
| Ethereum-Mainnet | 1 |
| OP Mainnet | 10 |
| BNB Smart Chain | 56 |
| Gnosis | 100 |
| Polygon PoS | 137 |
| Base | 8453 |
| Arbitrum One | 42161 |
| Avalanche C-Chain | 43114 |
| Sepolia (Testnetz) | 11155111 |
Für weitere Chains dient üblicherweise ein von der Community gepflegtes Verzeichnis der Chain-IDs als Referenz. Verwenden Sie eth_chainId statt net_version, dessen Netzwerk-ID von der Chain-ID abweichen kann. Schließen Sie Testnetze ausdrücklich aus und behandeln Sie über Bridges übertragene Token als eigenständige Vermögenswerte mit eigenen Vertragsadressen auf jeder Chain. Die Lektion Bridges, Layer 2, Sidechains und weitere Chain-Architekturen behandelt die Zuordnung der Flüsse zwischen ihnen. Bitcoin hat keine Chain-ID, seine Testnetze verwenden jedoch andere Adresspräfixe (tb1… statt bc1…).
Deduplizierung und natürliche Schlüssel
Duplikate entstehen durch überlappende rückwirkende Datenimporte, wiederholte Anfragen, Reorganisationen, bei denen dieselbe Transaktion in zwei Blöcken erfasst wird, und zusammengeführte Exporte. Durch die Definition eines natürlichen Schlüssels für jeden Datensatztyp lassen sie sich erkennen:
| Datensatz | Natürlicher Schlüssel |
|---|---|
| Block | block_hash |
| Bitcoin-Transaktion | block_hash, txid |
| Bitcoin-Ausgabe | txid, vout |
| EVM-Transaktion | chain_id, hash |
| EVM-Log | chain_id, block_hash, log_index |
| EVM-Trace | chain_id, transaction_hash, trace_address |
Warum benötigt eine Bitcoin-Transaktion den Block-Hash? Weil die Eindeutigkeit von txids nicht garantiert ist: Zwei frühe Coinbase-Transaktionen wurden in den Blöcken 91.842 und 91.880 exakt wiederholt. BIP 30 ließ diese als bestehende Ausnahmen zu; BIP 34 machte solche Wiederholungen später unmöglich, indem die Blockhöhe in jeder Coinbase vorgeschrieben wurde. Die folgende Abfrage behält eine Kopie jedes Logs und nur Logs aus kanonischen Blöcken bei:
-- Eine Zeile pro Log, ausschließlich aus kanonischen Blöcken
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 nummeriert die Kopien jedes Schlüssels und behält die zuletzt eingelesene bei; der abschließende Filter entfernt Logs aus Blöcken, die durch eine Reorganisation ersetzt wurden. Auch logische Duplikate sind relevant: Werden ein Transfer-Log und der Trace desselben Token-Aufrufs oder natives ETH und WETH gezählt, wird der Wert doppelt erfasst.
Provenienz dokumentieren
Provenienz dokumentiert, woher ein Datensatz stammt und wie er erstellt wurde. Fügen Sie jedem Datenextrakt, der einen Befund stützt, einen Provenienznachweis bei:
{
"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"
}
Der Blockbereich und der Anker-Hash legen die Daten fest; der Finalitätsstatus zeigt, dass sie sich nicht mehr ändern können; Quelle, Client-Version und Methode geben an, wie sie beschafft wurden; die Abfragedatei dokumentiert die Transformation; die Abrufzeit in UTC datiert sie; und die Zeilenanzahl sowie der SHA-256-Hash der Ausgabedatei ermöglichen es jedem zu bestätigen, dass dieselben Daten vorliegen. Eine prüfende Person mit einem Knoten kann die Abfrage erneut ausführen und die Ergebnisse vergleichen. Die Lektion Werkzeuge, Reproduzierbarkeit und Validierung erweitert dies auf versionierte Abfragen in einem Data Warehouse.
Häufige Datenfehler
| Fehler | Symptom | Vorbeugung |
|---|---|---|
| Gleitkommabeträge | Gesamtsummen weichen in den letzten Stellen ab | Ganzzahlige Basiseinheiten, dezimales Parsen |
| Falsche Dezimalstellen | Beträge weichen um den Faktor 10^12 ab (6 als 18 behandelt) | decimals je Vertrag und Chain auslesen |
| Lokale Zeitzone | Tagessummen zwischen Tagen verschoben | Durchgehend UTC verwenden |
| Fehlende Chain-ID | Sidechain-Aktivitäten in Mainnet-Gesamtsummen | chain_id in jeder Zeile und jedem Join |
| Fehlgeschlagene Transaktionen mitgezählt | Wert, der nie übertragen wurde | Nach status = 1 und Trace-Fehlern filtern |
| Zeilen aus reorganisierten Blöcken beibehalten | Transaktionen fehlen in der endgültigen Chain | Über den Hash mit kanonischen Blöcken verknüpfen |
| Durch Änderung der Groß- und Kleinschreibung verfälschte Kennungen | Joins lassen Zeilen aus; Base58-Adressen beschädigt | Nur Hex-Kennungen in Kleinbuchstaben umwandeln |
Ein einfacher Abschlusstest deckt viele dieser Fehler auf einmal auf: Wählen Sie drei bis fünf Zeilen aus, die für einen Befund am wichtigsten sind, und prüfen Sie jede anhand eines Knotens oder einer zweiten Quelle. Vergleichen Sie dabei Block-Hash, Betrag in Basiseinheiten und Zeitstempel. Wenn auch nur eine Zeile abweicht, halten Sie inne und ermitteln Sie den Grund, bevor Sie dem Aggregat vertrauen.
Prüfungsschwerpunkt: satoshi, wei, gwei und Token-Dezimalstellen sowie der Grund, weshalb Beträge Ganzzahlen bleiben; Risiken durch Gleitkommazahlen und 64-Bit-Überläufe (BIGINT fasst in wei nur etwa 9,22 ETH); UTC-Zeitstempel und Sortierung nach Blockhöhe; EIP-55-Prüfsummen im Vergleich zur Speicherung in Kleinbuchstaben und die Unterscheidung zwischen Groß- und Kleinschreibung bei Base58; Chain-IDs (EIP-155, eth_chainId) und warum jede Zeile eine benötigt; natürliche Schlüssel zur Deduplizierung und die doppelten txids aus BIP 30; die Felder eines Provenienznachweises; häufige Datenfehler und deren Vermeidung.