Путешествие во времени

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

Вспомогательный методЦельВозвращает
time_travel_to_slot / timeTravelToSlotАбсолютный номер slotEpochInfo с новым absolute_slot
time_travel_to_epoch / timeTravelToEpochАбсолютный номер epochEpochInfo с новым epoch
time_travel_to_timestamp / timeTravelToTimestampUnix-временная метка в миллисекундах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:00Z
let 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 со следующими полями:

Поле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?