Surfpool 运行时提供了三个时间旅行辅助函数。每个函数均将本地时钟移动至绝对目标时间(而非相对偏移量),并返回更新后的
EpochInfo,以便测试用例验证运行时是否确实到达了请求的时间。
| 辅助函数 | 目标 | 返回值 |
|---|---|---|
time_travel_to_slot / timeTravelToSlot | 绝对 slot 编号 | 包含新 absolute_slot 的 EpochInfo |
time_travel_to_epoch / timeTravelToEpoch | 绝对 epoch 编号 | 包含新 epoch 的 EpochInfo |
time_travel_to_timestamp / timeTravelToTimestamp | 毫秒级 Unix 时间戳 | 反映对应 slot 的 EpochInfo |
仅支持向前跳转
时间旅行只能将时钟向前拨动。目标时间若位于过去则会被拒绝而非忽略——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:00Zlet info = cheats.time_travel_to_timestamp(1_893_456_000_000)?;
时钟返回的是秒,而非毫秒
从运行时读取回来的每个时间戳均以秒为单位——包括链上 Clock sysvar 的 unix_timestamp,以及 systemClockUpdated 事件携带的 ClockValue。若将这些值直接传入时间旅行辅助函数,会被解析为 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 和 @solana/surfpool 中的 Surfnet 类均未封装这两个作弊码。请通过 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没有时间字段。请改从Clocksysvar 读取模拟的 Unix 时间:对SysvarC1ock11111111111111111111111111111111使用jsonParsed编码调用getAccountInfo——同样以秒为单位。 - 无暂停标志。 没有任何 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
的奖励),请在每个 epoch 之间 携带所需交易调用一次 time_travel_to_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 |
| epoch 内的 slot | slot_index: u64 | slotIndex: number |
| 每 epoch 的 slot 数 | 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?