Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 127 additions & 80 deletions solutions/LP-0012.md
Original file line number Diff line number Diff line change
@@ -1,120 +1,167 @@
# Solution: LP-0012 — Structured Event System for LEZ Programs
# Solution: LP-0012 — Structured Event/Log Mechanism for LEZ Programs

**Submitted by:** bristinWild
**Submitted by:** pramadanif

## Summary

This submission implements a complete structured event system for the Logos Execution Zone (LEZ). Programs can emit typed, Borsh-encoded events during execution via `emit_event()`. Events are preserved in transaction receipts on both success and failure paths, retrievable by clients via a new `getTransactionReceipt` RPC method. A CLI decoder renders events in human-readable form for debugging and indexing.
The **LEZ Event System** solves a critical problem in the RISC0 zkVM environment: when a transaction panics, developers have no visibility into the failure reason because the journal is discarded. This project introduces a **minimally-invasive event transport layer for LEZ execution journals**.

Rather than requiring core rewrites to `ProgramOutput` or `TxReceipt` structs upstream, this architecture introduces a deterministic, framed event stream (`LEZE`) prepended to the standard execution journal. A transparent runtime adapter (`execute_program`) automatically manages event aggregation and flushing, ensuring events survive transaction panics without bleeding transport logic into the developer's SDK experience.

## Repository

- **Repo:** https://github.com/bristinWild/logos-execution-zone
- **Commit:** `6dca25ba` (branch: `main`)
- **Key files:**
- `lez-events/src/lib.rs` — guest SDK (`emit_event`, `EventRecord`, `drain_events`)
- `nssa/core/src/program.rs` — `ProgramOutput.events`, `write_nssa_outputs_on_failure`
- `nssa/src/program.rs` — host-side event extraction, `ExitCode` handling
- `nssa/src/error.rs` — `NssaError::ProgramExecutionFailed` with `partial_output`
- `sequencer/core/src/block_store.rs` — `RejectedTx`, `RejectedTxStore`
- `sequencer/core/src/lib.rs` — event preservation on tx failure
- `sequencer/service/rpc/src/lib.rs` — `TxReceipt`, `TxStatus`, `getTransactionReceipt`
- `sequencer/service/src/service.rs` — RPC implementation
- `lez-events-decoder/src/main.rs` — CLI decoder tool
- `examples/emit_event_demo/` — success + failure path example programs
- `docs/event-format.md` — schema specification
- **Repo:** https://github.com/pramadanif/lez-event-system
- **Commit:** `a551b2e177bcdad0fcdba93ba1aa63584fef063b` (branch: `main`, tag: `v0.1.0`)
- **License:** MIT
- **Version:** 0.1.0
- **Interactive decoder:** https://lez-event-system.vercel.app/
- **Video (overview):** https://youtu.be/jPAzqcxP_go
- **Video (E2E demo walkthrough):** https://youtu.be/qmyBhWTii6Y

### Key deliverables

| Path | Role |
|------|------|
| `lez-events/` | Core SDK — `emit_event`, `drain_events`, `EventRecord`, `impl_lez_event!`, size limits |
| `lez-events-runtime/` | `execute_program` panic-catching wrapper + `parse_journal` host parser |
| `lez-event-decoder/` | Borsh decoder + `lez-event-cli` (`decode`, `decode-raw`, `watch`) |
| `examples/token-transfer/` | Success-path demo binary |
| `examples/withdraw/` | Failure-path demo binary |
| `examples/indexer/` | Reference indexer demo binary |
| `tests/integration.rs` | E2E SDK → encode → decode pipeline (6 tests) |
| `docs/event-format.md` | Wire format / schema (~500 lines) |
| `docs/architecture-decision.md` | Design rationale + sequencer integration |
| `docs/runtime-integration.md` | Sequencer-side `parse_journal` glue |
| `docs/gap-analysis.md` | Honest audit of live-testnet gaps |
| `docs/benchmarks.md` | Storage / overhead analysis (`RISC0_DEV_MODE=0`) |
| `docs/research-notes.md` | Pre-implementation LEZ research |
| `docs/deployments.md` | Testnet deployment guide / status |
| `docs/submission-writeup.md` | Full bounty write-up |
| `scripts/demo.sh` | Reproducible E2E demo (`RISC0_DEV_MODE=0`) |
| `demo/index.html` | Serverless web decoder UI |
| `.github/workflows/ci.yml` | CI with `RISC0_DEV_MODE=0` (green on `main`) |

