События среды выполнения

Среда выполнения Surfnet генерирует поток структурированных событий, охватывающих обработку транзакций, тики slot, потоковую передачу аккаунтов, перемещение во времени и ошибки. SDK предоставляет этот поток, чтобы тесты могли проверять поведение среды выполнения без разбора логов.

Rust: Channel Receiver

Surfnet::events() возвращает ссылку на crossbeam_channel::Receiver<SimnetEvent>, в который публикует события среда выполнения. Извлекайте их с помощью try_iter() (неблокирующий режим) или читайте отдельные события через 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:?}");
}
}
}

Receiver is shared

Если вы запускаете фоновую задачу для потребления событий, не вызывайте также try_iter из основного потока. События будут непредсказуемо распределяться между двумя потребителями.

JS: Drain Method

JS-биндинги буферизуют события внутри и предоставляют доступ к ним через drainEvents. Каждый вызов возвращает всё накопленное с момента предыдущего вызова, а затем очищает буфер.

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

Unbounded buffer

Если вы никогда не вызываете drainEvents, буфер растёт на протяжении всего времени жизни экземпляра. Для коротких тестов это допустимо, но при длительных фикстурах вызывайте его периодически, чтобы не допустить неконтролируемого роста памяти.

Структура SimnetEventValue (JS)

Тип события JS представляет собой плоский объект с дискриминатором kind и необязательными полями. Не каждое поле задано для каждого варианта — выполняйте деструктуризацию на основе 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[];
}

Типы событий

Строки типов на стороне JS используют camelCase; соответствующий вариант Rust использует PascalCase. Сопоставляйте по этим точным строкам — они чувствительны к регистру и стабильны между версиями.

JS kindВариант RustГенерируется когда
readySimnetEvent::ReadyСреда выполнения завершила загрузку; содержит initialTransactionCount.
connectedSimnetEvent::ConnectedУстановлено соединение с удалённым RPC; содержит message (URL).
abortedSimnetEvent::AbortedЗапуск был прерван; содержит message (причина).
shutdownSimnetEvent::ShutdownНачинается плановое завершение работы.
systemClockUpdatedSimnetEvent::SystemClockUpdatedСистемные часы продвинулись вперёд; содержит clock.
clockUpdateSimnetEvent::ClockUpdateВыполнена команда управления часами (пауза/возобновление/обновление интервала); содержит clockCommand и, опционально, slotIntervalMs.
epochInfoUpdateSimnetEvent::EpochInfoUpdateДоступен новый снимок информации epoch; содержит epochInfo.
blockHashExpiredSimnetEvent::BlockHashExpiredИстёк срок действия использовавшегося blockhash.
transactionReceivedSimnetEvent::TransactionReceivedТранзакция поставлена в очередь; содержит timestamp, transactionSignature.
transactionProcessedSimnetEvent::TransactionProcessedТранзакция выполнена; содержит timestamp, transactionSignature, logs, computeUnitsConsumed, fee и errorMessage в случае сбоя.
accountUpdateSimnetEvent::AccountUpdateАккаунт изменился — охватывает потоковые аккаунты и мутации через cheatcode; содержит timestamp и accountPubkey.
infoLog / warnLog / errorLog / debugLogSimnetEvent::InfoLog / WarnLog / ErrorLog / DebugLogСреда выполнения вывела строку лога; содержит timestamp и message.
pluginLoadedSimnetEvent::PluginLoadedЗагружен плагин Geyser; содержит message (имя плагина).
taggedProfileSimnetEvent::TaggedProfileЗафиксирован результат профилировщика; содержит tag, profileKey, profileSlot, logs, computeUnitsConsumed.
runbookStarted / runbookCompletedSimnetEvent::RunbookStarted / RunbookCompletedRunbook запущен или завершён; содержит runbookId и (по завершении) необязательный runbookErrors.

Паттерны проверок

Проверка успешного выполнения транзакции

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

Проверка отсутствия ошибок во время выполнения

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

Почему события лучше парсинга логов

Используйте поток событий вместо stdout

Среда выполнения выводит структурированные события через tracing, что может соблазнить вас перехватывать и парсить их из stdout. Не делайте этого. Структурированный поток событий:

  • Устойчив к изменениям уровня логирования — события генерируются независимо от RUST_LOG.
  • Содержит типизированные данные — никакого парсинга строк, никакого смещения формата между версиями.
  • Имеет стабильные типы — строки логов форматируются для людей и могут свободно меняться; типы событий являются частью контракта SDK.

Is this page helpful?