أحداث وقت التشغيل

يُصدر وقت تشغيل Surfnet تدفقًا من الأحداث المنظمة التي تغطي معالجة المعاملات، ونبضات slot، وبث الحسابات، والتنقل عبر الزمن، والأخطاء. يعرض SDK هذا التدفق حتى تتمكن الاختبارات من التحقق من سلوك وقت التشغيل دون تحليل السجلات.

Rust: مستقبل القناة

يُعيد 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:?}");
}
}
}

المستقبل مشترك

إذا أطلقت مهمة خلفية لاستهلاك الأحداث، فلا تستدعِ try_iter من الخيط الرئيسي أيضًا. سيتم توزيع الأحداث بشكل غير متوقع بين المستهلكَين.

JS: طريقة التصريف

تقوم روابط 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();
}

مخزن مؤقت غير محدود

إذا لم تستدعِ 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انتهت صلاحية تجزئة كتلة كانت قيد الاستخدام.
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 / RunbookCompletedبدأ دفتر تشغيل أو انتهى؛ يحمل 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?