## Approach

### Event Model

Each emitted event is an `EventRecord` containing a caller-defined `discriminant` (u32), a monotonically increasing `sequence` number for ordering, and a Borsh-encoded `payload`:
```rust
pub struct EventRecord {
pub discriminant: u32, // program-defined event type
pub sequence: u32, // emission order within transaction
pub payload: Vec<u8>, // Borsh-encoded event data
}
```
### Architecture: adapter-driven panic capture

Borsh was chosen per the team's own direction (issues #131, #260) for determinism and compactness. `program_id` attribution is injected by the sequencer host at receipt time (programs cannot read their own ID — issue #347).
1. Program calls `emit_event` → thread-local buffer
2. Logic wrapped in `execute_program(|| { ... })`
3. On panic: adapter catches abort → `drain_events` → serializes `LEZE` framed envelope → commits to RISC0 journal → resumes panic
4. State reverts; events remain in journal
5. Sequencer slices off `LEZE` frame via `parse_journal` before normal `ProgramOutput` parsing

### Guest SDK
### Why this design

Programs call `emit_event(discriminant, &payload)` from the `lez-events` crate. Events are buffered in a thread-local and flushed into `ProgramOutput.events` when `write_nssa_outputs()` is called.
- Compatible with current LEZ journal semantics
- Minimal, non-invasive sequencer glue (no structural rewrite of core tx primitives)
- Developer only calls `emit_event` + wraps body in `execute_program`

### Failure-Path Persistence
### Why Logos / LEZ fits

For failure cases, programs call `write_nssa_outputs_on_failure()` before `panic!()`. This commits `(FAILURE_SENTINEL, Vec<EventRecord>)` to the Risc0 journal using a sentinel value (`0xDEAD_FA11`) so the host can distinguish it from a success `ProgramOutput`. The sequencer detects the non-zero exit code, extracts events from the partial journal, and stores them in `RejectedTxStore`.
LEZ’s Risc0 zkVM journal + public/private execution split make structured, privacy-aware events a natural client interface for debugging, wallets, and indexers. Events are treated as **public** data; docs call out what is safe vs unsafe to emit under private execution (never viewing keys, hidden balances, nullifiers).

**Dev-mode note:** In `RISC0_DEV_MODE=1`, the Risc0 executor does not expose the journal after a guest panic. Failure-path recovery works in production ZK mode.
### Encoding & limits

### Sequencer Integration
- **Encoding:** Borsh 1.5.0 (deterministic, LEZ-aligned)
- **`EventRecord` fields:** `program_id`, `sequence`, `discriminant`, `schema_version`, `schema_hash`, `payload`
- **Max payload / event:** 1,024 bytes
- **Max events / tx:** 64
- **Max total bytes / tx:** 65,536
- **Errors:** `PayloadTooLarge` (`0xEE01`), `TooManyEvents` (`0xEE02`), `TotalSizeTooLarge` (`0xEE03`), `EncodingFailed` (`0xEE04`) — never panic, never silent truncate

- `RejectedTxStore`: in-memory store keyed by tx hash, preserving error message + events
- `getTransactionReceipt` RPC: returns `TxStatus` (Included/Rejected/Pending/Unknown) + events
- Events from included transactions come from `ProgramOutput.events` in the block
- Events from rejected transactions come from `RejectedTxStore`

### Decoder CLI
```bash
lez-events-decoder --receipt receipt.json
```
### Architectural honesty

Outputs human-readable transaction receipt with event discriminants, sequence numbers, and hex/UTF-8 decoded payloads.
Compatible with existing LEZ journal flow. Requires minimal sequencer adapter (`parse_journal`). See `docs/architecture-decision.md`, `docs/runtime-integration.md`, and `docs/gap-analysis.md` for the exact glue and live-testnet gaps (including pending real CU metering on sequencer).

