Skip to main content

How to Consume Execution Events in Rust: A Basic Example

This guide walks through building a minimal Rust application that reads execution events from a live Monad node. By the end of this guide, you'll have a working event consumer that prints each EVM action as it happens.

Prerequisites

This guide assumes you have:

  • A running Monad node with execution events enabled (setup guide)

Project Setup

Here is the repo with the code.

Create a new Rust project:

cargo new --bin exec-events-demo
cd exec-events-demo

Replace Cargo.toml with:

[package]
name = "exec-events-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
monad-exec-events = { git = "https://github.com/category-labs/monad-bft", tag = "release/exec-events-sdk-v1.0" }
monad-event-ring = { git = "https://github.com/category-labs/monad-bft", tag = "release/exec-events-sdk-v1.0" }

The Code

Replace src/main.rs with:

main.rssrc
use std::time::Duration;
use monad_event_ring::{DecodedEventRing, EventNextResult, EventPayloadResult, EventRingPath};
use monad_exec_events::{ExecEvent, ExecEventReaderExt, ExecEventRing, ExecEventType};
/// Default path for the execution event ring
const EVENT_RING_PATH: &str =
"/var/lib/hugetlbfs/user/monad/pagesize-2MB/event-rings/monad-exec-events";
fn main() {
// Resolve the event ring path (full path bypasses hugetlbfs lookup)
let event_ring_path =
EventRingPath::resolve(EVENT_RING_PATH).expect("Failed to resolve path");
// Open the live event ring
let ring = ExecEventRing::new(&event_ring_path).expect("Failed to open event ring");
println!("Connected to event ring");
println!("Waiting for events...\n");
// Create a reader and rewind to the start of the current block
let mut reader = ring.create_reader();
reader.consensus_prev(Some(ExecEventType::BlockStart));
loop {
match reader.next_descriptor() {
EventNextResult::Ready(event) => {
let seqno = event.info().seqno;
// Try to read the event payload
match event.try_read() {
EventPayloadResult::Ready(exec_event) => {
print_event(&exec_event, seqno);
}
EventPayloadResult::Expired => {
eprintln!("[{}] Payload expired!", seqno);
reader.reset();
}
}
}
EventNextResult::Gap => {
eprintln!("Warning: event gap occurred (reader too slow)");
reader.reset();
}
EventNextResult::NotReady => {
// No events available, wait briefly
std::thread::sleep(Duration::from_millis(10));
}
}
}
}
fn print_event(event: &ExecEvent, seqno: u64) {
match event {
ExecEvent::BlockStart(block) => {
println!(
"[{}] BLOCK_START: number={}",
seqno, block.block_tag.block_number
);
}
ExecEvent::BlockEnd(_) => {
println!("[{}] BLOCK_END", seqno);
}
ExecEvent::TxnHeaderStart { txn_index, .. } => {
println!("[{}] TXN_START: index={}", seqno, txn_index);
}
ExecEvent::TxnEvmOutput { txn_index, output } => {
println!(
"[{}] TXN_OUTPUT: index={} gas_used={}",
seqno, txn_index, output.receipt.gas_used
);
}
ExecEvent::TxnLog { txn_index, txn_log, .. } => {
println!(
"[{}] LOG: txn={} topics={}",
seqno, txn_index, txn_log.topic_count
);
}
ExecEvent::TxnEnd => {
println!("[{}] TXN_END", seqno);
}
// Silently ignore other events (call frames, storage access, etc.)
_ => {}
}
}

Build and Run

cargo build --release
./target/release/exec-events-demo

Expected Output

You'll see a stream of events as your node processes transactions:

Connected to event ring
Waiting for events...
[508183802] BLOCK_START: number=45199825
[508183811] TXN_START: index=0
[508183822] TXN_OUTPUT: index=0 gas_used=0
[508183823] LOG: txn=0 topics=3
[508183838] TXN_END
[508183942] BLOCK_END

Key Concepts

Event Ring

The event ring is a shared memory buffer where the execution daemon writes events. Multiple readers can consume from it simultaneously, each maintaining their own position.

EventNextResult

  • Ready: An event is available to process
  • NotReady: No new events yet (daemon hasn't written any)
  • Gap: Reader fell behind and events were overwritten - call reset() to recover

Event Types

Common events you'll see:

  • BlockStart / BlockEnd - Block boundaries
  • TxnHeaderStart / TxnEnd - Transaction boundaries
  • TxnEvmOutput - Transaction execution result (contains receipt with gas_used)
  • TxnLog - EVM log emission (Solidity events)
  • AccountAccess - Account touched during execution
  • StorageAccess - Contract storage read/write

Next Steps