Die Surfnet-Laufzeit gibt einen Stream strukturierter Ereignisse aus, der die Transaktionsverarbeitung, slot-Ticks, Konten-Streaming, Zeitreisen und Fehler abdeckt. Das SDK stellt diesen Stream bereit, damit Tests das Laufzeitverhalten prüfen können, ohne Logs analysieren zu müssen.
Rust: Channel-Empfänger
Surfnet::events() gibt eine Referenz auf den
crossbeam_channel::Receiver<SimnetEvent> zurück, in den die Laufzeit
veröffentlicht. Leeren Sie ihn mit try_iter() (nicht-blockierend) oder lesen
Sie einzelne Ereignisse mit recv() / recv_timeout().
use surfpool_sdk::{SimnetEvent, Surfnet};let surfnet = Surfnet::start().await?;// Drain whatever has accumulated so far.for event in surfnet.events().try_iter() {match event {SimnetEvent::TransactionProcessed(_ts, metadata, error) => {println!("tx {} -> {:?}", metadata.signature, metadata.logs);if let Some(err) = error {eprintln!(" failed: {err:?}");}}SimnetEvent::ErrorLog(_ts, message) => {eprintln!("runtime error: {message}");}other => {println!("event: {other:?}");}}}
Empfänger wird geteilt
Wenn Sie einen Hintergrund-Task zum Konsumieren von Ereignissen starten, rufen
Sie try_iter nicht zusätzlich vom Haupt-Thread aus auf. Ereignisse werden
unvorhersehbar auf beide Konsumenten aufgeteilt.
JS: Drain-Methode
Die JS-Bindings puffern Ereignisse intern und stellen sie über drainEvents
bereit. Jeder Aufruf gibt alles zurück, was seit dem letzten Aufruf angesammelt
wurde, und leert anschließend den Puffer.
import { Surfnet } from "@solana/surfpool";const surfnet = Surfnet.start();try {// ... do test setup that triggers events ...const events = surfnet.drainEvents();for (const event of events) {switch (event.kind) {case "transactionProcessed":console.log("tx", event.transactionSignature, event.logs);break;case "errorLog":console.error("runtime error:", event.message);break;default:console.log(event.kind, event.message ?? "");}}} finally {surfnet.stop();}
Unbegrenzter Puffer
Wenn Sie drainEvents nie aufrufen, wächst der Puffer für die gesamte
Lebensdauer der Instanz. Das ist für kurze Tests in Ordnung, rufen Sie es
jedoch periodisch bei lang laufenden Fixtures auf, damit der Speicher nicht
unkontrolliert wächst.
SimnetEventValue-Struktur (JS)
Der JS-Ereignistyp ist ein flaches Objekt mit einem kind-Diskriminator und
optionalen Feldern. Nicht jedes Feld ist bei jeder Variante gesetzt —
destrukturieren Sie basierend auf kind.
interface SimnetEventValue {kind: string; // discriminator, e.g. "TransactionProcessed"message?: string; // human-readable messagetimestamp?: string; // ISO timestamp// startupinitialTransactionCount?: number;clock?: ClockValue;// slot / clockepochInfo?: EpochInfoValue;clockCommand?: string;slotIntervalMs?: number;// accountsaccountPubkey?: string;// transactionstransactionSignature?: string;logs?: string[];computeUnitsConsumed?: number;fee?: number;// errorserrorMessage?: string;// misctag?: string;profileKey?: string;profileSlot?: number;runbookId?: string;runbookErrors?: string[];}
Ereignisarten
Kind-Strings auf der JS-Seite verwenden camelCase; die entsprechende Rust-Variante verwendet PascalCase. Gleichen Sie auf genau diese Strings ab — sie sind Groß-/Kleinschreibung-sensitiv und über Versionen hinweg stabil.
JS kind | Rust-Variante | Ausgelöst wenn |
|---|---|---|
ready | SimnetEvent::Ready | Die Laufzeit beendet den Startvorgang; enthält initialTransactionCount. |
connected | SimnetEvent::Connected | Verbindung zum vorgelagerten Remote-RPC hergestellt; enthält message (die URL). |
aborted | SimnetEvent::Aborted | Der Start wurde abgebrochen; enthält message (den Grund). |
shutdown | SimnetEvent::Shutdown | Ein geordnetes Herunterfahren beginnt. |
systemClockUpdated | SimnetEvent::SystemClockUpdated | Die Systemuhr ist vorgerückt; enthält clock. |
clockUpdate | SimnetEvent::ClockUpdate | Ein Uhr-Befehl wurde ausgeführt (Pause/Fortsetzen/Intervall aktualisieren); enthält clockCommand und optional slotIntervalMs. |
epochInfoUpdate | SimnetEvent::EpochInfoUpdate | Ein neuer epoch-Info-Snapshot ist verfügbar; enthält epochInfo. |
blockHashExpired | SimnetEvent::BlockHashExpired | Ein verwendeter Blockhash ist abgelaufen. |
transactionReceived | SimnetEvent::TransactionReceived | Eine Transaktion wurde in die Warteschlange eingereiht; enthält timestamp, transactionSignature. |
transactionProcessed | SimnetEvent::TransactionProcessed | Eine Transaktion wurde ausgeführt; enthält timestamp, transactionSignature, logs, computeUnitsConsumed, fee und errorMessage bei Fehler. |
accountUpdate | SimnetEvent::AccountUpdate | Ein Konto hat sich geändert — umfasst gestreamte Konten und durch Cheatcode ausgelöste Mutationen; enthält timestamp und accountPubkey. |
infoLog / warnLog / errorLog / debugLog | SimnetEvent::InfoLog / WarnLog / ErrorLog / DebugLog | Die Laufzeit hat eine Log-Zeile ausgegeben; enthält timestamp und message. |
pluginLoaded | SimnetEvent::PluginLoaded | Ein Geyser-Plugin wurde geladen; enthält message (den Plugin-Namen). |
taggedProfile | SimnetEvent::TaggedProfile | Ein Profiler-Ergebnis wurde markiert; enthält tag, profileKey, profileSlot, logs, computeUnitsConsumed. |
runbookStarted / runbookCompleted | SimnetEvent::RunbookStarted / RunbookCompleted | Ein Runbook wurde gestartet oder abgeschlossen; enthält runbookId und (bei Abschluss) optionales runbookErrors. |
Assertion-Muster
Bestätigen, dass eine Transaktion gelandet ist
import { Surfnet } from "@solana/surfpool";const surfnet = Surfnet.start();try {const sig = await sendTransaction(surfnet.rpcUrl /* ... */);// ... wait for confirmation via RPC ...const landed = surfnet.drainEvents().some((e) => e.kind === "transactionProcessed" && e.transactionSignature === sig);console.assert(landed, "expected transaction to land");} finally {surfnet.stop();}
Bestätigen, dass keine Laufzeitfehler aufgetreten sind
use surfpool_sdk::{SimnetEvent, Surfnet};let mut surfnet = Surfnet::start().await?;// ... run the test ...let errors: Vec<_> = surfnet.events().try_iter().filter(|e| matches!(e, SimnetEvent::ErrorLog(_, _))).collect();assert!(errors.is_empty(), "runtime errors: {errors:?}");surfnet.stop()?;
Warum Events dem Log-Parsing überlegen sind
Den Event-Stream gegenüber stdout bevorzugen
Die Laufzeitumgebung gibt strukturierte Events über tracing aus, was verführerisch sein kann, diese von stdout zu erfassen und zu parsen. Tu das nicht. Der strukturierte Event-Stream:
- Übersteht Log-Level-Änderungen — Events werden unabhängig von
RUST_LOGausgegeben. - Liefert typisierte Payloads — kein String-Parsing, kein Formatwechsel zwischen Versionen.
- Hat stabile Arten — Log-Zeilen sind für Menschen formatiert und ändern sich frei; Event-Arten sind Teil des SDK-Vertrags.
Is this page helpful?