Eventi di Runtime

Il runtime Surfnet emette un flusso di eventi strutturati che riguardano l'elaborazione delle transazioni, i tick di slot, lo streaming di account, il viaggio nel tempo e gli errori. L'SDK espone questo flusso in modo che i test possano verificare il comportamento del runtime senza dover analizzare i log.

Rust: Channel Receiver

Surfnet::events() restituisce un riferimento al crossbeam_channel::Receiver<SimnetEvent> su cui il runtime pubblica. Svuotalo con try_iter() (non bloccante) oppure leggi i singoli eventi con 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:?}");
}
}
}

Il receiver è condiviso

Se avvii un task in background per consumare gli eventi, non chiamare anche try_iter dal thread principale. Gli eventi verranno suddivisi in modo imprevedibile tra i due consumer.

JS: Metodo Drain

I binding JS bufferizzano gli eventi internamente e li espongono tramite drainEvents. Ogni chiamata restituisce tutto ciò che si è accumulato dalla chiamata precedente, quindi svuota il buffer.

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();
}

Buffer illimitato

Se non chiami mai drainEvents, il buffer cresce per tutta la durata dell'istanza. Va bene per test brevi, ma chiamalo periodicamente per fixture a lunga esecuzione in modo che la memoria non cresca senza controllo.

Struttura di SimnetEventValue (JS)

Il tipo di evento JS è un oggetto piatto con un discriminatore kind e campi opzionali. Non tutti i campi sono impostati in ogni variante — destruttura in base a 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[];
}

Tipi di Evento

Le stringhe dei tipi sul lato JS sono in camelCase; la variante Rust corrispondente usa il PascalCase. Fai il match su queste stringhe esatte — sono case-sensitive e stabili tra le versioni.

JS kindVariante RustEmesso quando
readySimnetEvent::ReadyIl runtime termina l'avvio; contiene initialTransactionCount.
connectedSimnetEvent::ConnectedConnesso all'RPC remoto upstream; contiene message (l'URL).
abortedSimnetEvent::AbortedL'avvio è stato interrotto; contiene message (il motivo).
shutdownSimnetEvent::ShutdownInizia uno spegnimento controllato.
systemClockUpdatedSimnetEvent::SystemClockUpdatedL'orologio di sistema è avanzato; contiene clock.
clockUpdateSimnetEvent::ClockUpdateÈ stato eseguito un comando sull'orologio (pausa/ripresa/aggiornamento intervallo); contiene clockCommand e opzionalmente slotIntervalMs.
epochInfoUpdateSimnetEvent::EpochInfoUpdateÈ disponibile un nuovo snapshot delle informazioni sull'epoch; contiene epochInfo.
blockHashExpiredSimnetEvent::BlockHashExpiredUn blockhash che era in uso è scaduto.
transactionReceivedSimnetEvent::TransactionReceivedUna transazione è stata accodata; contiene timestamp, transactionSignature.
transactionProcessedSimnetEvent::TransactionProcessedUna transazione è stata eseguita; contiene timestamp, transactionSignature, logs, computeUnitsConsumed, fee e errorMessage in caso di errore.
accountUpdateSimnetEvent::AccountUpdateUn account è cambiato — include account in streaming e mutazioni tramite cheatcode; contiene timestamp e accountPubkey.
infoLog / warnLog / errorLog / debugLogSimnetEvent::InfoLog / WarnLog / ErrorLog / DebugLogIl runtime ha emesso una riga di log; contiene timestamp e message.
pluginLoadedSimnetEvent::PluginLoadedUn plugin Geyser è stato caricato; contiene message (il nome del plugin).
taggedProfileSimnetEvent::TaggedProfileUn risultato del profiler è stato taggato; contiene tag, profileKey, profileSlot, logs, computeUnitsConsumed.
runbookStarted / runbookCompletedSimnetEvent::RunbookStarted / RunbookCompletedUn runbook è iniziato o terminato; contiene runbookId e (al completamento) runbookErrors opzionale.

Pattern di Asserzione

Verifica che una Transazione sia Avvenuta

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();
}

Verifica che non si siano Verificati Errori di Runtime

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()?;

Perché gli Eventi Superano il Parsing dei Log

Preferisci lo stream di eventi rispetto a stdout

Il runtime stampa eventi strutturati tramite tracing, che può essere allettante catturare e analizzare da stdout. Non farlo. Lo stream di eventi strutturati:

  • Sopravvive ai cambiamenti del livello di log — gli eventi vengono emessi indipendentemente da RUST_LOG.
  • Include payload tipizzati — nessun parsing di stringhe, nessuna deriva del formato tra le versioni.
  • Ha tipi stabili — le righe di log sono formattate per gli esseri umani e cambiano liberamente; i tipi di evento fanno parte del contratto dell'SDK.

Is this page helpful?

Indice dei contenuti

Modifica pagina
© 2026 Solana Foundation. Tutti i diritti riservati.