Подорож у часі

Рантайм Surfpool надає три допоміжні засоби для подорожей у часі. Кожен із них переміщує локальний годинник до абсолютної цілі (а не відносного зміщення) і повертає оновлений EpochInfo, щоб тести могли перевірити, що рантайм справді досяг запитаного часу.

ПомічникЦільПовертає
time_travel_to_slot / timeTravelToSlotАбсолютний номер slotEpochInfo з новим absolute_slot
time_travel_to_epoch / timeTravelToEpochАбсолютний номер epochEpochInfo з новим epoch
time_travel_to_timestamp / timeTravelToTimestampМітка часу Unix у мілісекундахEpochInfo, що відображає відповідний slot

Лише вперед

Подорож у часі може переміщати годинник лише вперед. Ціль у минулому відхиляється, а не ігнорується — хелпери Rust повертають Err, а JS-хелпери кидають виняток. Повідомлення вказує, яка одиниця пішла назад, наприклад: Cannot travel to past slot: target=1000, current=2000.

Перехід до slot

use surfpool_sdk::Surfnet;
let surfnet = Surfnet::start().await?;
let cheats = surfnet.cheatcodes();
let info = cheats.time_travel_to_slot(1_000_000)?;
assert!(info.absolute_slot >= 1_000_000);

Перехід до epoch

let info = cheats.time_travel_to_epoch(420)?;
assert_eq!(info.epoch, 420);

Перехід до мітки часу Unix

Передавайте мітку часу в мілісекундах від початку епохи Unix — у тих самих одиницях, що й JavaScript Date.now() — а не в секундах. Середовище виконання обчислює найближчий slot для цієї мітки часу.

// 2030-01-01T00:00:00Z
let info = cheats.time_travel_to_timestamp(1_893_456_000_000)?;

Годинник повертає секунди, а не мілісекунди

Кожна мітка часу, яку ви зчитуєте з середовища виконання, вимірюється в секундахunix_timestamp системної змінної Clock on-chain, і ClockValue, що передається подією systemClockUpdated. Якщо передати одне з цих значень безпосередньо до хелпера подорожі в часі, воно вказуватиме на момент на початку 1970 року, тому виклик буде відхилено як ціль у минулому. Попередньо помножте на 1000.

// WRONG — `unixTimestamp` is in seconds, so this is a past target.
surfnet.timeTravelToTimestamp(clock.unixTimestamp + 3600);
// RIGHT — convert to milliseconds.
surfnet.timeTravelToTimestamp((clock.unixTimestamp + 3600) * 1000);

Призупинення та відновлення годинника

Подорож у часі переміщає до цілі; призупинення повністю зупиняє рух годинника. surfnet_pauseClock зупиняє виробництво slot і плин часу до моменту запуску surfnet_resumeClock — використовуйте це, коли тесту потрібно зафіксувати slot і мітку часу незмінними протягом кількох перевірок.

Ні Rust SDK, ні клас Surfnet у @solana/surfpool не обгортають ці два читкоди. Викликайте їх через плагін Kit або безпосередньо через JSON-RPC.

const paused = await client.cheatcodes.pauseClock().send();
// Nothing advances until the clock is resumed, so this is the only thing that
// moves the slot.
await client.cheatcodes
.timeTravel({ absoluteSlot: paused.absoluteSlot + 1_000n })
.send();
await client.cheatcodes.resumeClock().send();

Обидва читкоди повертають EpochInfo — стан годинника на момент призупинення та після відновлення. Зверніть увагу на дві речі, яких ця структура не містить:

  • Без мітки часу. EpochInfo не має поля часу. Зчитуйте симульований час Unix із системної змінної Clock: getAccountInfo для SysvarC1ock11111111111111111111111111111111 з кодуванням jsonParsed — знову ж у секундах.
  • Без прапорця призупинення. Жоден метод RPC не повідомляє, чи призупинено годинник наразі. З боку SDK відстежуйте подію clockUpdate, яка спрацьовує з clockCommand щоразу, коли виконується призупинення, відновлення або зміна інтервалу. В іншому випадку відстежуйте стан у своєму тесті.

Поширені шаблони

Тестування блокування або вікна вестингу

Наведений нижче приклад є схематичним — assertWithdrawFails та assertWithdrawSucceeds є заповнювачами для будь-яких допоміжних засобів на стороні клієнта, які ваш набір тестів використовує для перевірки поведінки RPC.

import { Surfnet } from "@solana/surfpool";
const surfnet = Surfnet.start();
const beneficiary = Surfnet.newKeypair();
// 1. Set up a vesting account that unlocks at slot 1,000,000.
surfnet.setAccount(/* ...lockup program state... */);
// 2. Verify withdrawal fails before unlock.
await assertWithdrawFails(surfnet.rpcUrl, beneficiary);
// 3. Travel past the unlock slot.
surfnet.timeTravelToSlot(1_000_001);
// 4. Verify withdrawal succeeds.
await assertWithdrawSucceeds(surfnet.rpcUrl, beneficiary);
surfnet.stop();

Керування Сценарієм із Кількох Epoch

Проміжні slot пропускаються

Подорож у часі переходить напряму до цілі — переміщення з epoch 1 до epoch 5 пропускає slot між ними. Якщо вашій програмі потрібно, щоб спрацювала кожна проміжна межа epoch (наприклад, для нарахування винагород за epoch), викликайте time_travel_to_epoch один раз на epoch із необхідними транзакціями між викликами.

for epoch in 2..=5 {
cheats.time_travel_to_epoch(epoch)?;
surfnet.rpc_client().send_transaction(&claim_rewards_tx)?;
}

Структура EpochInfo

Обидва SDK повертають об'єкт типу EpochInfo із такими полями:

ПолеRustJS
Абсолютний slotabsolute_slot: u64absoluteSlot: number
Slot у межах epochslot_index: u64slotIndex: number
Slot на epochslots_in_epoch: u64slotsInEpoch: number
Номер epochepoch: u64epoch: number
Висота блокуblock_height: u64blockHeight: number
Кількість транзакційtransaction_count: Option<u64>transactionCount?: number

Is this page helpful?