writing, write, fountain pen

Logging in Ethereum – Part 1

Logging and Reporting Events

  1. Logging and Reporting Events
  2. The EVM’s Log Opcodes
  3. Event Topics

If you’re not used to blockchain yet, coding Smart Contracts will feel very different from your run of the mill experience. One of the main reasons is that smart contracts are not interactive, and so don’t offer many techniques to communicate to the outside, be it users or the blockchain. In fact, pretty much the only way is through emitting events and thereby creating logs on chain. If you are curious about what they are and how they work, this series will answer your questions.

Ethereum is like a Log book for your contracts, where you can record all your events

Events are Outputs from Send Transactions

When you call a function from a smart contract, you receive in return the value produced by that function. But when you send a transaction to invoke a contract method, you will only get the transaction’s receipt. If the method would like to communicate anything to the outside world (for example, the identifier of an object it created), it should not return it as an output of the method, because it will be ignored. Instead, methods should send events to publish one or more values.

Events are one of those particularities of the Solidity language that do not have counterparts in traditional settings. They can occupy the same niche of output variables, in that they are the way methods can communicate with the outside world, but they have a lot more structure and cannot be received by other methods (if you’ve never thought that Solidity is strange and quirky, you probably haven’t done enough programming in it).

No, their audience is not other functions, nor do they ever respond to previous invocations. Instead, they are notifications for external agents looking at the blockchain, most likely pieces of code in search for specific information.

Why Should I Use Events?

The most defining property of public blockchains is that whatever happens on the blockchain, is permanent and open for everyone to see. This is why smart contracts are interesting and trustworthy. If a Contract performs an action, that is irrevocable and universally known. But how does this happen?

Ethereum transactions are public, which means anyone reading the blockchain can see the inputs and outputs of a transaction. These are stored in blocks and so are recorded in the history of the blockchain. But changes to the internal state of a contract are not immediately visible: they are held in the memory of each full node.

Events make such changes explicit. Typically a contract emits an event as a result of some action, as a notification. This does not change any internal variable but it does change the state of the blockchain, since events are stored as logs of a transaction. Plus, the transaction includes also an index (a Bloom Filter) to search these logs efficiently.

This is particularly useful for the higher layers of your app, which can then scan all the transactions in the blockchain as they happen, and filter them for interesting events. For example, if you are running a Trading Marketplace based on Ethereum, your MarketData App may scan the blockchain for Bid or Ask events, signalling other traders’ offers.

Transaction Receipts

Events are one of the many things included in a transaction’s receipt. Let’s get a little closer and examine their behaviour. First, I create a basic smart contract like this:

pragma solidity ^0.5.0;

contract Events {

  event Logged(uint valueOfA);
  event AnotherEventType(uint first, uint indexed second);
 
  function testEvent(uint a) public {
   emit Logged(a);
   emit AnotherEventType(100, 25);
   emit AnotherEventType(300, 25);
   emit AnotherEventType(40, 12);
  }

}

I execute the method testEvent(10)and then output the transaction’s receipt. Take a look at my previous series for suggestions on how to do this. It is a long one:

Receipt: { transactionHash:
'0xb9bb01e64aa54824efd931ce071a1077422299c8380010b6ab7306de9058e713',
transactionIndex: 0,
blockHash:
'0xd8bdcb89f0a24c9cee8b3d98916f45b3eddafe0a2c2510b0c2fc3eb26a2dc10f',
blockNumber: 15,
from: '0x3b4b90d5c53140933b22c67630b0e3ad8e3975d5',
to: '0xa5555c200fef816bbeae5b233ea12d4fdf3aebf9',
gasUsed: 27142,
cumulativeGasUsed: 27142,
contractAddress: null,
status: true,
logsBloom:
'0x00000000000020000000000000000000000002000000000000000000000080000000000000000200000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000008020000000000000000000000000000000000000004200000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000010000000000008000000000',
v: '0x1b',
r:
'0x73a92d8bc1ebfd2173453ef0ab1decd5b212a6255ddb1e3e8231ecdea2937ae6',
s:
'0x5d2ff6f5b109311c904ec248c13fc24c37346bbb28d96139c292e4261e15f23c',
events:
{ Logged:
{ logIndex: 0,
transactionIndex: 0,
transactionHash:
'0xb9bb01e64aa54824efd931ce071a1077422299c8380010b6ab7306de9058e713',
blockHash:
'0xd8bdcb89f0a24c9cee8b3d98916f45b3eddafe0a2c2510b0c2fc3eb26a2dc10f',
blockNumber: 15,
address: '0xA5555C200fef816bbeAE5B233ea12d4fDf3aebF9',
type: 'mined',
id: 'log_7130e801',
returnValues: Result { '0': '12', valueOfA: '12' },
event: 'Logged',
signature:
'0x2adf2e2e5f5d116e0d0e1f0231d3f5b75916c3d37d9a16ce060f634cbcc61430',
raw:
{ data:
'0x000000000000000000000000000000000000000000000000000000000000000c',
topics:
[ '0x2adf2e2e5f5d116e0d0e1f0231d3f5b75916c3d37d9a16ce060f634cbcc61430',
[length]: 1 ] } },
AnotherEventType:
[ { logIndex: 1,
transactionIndex: 0,
transactionHash:
'0xb9bb01e64aa54824efd931ce071a1077422299c8380010b6ab7306de9058e713',
blockHash:
'0xd8bdcb89f0a24c9cee8b3d98916f45b3eddafe0a2c2510b0c2fc3eb26a2dc10f',
blockNumber: 15,
address: '0xA5555C200fef816bbeAE5B233ea12d4fDf3aebF9',
type: 'mined',
id: 'log_600472a1',
returnValues: Result { '0': '100', '1': '25', first: '100', second: '25' },
event: 'AnotherEventType',
signature:
'0x4cc30c797f579d227410fd1819a0e688975f04a30fd2e599edb16ae7f5743b60',
raw:
{ data:
'0x0000000000000000000000000000000000000000000000000000000000000064',
topics: [Array] } },
{ logIndex: 2,
transactionIndex: 0,
transactionHash:
'0xb9bb01e64aa54824efd931ce071a1077422299c8380010b6ab7306de9058e713',
blockHash:
'0xd8bdcb89f0a24c9cee8b3d98916f45b3eddafe0a2c2510b0c2fc3eb26a2dc10f',
blockNumber: 15,
address: '0xA5555C200fef816bbeAE5B233ea12d4fDf3aebF9',
type: 'mined',
id: 'log_aaa2067a',
returnValues: Result { '0': '300', '1': '25', first: '300', second: '25' },
event: 'AnotherEventType',
signature:
'0x4cc30c797f579d227410fd1819a0e688975f04a30fd2e599edb16ae7f5743b60',
raw:
{ data:
'0x000000000000000000000000000000000000000000000000000000000000012c',
topics: [Array] } },
{ logIndex: 3,
transactionIndex: 0,
transactionHash:
'0xb9bb01e64aa54824efd931ce071a1077422299c8380010b6ab7306de9058e713',
blockHash:
'0xd8bdcb89f0a24c9cee8b3d98916f45b3eddafe0a2c2510b0c2fc3eb26a2dc10f',
blockNumber: 15,
address: '0xA5555C200fef816bbeAE5B233ea12d4fDf3aebF9',
type: 'mined',
id: 'log_031c1ff6',
returnValues: Result { '0': '40', '1': '12', first: '40', second: '12' },
event: 'AnotherEventType',
signature:
'0x4cc30c797f579d227410fd1819a0e688975f04a30fd2e599edb16ae7f5743b60',
raw:
{ data:
'0x0000000000000000000000000000000000000000000000000000000000000028',
topics: [Array] } },
[length]: 3 ] } }

There is a lot here, and most of it is precisely to do with events. We don’t need to carry around such a cluttered list of data, and would do well instead to reason on an easier level of abstraction:

  • The receipt has a member events with all the events emitted by the transaction
  • events is a dictionary where each member represents a specific event type 
  • This member can be either a single event or an array of events (if several instances have been emitted)

In the example above we have one instance of event type Logged and 3 instances of event type AnotherEventType. We can access, say, the second of these by writing

console.log('Event: %o', (receipt.events.AnotherEventType[1]));

which returns just

Event: { logIndex: 2,
transactionIndex: 0,
transactionHash:
'0xdc3d26df131fde366a54926012a56f98e41ad15b5e03b8f808a2050eac81c5a1',
blockHash:
'0xef196116c7680f1ae77b80e2d01b8ab2d1759d8ea8523c9c80539551a4512aee',
blockNumber: 65,
address: '0xD3642cCd33b99AB2370968854F000121C6e942F5',
type: 'mined',
id: 'log_99934ed2',
returnValues: Result { '0': '300', '1': '25', first: '300', second: '25' },
event: 'AnotherEventType',
signature:
'0x4cc30c797f579d227410fd1819a0e688975f04a30fd2e599edb16ae7f5743b60',
raw:
{ data:
'0x000000000000000000000000000000000000000000000000000000000000012c',
topics:
[ '0x4cc30c797f579d227410fd1819a0e688975f04a30fd2e599edb16ae7f5743b60',
'0x0000000000000000000000000000000000000000000000000000000000000019',
[length]: 2 ] } }

I’ll let you explore the data returned in an event on your own. If all you need is a light and intuitive understanding of events, you could just stop here and explore on your own. But if you’re curious like me, come with me a level deeper in my next post, and see how everything works under the hood.

Leave a Reply