## Success Criteria Checklist

- [x] **Event API for programs** — `emit_event(discriminant, &MyEvent { ... })` in `lez-events` crate, usable from any LEZ guest program. See `examples/emit_event_demo/methods/guest/src/bin/withdraw.rs`.
- [x] **Machine-readable format** — Borsh encoding, stable field ordering, sequence numbers for ordering, `program_id` injected by sequencer host, documented schema in `docs/event-format.md`.
- [x] **Human-friendly rendering** — `lez-events-decoder` CLI renders receipts as structured text with discriminants, sequence numbers, and decoded payloads.
- [x] **Available post-execution** — `getTransactionReceipt` RPC method returns events for both included and rejected transactions.
- [x] **Emittable on failure** — `write_nssa_outputs_on_failure()` + `FAILURE_SENTINEL` pattern preserves events before panic. Works in production ZK mode; dev-mode limitation documented.
- [x] **Testing** — 167 tests passing across all crates:
- Deterministic encoding/decoding: `lez-events` tests
- Event ordering: sequence counter tests
- Failure-path persistence: `emit_event_demo::failure_path_emits_insufficient_funds_event`
- Full nssa suite: 118 tests including all existing program execution tests
Mirror of README **Requirements Fulfillment** for LP-0012:

- [x] **Event format specification** — `docs/event-format.md` (~500 lines)
- [x] **Borsh encoding/decoding** — `lez-event-decoder/`, tests verify correctness
- [x] **SDK for LEZ programs** — `lez-events/` + examples
- [x] **Event persistence through panic** — `examples/withdraw/` + `test_failure_path` + integration tests
- [x] **CLI decoder tool** — `lez-event-cli` (`decode` / `decode-raw` / `watch`)
- [x] **Unit tests** — 21 tests in `lez-events/tests/`
- [x] **Example programs** — 3 demos; flows covered by unit + integration suites
- [x] **Integration tests** — `tests/integration.rs` (6 tests)
- [x] **Runtime parser tests** — 3 tests in `lez-events-runtime`
- [x] **RISC0_DEV_MODE=0** — demo, tests, and CI enforce real proving
- [x] **Clippy compliance** — zero warnings
- [x] **Code formatting** — `cargo fmt --check --all` clean
- [x] **Documentation** — 8 files under `docs/`

### Test coverage (30 / 30)

| Suite | Count |
|-------|-------|
| `lez-events` unit (`tests/`) | 21 |
| `lez-events-runtime` | 3 |
| Integration | 6 |
| **Total** | **30** |

## FURPS Self-Assessment

### Functionality
- Programs emit typed events on success and failure paths
- Events retrievable via `getTransactionReceipt` RPC
- Decoder CLI renders events in human-readable form
- `program_id` attribution via sequencer host injection
- Privacy: public execution emits by default; private execution requires explicit opt-in (documented)
- Limitation: failure-path event recovery requires production ZK mode (not `RISC0_DEV_MODE`)

Typed `emit_event` API, Borsh `EventRecord` (incl. `schema_hash`), `LEZE` framed journal transport, panic-path preservation via `execute_program`, CLI + web decoder, reference indexer. Sequencer adoption is intentionally minimal (`parse_journal`).

### Usability
- Single function call: `emit_event(discriminant, &event_struct)`
- Pattern for failure path: `emit_event(...); write_nssa_outputs_on_failure(); panic!(...)`
- CLI decoder: `lez-events-decoder --receipt file.json`
- Full schema documented in `docs/event-format.md`

```toml
lez-events = { git = "https://github.com/pramadanif/lez-event-system", tag = "v0.1.0" }
```

Define events with `impl_lez_event!`; wrap program in `execute_program`. Offline `decode-raw` needs no sequencer. Live decoder: https://lez-event-system.vercel.app/

### Reliability
- Events in `ProgramOutput.events` are committed atomically with program output — consistent with existing tx semantics
- `RejectedTxStore` is in-memory (cleared on restart) — sufficient for testnet; persistent storage is a natural next step
- 167 tests passing; all 118 existing nssa tests continue to pass

