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-democd exec-events-demoReplace 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.rs
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 ringconst 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-demoExpected Output
You'll see a stream of events as your node processes transactions:
Connected to event ringWaiting 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_ENDKey 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 boundariesTxnHeaderStart/TxnEnd- Transaction boundariesTxnEvmOutput- Transaction execution result (contains receipt with gas_used)TxnLog- EVM log emission (Solidity events)AccountAccess- Account touched during executionStorageAccess- Contract storage read/write
Next Steps
- See the full eventwatch.rs example for production patterns (timestamps, timeout detection, CLI args)
- Read the Rust API docs for detailed type information
- Explore the event ring internals to understand the shared memory protocol