Среда выполнения Surfpool предоставляет три вспомогательных метода для
путешествия во времени. Каждый из них перемещает локальные часы к абсолютной
цели (не к относительному смещению) и возвращает обновлённый EpochInfo, чтобы
тесты могли убедиться, что среда выполнения действительно достигла запрошенного
момента времени.
| Вспомогательный метод | Цель | Возвращает |
|---|---|---|
time_travel_to_slot / timeTravelToSlot | Абсолютный номер slot | EpochInfo с новым absolute_slot |
time_travel_to_epoch / timeTravelToEpoch | Абсолютный номер epoch | EpochInfo с новым 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's Date.now(), — не в секундах. Среда выполнения вычисляет ближайший slot
для этой временно́й метки.
// 2030-01-01T00:00:00Zlet info = cheats.time_travel_to_timestamp(1_893_456_000_000)?;
Часы возвращают секунды, а не миллисекунды
Каждая временна́я метка, считываемая из среды выполнения, указана в секундах —
unix_timestamp on-chain sysvar'а Clock и
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-время из sysvar'а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 со следующими полями:
| Поле | Rust | JS |
|---|---|---|
| Абсолютный slot | absolute_slot: u64 | absoluteSlot: number |
| slot внутри epoch | slot_index: u64 | slotIndex: number |
| slot в epoch | slots_in_epoch: u64 | slotsInEpoch: number |
| Номер epoch | epoch: u64 | epoch: number |
| Высота блока | block_height: u64 | blockHeight: number |
| Число транзакций | transaction_count: Option<u64> | transactionCount?: number |
Is this page helpful?