Unlocking the Secrets of an Ethereum Transaction (part 2)

Decoding Transaction Logs with Python

Logan Bonsignore
Coinmonks
Published in
4 min readJul 13, 2021

--

In part 1 of this series, we used Python to decode transaction input data. Input data holds key insights for understanding the broad scope of a transaction. In this article we focus on transaction logs. Logs allow us dive deeper into individual runtime events that occur during a transaction. We will explore what a transaction log is, why they are important and how to decode them.

What is a transaction log?

Ethereum transaction logs can be compared to logging in traditional app development. Programmers of a traditional app use logging to debug, detect specific events, or notify the viewer of the log that something happened. Logs on the Ethereum Virtual Machine (EVM) serve a very similar purpose as they are used to describe events that occur within a smart contract’s runtime execution. These events can be subscribed to by dapps or anything connected to an Ethereum node that wants to listen for these events. Events inside of a log can also be indexed so that the event history is searchable on the blockchain.

There are two main components to a transaction log — Topics and Data. Take a look at this transaction log as an example. This event log describes the transfer of ETH between two addresses which was done as part of a broader transaction.

Transaction log example

Let’s breakdown Topics. Topics can be described as an array of indexed parameters to an event. topics[0] always refers to the hexadecimal value of the Keccak-256 hash of the event name and input argument types. In our example, topics[0] is equal to Transfer(address,address,unit256). topics[1:] are up to 3 indexed arguments. In our example, topics[1] and topics[2] are addresses in which the Transfer() method used to execute the event.

The Data section can be described as additional data required by the event. In our example, the amount of ETH transferred between addresses is included in the data section in hexadecimal format. The smart contract’s programmer likely programmed it this way because writing to a log’s Topics array requires gas while writing to Data does not. Topics can also be queried while Data cannot. No one is likely to search for an event with a specific amount of ETH transferred so it makes sense to put the amount in the data section to save on gas fees.

Now that we understand a little more about how transaction logs are structured, let’s dive into the code.

Decoding Transactions

We will use the Web3 library to interact with an Ethereum node. For installation and getting started requirements see the Web3 documentation.

Our first step is to get the transaction receipt. We can do this by passing a transaction hash to Web3’s get_transaction_receipt() method. This returns a dictionary containing high-level information about the transaction, including logs.

This transaction contains four logs but we will focus on the first log object for this tutorial. We isolate the log we want to decode as well as the smart contract of the address where the event was initiated.

Next we need to get the ABI of the smart contract which initiated the event. We can do this with Etherscan’s API. You will need an API key and can get one for free from the developer page.

Next we create our Contract object which allows us to interact with smart contracts through our Python code.

Here we isolate the event signature of our log. It is always the first item of the Topics array. We will use this to identify which smart contract function was invoked during this event.

Next we can use our contract’s ABI to find all elements that have an event type. This helps us narrow our search for the function used during the event as the function must have an event type.

Lastly we can loop through our contract’s events to figure out which event matches our log’s event signature. We assemble an event object’s signature by extracting the function name and parameters. We use the join function and f-strings to assemble the function signature. We can then hash the assembled signature to compare it with the signature in our log. When we find a match between these two signatures we know we’ve found the function used in our event. Lastly we can use our Contract object to decode the transaction log by calling the processReceipt method.

Conclusion

In our example, the parent transaction was a simple token exchange on SushiSwap. Decoding the transaction’s logs allows us to understand what happened behind the scenes to ensure this transaction was competed successfully. This tutorial only decoded 1/4 events so to get the full picture our app would need to decode all four.

This foundational skill can be applied to many applications including blockchain intelligence, blockchain data analytics and more. My next article will be about the Substrate framework and how it is changing the way blockchains are being developed. Hit the follow button so you don’t miss it.

If you found this article helpful it would be greatly appreciated if you could leave a comment and applaud. Thank you!

Also, Read

--

--