Ethereum data — Transaction Receipt Trie and Logs Simplified

Kirubakumaresh Rajendran
Coinmonks
Published in
7 min readJul 6, 2022

--

Transaction receipt contains transaction outcomes (status and logs) and are organised in a trie structure. The receipt data resides in the state database and the root hash is stored in the block header. What information does the receipt trie contain? Who stands to benefit? Let’s dig deeper into those questions.

Ethereum Full Node — Transaction Receipt Trie

A smart contract stores information in blockchain in two distinct ways: account storage and logs. Account storage contains any data that defines the smart contract state and that the contract can access. Logs are used to store information that is not required by the contract but must be accessed by other off chain applications (front end, analytics etc). It is also worth noting that log storage is much cheaper than account storage.

Given a simple token contract that allows users to own non fungible (i.e unique) tokens, this is how the data could be stored.

  • Token Ownership → Account storage: The smart contract should obviously know who owns which token. To enable the contract to prove ownership and provide ownership transfer functionality, the token id and token owner should be stored in the account storage.
  • Token Ownership History → Logs: To function, the contract only needs to be aware of current token ownership. Tracking a token’s ownership history, on the other hand, would be of interest to investors or decision makers. This information could be captured in the logs whenever a token is minted or transferred. The logs could then be aggregated to reveal any such insights.
  • UI Notifications → Logs: When a token is minted (created), the front end app may want to show the confirmation and other details to the token owner. Because the transactions are asynchronous, the smart contract cannot return value to the front end. Instead, when the mint occurs, it could write it to the log. The front end could then listen for notifications and display them to the user.
  • Off chain triggers → Logs: When you want to transfer your token to another blockchain for various reasons (for example, to play a game built on a different blockchain), you can initiate a transfer to a common gateway, and the transaction will log the transfer. The common gateway would then pick that information and mint a corresponding token in the other chain.

New to trading? Try crypto trading bots or copy trading

When the smart contract wants to log data for the aforementioned scenarios, it can emit an event (as shown below), which is then written to the transaction receipt’s log records.

Simple Non Fungible Token Contract — Events

Each transaction has only one transaction receipt. In addition to log records, the receipt includes status, gas used, and logs bloom, which we will go over in detail.

Transaction Receipt — Fields

Status

Status is either 0 or 1. It serves as a cue for the caller to determine whether or not the transaction was successful. Because the transaction is asynchronous, the caller has to wait until the block is mined to read the status from transaction receipt.

💁 All events are also reverted when a transaction is reverted (due to running out of gas or condition failures). The caller can only read the status, not any further information as the logged events are reverted as well. The only way to understand what happened is to examine client node traces, which contains detailed information about the execution.

Gas Used

It is the total amount of gas consumed by all previous transactions in the block, including the current one.

Logs

Logs are simply a collection of log records. A single log record includes both topics and data. Topics is a list that includes the signature of the event as well as any indexed fields. A single log record can have up to four topics in it. Topics have a limited storage capacity and not recommended to store large amounts of data. In general, any data that is likely to appear in a log search should be stored in topics as they are indexed. Any other information is stored into the data field.

Transaction Receipt — Log Record Indexing
Transaction Receipt — Log Record Creation

Since the fields event name, from, to are indexed, the following queries can be efficiently retrieved.

  • List all token transfers that occurred today.
  • Return all tokens sold by this user (wallet address) in the last one hour.
  • Get a list of all the tokens purchased by this user in the previous week.

However, because the tokenId is not indexed and is stored in the data field, any query to check “Are there any transfers done on this tokenId today?” will be extremely slow.

Due to the additional computation required for indexing, storing in topics is slightly more expensive than storing in data field. Let’s take a look at how indexing is done in the sections below.

Logs Bloom

Assume we want to

find all tokens sold by a specific user (wallet) in a specific block.

We can parse logs to answer that question because the “from” address is captured. However, in order to determine this, we must conduct a search across all transactions in the block.

Given that the block contains 500 transactions, our search requires us to go through all log records(topics and data) for all transactions. Instead, can we create a field for each transaction that indicates whether or not the information we’re looking for exists in that transaction logs? Bloom Filter.

Before we go any further, let’s look at the bloom filter with a simple list search example. Assume we want to find out if a user exists in a given list. A simple way to find out is to go through the list. However, if a list contains thousands of users, it becomes prohibitively expensive and extremely slow. To improve the search, we can create an efficient index for the list by hashing and mapping each user in the list, as shown below.

Bloom filter construction— simplified

The Bloom filter, as you may have noticed, is created by taking the union of the individual hashes. Assume we want to know if Xin, Kia, and Eva are on the list. We can get the answers without querying the list by using the bloom filter.

Bloom filter search — simplified

The Bloom filter is a probabilistic data structure that can either say “probably present” or “definitely not present”. In this case, the Bloom filter isn’t very useful for determining whether Xin or Kia is on the list. To find out, we must actually query the list. However, it provides a definite answer for Eva that they are not on the list. The Bloom filter is extremely useful, especially when you expect the majority of the answers to be DEFINITELY NOT.

Let’s return to the example of finding tokens sold by a specific user in a block. Assuming that event occurred twice in that specific block of 500 transactions. Instead of parsing all the topics for every transaction, we can query the logs bloom (created from the topics) for presence of the user address and do a topic search only if it matches, otherwise skip to the next transaction.

In addition to the transaction level logs bloom, EVM combines the logs bloom of each transaction and creates a logs bloom in the header. Assume we have to search for the same query (tokens sold by a specific user) but across many blocks. Instead of querying the bloom of every transaction in every block, we can simply query the bloom at the header. If it matches, proceed to query the transaction logs bloom else skip to the next block. Thus, Bloom filters aid in making the search more efficient.

Why Trie structure

Now that we know what a transaction receipt is, we need to understand why it is organised in a trie structure.

Transaction Receipt Trie — Merkle

The purpose of this trie construction is to make the Ethereum protocol light client friendly. Because light clients only store the block headers, they must ask a full node for queries such as “Retrieve all transactions involving a transfer from wallet X in the last n days.”. Because the inherent assumption of blockchain is that no node can be trusted, the light node would require proof from the full node in addition to the results. Organizing the receipts in Merkle tree structures allows for efficient proof distribution over the network. This post goes into great detail on this.

I hope this article has given you a better understanding of transaction receipt trie and event logs in the Ethereum blockchain. I’ll deconstruct the EVM traces in my future post. If you want to be notified when that posts, make sure to follow. Feel free to reach out if you have any questions or comments. Twitter | LinkedIn

--

--

Kirubakumaresh Rajendran
Coinmonks

Building twigblock.com to empower anyone to unlock web3 data. I write about web3 data analytics/science. kikura.eth | linktr.ee/kirubakumaresh