Events flushed to journal before panic resume. Size limits return stable `EventError` codes. `RISC0_DEV_MODE=0` enforced. Gap analysis documents live-sequencer dependencies. `program_id` must be overwritten by sequencer (documented).

### Performance
- Borsh encoding is zero-copy and allocation-minimal
- Thread-local event buffer adds negligible overhead to guest execution
- `RejectedTxStore` uses a `HashMap` — O(1) lookup per tx hash

| Payload | Serialization time |
|---------|-------------------|
| 64 B | ~800 ns |
| 256 B | ~1.2 µs |
| 1,024 B (max) | ~2.5 µs |

Drain cost: ~50 ns (1 event) … ~300 ns (64 events). Real on-chain CU metering pending live sequencer — see `docs/benchmarks.md`.

### Supportability
- New crates (`lez-events`, `lez-events-decoder`) are self-contained and independently testable
- `docs/event-format.md` documents the full schema, encoding, privacy considerations, and size guidelines
- `artifacts/program_methods/` update process documented (critical for future schema changes)
- Build scripts updated with `rerun-if-changed` for `nssa_core` and `lez-events`

30 passing tests, CI green on `main`, clippy-clean, MIT, `scripts/demo.sh`, narrated videos, Rust 1.94.0 toolchain, full docs set including research notes + deployments.

## How evaluators verify

```bash
git clone https://github.com/pramadanif/lez-event-system
cd lez-event-system
git checkout v0.1.0
export RISC0_DEV_MODE=0
./scripts/demo.sh
cargo test --workspace
cargo clippy --workspace --tests -- -D warnings
cargo fmt --check --all
```

Demo must show `RISC0_DEV_MODE=0`, success-path events committed, and failure-path events committed **before** panic.

## Supporting Materials

- **Event format spec:** [`docs/event-format.md`](https://github.com/bristinWild/logos-execution-zone/blob/main/docs/event-format.md)
- **Example program (success + failure):** [`examples/emit_event_demo/`](https://github.com/bristinWild/logos-execution-zone/tree/main/examples/emit_event_demo)
- **Guest SDK:** [`lez-events/src/lib.rs`](https://github.com/bristinWild/logos-execution-zone/blob/main/lez-events/src/lib.rs)
- **Decoder CLI:** [`lez-events-decoder/src/main.rs`](https://github.com/bristinWild/logos-execution-zone/blob/main/lez-events-decoder/src/main.rs)
- **RPC interface:** [`sequencer/service/rpc/src/lib.rs`](https://github.com/bristinWild/logos-execution-zone/blob/main/sequencer/service/rpc/src/lib.rs)
- **Related issues addressed:** #170 (Risc0 error handling), #379 (TX visibility), #347 (program_id), #131/#260 (Borsh encoding)
- **README (source of truth):** https://github.com/pramadanif/lez-event-system/blob/main/README.md
- **Event format:** https://github.com/pramadanif/lez-event-system/blob/main/docs/event-format.md
- **Architecture ADR:** https://github.com/pramadanif/lez-event-system/blob/main/docs/architecture-decision.md
- **Runtime integration:** https://github.com/pramadanif/lez-event-system/blob/main/docs/runtime-integration.md
- **Gap analysis:** https://github.com/pramadanif/lez-event-system/blob/main/docs/gap-analysis.md
- **Benchmarks:** https://github.com/pramadanif/lez-event-system/blob/main/docs/benchmarks.md
- **Research notes:** https://github.com/pramadanif/lez-event-system/blob/main/docs/research-notes.md
- **Deployments:** https://github.com/pramadanif/lez-event-system/blob/main/docs/deployments.md
- **Submission write-up:** https://github.com/pramadanif/lez-event-system/blob/main/docs/submission-writeup.md
- **Demo script:** https://github.com/pramadanif/lez-event-system/blob/main/scripts/demo.sh
- **Videos:** https://youtu.be/jPAzqcxP_go · https://youtu.be/qmyBhWTii6Y
- **Live decoder:** https://lez-event-system.vercel.app/

## Terms & Conditions

Expand Down
Loading