Laufzeit-Ereignisse

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 message
timestamp?: string; // ISO timestamp
// startup
initialTransactionCount?: number;
clock?: ClockValue;
// slot / clock
epochInfo?: EpochInfoValue;
clockCommand?: string;
slotIntervalMs?: number;
// accounts
accountPubkey?: string;
// transactions
transactionSignature?: string;
logs?: string[];
computeUnitsConsumed?: number;
fee?: number;
// errors
errorMessage?: string;
// misc
tag?: 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 kindRust-VarianteAusgelöst wenn
readySimnetEvent::ReadyDie Laufzeit beendet den Startvorgang; enthält initialTransactionCount.
connectedSimnetEvent::ConnectedVerbindung zum vorgelagerten Remote-RPC hergestellt; enthält message (die URL).
abortedSimnetEvent::AbortedDer Start wurde abgebrochen; enthält message (den Grund).
shutdownSimnetEvent::ShutdownEin geordnetes Herunterfahren beginnt.
systemClockUpdatedSimnetEvent::SystemClockUpdatedDie Systemuhr ist vorgerückt; enthält clock.
clockUpdateSimnetEvent::ClockUpdateEin Uhr-Befehl wurde ausgeführt (Pause/Fortsetzen/Intervall aktualisieren); enthält clockCommand und optional slotIntervalMs.
epochInfoUpdateSimnetEvent::EpochInfoUpdateEin neuer epoch-Info-Snapshot ist verfügbar; enthält epochInfo.
blockHashExpiredSimnetEvent::BlockHashExpiredEin verwendeter Blockhash ist abgelaufen.
transactionReceivedSimnetEvent::TransactionReceivedEine Transaktion wurde in die Warteschlange eingereiht; enthält timestamp, transactionSignature.
transactionProcessedSimnetEvent::TransactionProcessedEine Transaktion wurde ausgeführt; enthält timestamp, transactionSignature, logs, computeUnitsConsumed, fee und errorMessage bei Fehler.
accountUpdateSimnetEvent::AccountUpdateEin Konto hat sich geändert — umfasst gestreamte Konten und durch Cheatcode ausgelöste Mutationen; enthält timestamp und accountPubkey.
infoLog / warnLog / errorLog / debugLogSimnetEvent::InfoLog / WarnLog / ErrorLog / DebugLogDie Laufzeit hat eine Log-Zeile ausgegeben; enthält timestamp und message.
pluginLoadedSimnetEvent::PluginLoadedEin Geyser-Plugin wurde geladen; enthält message (den Plugin-Namen).
taggedProfileSimnetEvent::TaggedProfileEin Profiler-Ergebnis wurde markiert; enthält tag, profileKey, profileSlot, logs, computeUnitsConsumed.
runbookStarted / runbookCompletedSimnetEvent::RunbookStarted / RunbookCompletedEin 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_LOG ausgegeben.
  • 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?

Inhaltsverzeichnis

Seite bearbeiten
© 2026 Solana Foundation. Alle Rechte vorbehalten.