On-Chain-Daten und SQL · Lektion 4 von 9
Rohe Ethereum-Transaktionen, Receipts und Logs
8 Min. Lesezeit
Auf Ethereum und anderen EVM-Blockchains hält eine Transaktion fest, was ein Absender angefordert hat, während das Receipt, die Logs und die Traces festhalten, was tatsächlich geschehen ist. Token-Bewegungen, fehlgeschlagene Aufrufe und durch Contracts bewegte Werte erscheinen nie in der Transaktion selbst. Wer als Analyst nur Transaktionen liest, übersieht den Großteil der Aktivitäten auf der Blockchain; wer die vier Objekte kennt, kann präzise angeben, was bewegt wurde, wer es ausgelöst hat und ob es erfolgreich war.
Transaktionsfelder und die fünf Typen
Seit EIP-2718 (2021) ist eine Ethereum-Transaktion eine typisierte Hülle: ein Typ-Byte, gefolgt von einer Nutzlast, deren Felder vom Typ abhängen. Legacy-Transaktionen, die vor der Einführung dieser Hülle entstanden, gelten als Typ 0.
| Typ | Eingeführt | Gebührenfelder | Zusätzliche Felder |
|---|---|---|---|
0x0 Legacy | Ursprünglicher Typ; Chain-ID durch EIP-155 (2016) ergänzt | gasPrice | Chain-ID in v integriert |
0x1 Zugriffsliste | EIP-2930, Berlin (April 2021) | gasPrice | accessList |
0x2 dynamische Gebühr | EIP-1559, London (August 2021) | maxFeePerGas, maxPriorityFeePerGas | accessList |
0x3 Blob | EIP-4844, Dencun (März 2024) | Wie Typ 2, zusätzlich maxFeePerBlobGas | blobVersionedHashes |
0x4 Code setzen | EIP-7702, Pectra (Mai 2025) | Wie Typ 2 | authorizationList |
Allen Typen gemeinsam sind nonce (der Transaktionszähler des Absenders), gas (das Gas-Limit), to, value (in wei), input (Aufrufdaten) und eine Signatur (v oder yParity, r, s). Beachten Sie, dass from kein Feld ist: Nodes rekonstruieren den Absender aus der Signatur und ergänzen ihn in API-Antworten. In einer Legacy-Transaktion ist die Chain-ID in v als chainId × 2 + 35 oder 36 codiert; typisierte Transaktionen enthalten ein explizites chainId.
Zwei neuere Typen sind für Analysten wichtig:
- Blob-Transaktionen (Typ 3) transportieren große Daten-Blobs für Rollups. Die Blobs sind nicht Teil des Ausführungsblocks: Sie werden als Sidecars der Konsensschicht übertragen, die Nodes nur für 4.096 Epochen, also etwa 18 Tage, aufbewahren müssen. Die Transaktion enthält von jedem Blob nur den versionierten Hash (erstes Byte
0x01). Eine Transaktion vom Typ 3 muss eineto-Adresse haben und kann daher keinen Contract erstellen. - Transaktionen zum Setzen von Code (Typ 4) ermöglichen es einem extern kontrollierten Konto (EOA), an Contract-Code zu delegieren. Jeder Eintrag der Autorisierungsliste ist vom Kontoinhaber signiert und enthält
chainId,address,nonce,yParity,runds. Nach der Verarbeitung besteht der Code des Kontos aus dem 23 Byte langen Delegationskennzeichen0xef0100, gefolgt von der Adresse des Delegaten; eine Delegation an die Nulladresse löscht ihn. Ab diesem Zeitpunkt führen Aufrufe dieses EOA den Code des Delegaten aus, und das Konto, das die Autorisierung signiert hat, muss nicht dasjenige sein, das für das Gas bezahlt hat. Bösartige Delegationen wurden bereits verwendet, um Wallets zu leeren. Wenneth_getCodefür eine scheinbar gewöhnliche Adresse0xef0100…zurückgibt, ist dies daher beachtenswert. Die Lektion Das Kontenmodell und die Rekonstruktion von Kontoständen behandelt die Folgen.
Hier ist eine Transaktion vom Typ 2, wie sie von eth_getTransactionByHash zurückgegeben wird, mit gekürzten langen Werten:
{
"type": "0x2", "chainId": "0x1", "nonce": "0x2a",
"hash": "0x7e4b…a3c0", "blockNumber": "0x1406f52", "transactionIndex": "0x5",
"from": "0xa1b2…009f", "to": "0x5d3a…7c10", "value": "0x0",
"gas": "0x186a0", "maxFeePerGas": "0x9502f9000", "maxPriorityFeePerGas": "0x3b9aca00",
"input": "0xa9059cbb000000000000000000000000c3d4…22e1000…009502f900",
"accessList": [], "yParity": "0x1", "r": "0x8f1e…", "s": "0x2c07…"
}
Alle Mengenangaben sind hexadezimale Zeichenfolgen: Das Gas-Limit 0x186a0 beträgt 100.000, die maximale Gebühr 0x9502f9000 beträgt 40.000.000.000 wei (40 gwei) pro Gas und das Trinkgeld 0x3b9aca00 beträgt 1 gwei. value ist null und to ist ein Contract: Dies ist ein Aufruf, keine ETH-Zahlung.
Receipts und Status
Ein Receipt wird bei der Ausführung der Transaktion erzeugt. eth_getTransactionReceipt gibt Folgendes zurück:
| Feld | Bedeutung |
|---|---|
status | 0x1 für Erfolg oder 0x0 für Fehlschlag (EIP-658, Byzantium, Oktober 2017; ältere Receipts enthalten stattdessen root) |
gasUsed, cumulativeGasUsed | Von dieser Transaktion verbrauchtes Gas sowie im Block bis einschließlich dieser Transaktion verbrauchtes Gas |
effectiveGasPrice | Tatsächlich gezahlter Preis pro Gas |
logs, logsBloom | Ausgegebene Ereignisse und deren Bloom-Filter |
contractAddress | Bei Bereitstellungen die erstellte Adresse; andernfalls null |
blobGasUsed, blobGasPrice | Bei Blob-Transaktionen vorhanden |
type, from, to, blockHash, blockNumber, transactionIndex | Identifikatoren |
Bei einer Transaktion vom Typ 2 ist der effektive Preis der kleinere Wert aus maxFeePerGas und der Summe aus Basisgebühr und maxPriorityFeePerGas. Wäre unser Beispiel bei einer Basisgebühr von 12 gwei ausgeführt worden und hätte es 52.000 Gas verbraucht, hätte der Preis min(40, 12 + 1) = 13 gwei und die Gebühr 52.000 × 13 = 676.000 gwei oder 0,000676 ETH betragen. Bei einer Blob-Transaktion fällt zusätzlich blobGasUsed × blobGasPrice an.
Wenn status den Wert 0x0 hat, wurde der Wert nicht übertragen, jedes Log wurde verworfen und die Zustandsänderungen wurden rückgängig gemacht; die Nonce wurde jedoch verbraucht und die Gebühr bezahlt. status beschreibt nur die Transaktion als Ganzes: Ein interner Aufruf kann fehlschlagen, während die Transaktion erfolgreich ist. Dies zeigen nur Traces.
Logs und Topics
Ein Log (Ereignis) ist ein Datensatz, den ein Contract während der Ausführung schreibt. Jedes Log enthält:
address: den Contract, der es ausgegeben hat;topics: null bis vier Werte von jeweils 32 Byte;data: die übrigen ABI-codierten Bytes;- Positionsfelder:
logIndex(Position im Block),transactionHash,transactionIndex,blockNumber,blockHashsowieremoved, das den Wert true hat, wenn das Log durch eine Reorganisation entfernt wurde.
Bei einem standardmäßigen (nicht anonymen) Solidity-Ereignis ist topics[0] der Keccak-256-Hash der Ereignissignatur, und die nachfolgenden Topics enthalten bis zu drei mit indexed gekennzeichnete Parameter. Das ERC-20-Ereignis Transfer wird wie folgt decodiert:
{
"address": "0x5d3a…7c10",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000a1b2…009f",
"0x000000000000000000000000c3d4…22e1"
],
"data": "0x000000000000000000000000000000000000000000000000000000009502f900",
"logIndex": "0x1c", "removed": false
}
topics[0] ist keccak256 von Transfer(address,address,uint256). Die Topics 1 und 2 sind die links auf 32 Byte aufgefüllten Adressen from und to: Entfernen Sie die ersten 12 Byte, um sie zu erhalten. data enthält den nicht indizierten value: 0x9502f900 entspricht 2.500.000.000 Roheinheiten, also 2.500 Token, wenn der Contract 6 Dezimalstellen angibt. Prüfen Sie immer address. Jeder kann einen Contract bereitstellen, der ein formal vollkommen korrektes Transfer-Ereignis ausgibt, und gefälschte Token, die bekannte Token nachahmen, tun genau dies; das Ereignis ist nur so aussagekräftig wie der Contract, der es geschrieben hat.
Eingabedaten und Grundlagen der ABI-Decodierung
Das Feld input teilt einem Contract mit, welche Funktion er ausführen soll. Seine ersten 4 Byte bilden den Funktionsselektor, den Anfang des Keccak-256-Hashes der Funktionssignatur: transfer(address,uint256) ergibt 0xa9059cbb. Die Argumente folgen gemäß den ABI-Codierungsregeln in 32-Byte-Wörtern: hier der aufgefüllte Empfänger c3d4…22e1, danach der Betrag 0x9502f900.
Für die Decodierung wird die ABI des Contracts (seine Schnittstellenbeschreibung) benötigt, die mit verifiziertem Quellcode veröffentlicht oder anhand öffentlicher Selektordatenbanken rekonstruiert wird. Selektoren sind nur 4 Byte lang, sodass unterschiedliche Signaturen kollidieren können; ein Datenbanktreffer ist daher eine Hypothese und kein Beweis. Betrachten Sie die Eingabe als Anforderung und Logs, Traces sowie den Zustand als Ergebnis: Ein rückgängig gemachter Aufruf von transfer hat nichts bewegt. Die Lektion Interaktionen mit Smart Contracts decodieren behandelt Proxys, Multicall und verifizierten Quellcode.
Traces für interne Aufrufe
Wenn ein Contract einen anderen Contract aufruft oder ETH sendet, ist dieser interne Aufruf keine Transaktion und erzeugt kein Log, sofern der Code keines ausgibt. Er ist nur sichtbar, wenn die Transaktion mit Tracing erneut ausgeführt wird. Es gibt zwei Ausgabeformate:
- Geth-Stil:
debug_traceTransactionmit demcallTracergibt einen verschachtelten Baum von Aufrufrahmen zurück, jeweils mittype(CALL, STATICCALL, DELEGATECALL, CREATE, CREATE2, SELFDESTRUCT),from,to,value,gas,gasUsed,input,output,errorund untergeordnetencalls. - Parity-Stil:
trace_transactionundtrace_block, die von Clients wie Erigon, Nethermind und Reth angeboten werden, geben eine flache Liste zurück, in dertraceAddressdie Position jedes Aufrufs im Baum undaction.callTypedessen Art angibt.
Traces beantworten Fragen, die sich auf keine andere Weise klären lassen: welche ETH ein Contract ausgezahlt hat, welcher Unteraufruf fehlgeschlagen ist und in welchem Kontext Code ausgeführt wurde (ein DELEGATECALL führt den Code eines anderen Contracts unter Verwendung des Speichers und Kontostands des Aufrufers aus). Das Tracing alter Transaktionen erfordert einen Node, der historische Zustände rekonstruieren kann, typischerweise einen Archiv-Node. Durch das Herausfiltern von Rahmen mit einem error und deren untergeordneten Rahmen vermeiden Sie, Werte mitzuzählen, die nie bewegt wurden; die Lektion UTXO-Daten sowie EVM-Logs und -Traces abfragen zeigt, wie dies in SQL umgesetzt wird.
Die vier Objekte zusammenführen
Die vollständige Betrachtung des durchgehenden Beispiels zeigt, wie sich die Objekte ergänzen. Die Transaktion besagt, dass 0xa1b2…009f den Contract 0x5d3a…7c10 aufgefordert hat, transfer für den Empfänger c3d4…22e1 und 2.500.000.000 Roheinheiten auszuführen, ohne dabei ETH zu zahlen. Das Receipt besagt, dass die Anforderung erfolgreich war (status 0x1) und 0,000676 ETH gekostet hat. Das vom selben Contract ausgegebene Log bestätigt, dass sich die Token-Kontostände geändert haben: 2.500 Token von a1b2…009f an c3d4…22e1. Da der Aufruf ein direkter Token-Transfer war, würde ein Trace nur einen Rahmen zeigen und keine weiteren Erkenntnisse liefern.
Ändert sich ein Detail, ändert sich die Interpretation. Hätte der Nutzer stattdessen einen DEX-Router aufgerufen, würde die Transaktion nur die Router-Adresse und eine Swap-Anforderung zeigen; die Logs würden mehrere Transfer-Ereignisse verschiedener Token-Contracts zeigen; und ausgezahlte ETH wären nur in den Traces sichtbar. Die Faustregel lautet: Beginnen Sie mit der Transaktion, prüfen Sie den Status im Receipt, entnehmen Sie Token-Bewegungen den Logs und ETH-Bewegungen dem Feld value und den Traces.
Prüfungsschwerpunkt: die fünf Transaktionstypen (0 bis 4), ihre EIPs, Upgrades und charakteristischen Felder; die Rekonstruktion vonfromaus der Signatur; Blob-Transaktionen, Sidecars und die etwa 18-tägige Aufbewahrung von Blobs; EIP-7702-Autorisierungen und das Delegationskennzeichen0xef0100; Receipt-Felder,status, der effektive Gaspreis und die Kosten, die auch bei einer fehlgeschlagenen Transaktion anfallen; die Log-Struktur,topics[0]als Hash der Ereignissignatur und die Decodierung des Transfer-Ereignisses; Funktionsselektoren und die Frage, warum Selektortreffer Hypothesen sind; Traces im Geth-Stil und im Parity-Stil für interne Aufrufe.
Übungsfragen
Prüfen Sie Ihr Verständnis. Diese Fragen zählen nicht für die Prüfung.
Melden Sie sich an, um Ihren Fortschritt zu speichern und später weiterzumachen. Kostenlos. Anmelden