Solana 系统变量详解

  • 0xE
  • 发布于 1天前
  • 阅读 177

在 Solana 中,系统变量(sysvars)是只读的系统账户,为程序提供区块链状态和网络信息的访问权限。它们类似于以太坊的全局变量,但每个系统变量拥有唯一的公钥地址,类似以太坊的预编译合约。

在 Solana 中,系统变量(sysvars)是只读的系统账户,为程序提供区块链状态和网络信息的访问权限。它们类似于以太坊的全局变量,但每个系统变量拥有唯一的公钥地址,类似以太坊的预编译合约。

在 Anchor 中,访问系统变量有两种方式:

  1. 使用 Anchor 提供的 get 方法。
  2. #[derive(Accounts)] 中通过公钥地址将其作为账户引用。

并非所有系统变量都支持 get 方法,且部分已被弃用(下文将注明)。对于无 get 方法的系统变量,需通过公钥访问。

以下是常见的 Solana 系统变量:

  • Clock:提供时间相关信息,如当前时间戳和槽号。
  • EpochSchedule:包含纪元(epoch)调度信息。
  • Rent:提供租金率和免租最低余额要求。
  • Fees:记录当前槽的费用计算器。
  • EpochRewards:保存纪元奖励分配记录。
  • RecentBlockhashes:记录最近区块哈希。
  • SlotHashes:记录最近槽哈希历史。
  • SlotHistory:记录最近纪元可用槽。
  • StakeHistory:记录网络质押激活和停用历史。
  • Instructions:提供当前交易的序列化指令。
  • LastRestartSlot:记录上次 Solana 重启的槽号。

Solana 中的槽与区块

槽(slot)是约 400 毫秒的时间窗口,领导者在此期间可生成区块。一个槽通常对应一个区块(包含交易列表),但若领导者未生成区块,槽可能为空。通过 Solana 浏览器 查看,槽与区块的哈希始终不同。


使用 get 方法访问系统变量

支持 get 方法的系统变量包括 Clock、EpochSchedule 和 Rent。Fees 和 EpochRewards 虽在 Solana 文档中列为可访问,但在最新 Anchor 版本中已弃用。

创建新 Anchor 项目:

anchor init sysvars
cd sysvars
anchor build

Clock 系统变量

更新 lib.rs:

use anchor_lang::prelude::*;

declare_id!("DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL");

#[program]
pub mod sysvars {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let clock = Clock::get()?;
        msg!("clock: {:?}",clock);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

运行 anchor test,日志显示 Clock 的字段(如 unix_timestamp、slot 等)。

Transaction executed in slot 6:
  Signature: 2VNMBwFkpvbiL1cdn2K1Xb2gUX4Zt9EnwxFTSabAS3WYxPUFhhuZqygu3bH4NTNfkhm7y6Cgw4dLU5sWaFbjHph1
  Status: Ok
  Log Messages:
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL invoke [1]
    Program log: Instruction: Initialize
    Program log: clock: Clock { slot: 6, epoch_start_timestamp: 1741833575, epoch: 0, leader_schedule_epoch: 1, unix_timestamp: 1741833577 }
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL consumed 3853 of 200000 compute units
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL success

EpochSchedule 系统变量

纪元(epoch)约为两天,质押或赎回 SOL 需在纪元边界生效(详见 Solana 质押)。

更新 initialize:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    let epoch_schedule = EpochSchedule::get()?;
    msg!("epoch schedule: {:?}",epoch_schedule);
    Ok(())
}

日志示例:

Transaction executed in slot 3:
  Signature: 5K8Nd27QHGABWSHSHqHLQri4mQYviHcb5PtkBXYzERLXLfcMbrZD7fJZLvJHVVXmVEuB4Ly8htUptMsM2UFwnq4w
  Status: Ok
  Log Messages:
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL invoke [1]
    Program log: Instruction: Initialize
    Program log: epoch schedule: EpochSchedule { slots_per_epoch: 432000, leader_schedule_slot_offset: 432000, warmup: false, first_normal_epoch: 0, first_normal_slot: 0 }
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL consumed 3447 of 200000 compute units
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL success
  • slots_per_epoch:432,000(每个纪元的槽数)
  • leader_schedule_slot_offset:432,000 (领导者调度槽偏移,即下一纪元领导者计划的参考槽位)
  • warmup:布尔值,指示网络热身阶段
  • first_normal_epoch 和 first_normal_slot:首个正常纪元及其槽号(本地测试为 0,主网更高)

Rent 系统变量

更新 initialize:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    let rent_var = Rent::get()?;
    msg!("Rent {:?}",rent_var);
    Ok(())
}

日志示例:

Transaction executed in slot 3:
  Signature: 5Fy1zqmj1UaU4ggaFipm4c6CNrGWUADBuebMHzmc9WfwAhoBteMg1g4rP42pbAWYoydyJnMXVgR1ftijcVRXLU4u
  Status: Ok
  Log Messages:
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL invoke [1]
    Program log: Instruction: Initialize
    Program log: Rent Rent { lamports_per_byte_year: 3480, exemption_threshold: 2.0, burn_percent: 50 }
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL consumed 3497 of 200000 compute units
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL success
  • lamports_per_byte_year:每年每字节的租金(3480 lamports)
  • exemption_threshold:免租余额乘数(2)
  • burn_percent:燃烧比例(50%)

租金详情将在后续文章中展开。


使用公钥地址访问系统变量

对于无 get 方法的系统变量,需通过公钥在 #[derive(Accounts)] 中引用。

StakeHistory 系统变量

记录网络质押历史,由于我们使用的是本地验证器,所以将返回空数据。公钥为 SysvarStakeHistory1111111111111111111111111。

更新 lib.rs:

use anchor_lang::prelude::*;

declare_id!("DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL");

#[program]
pub mod sysvars {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        // Previous code...

        // Accessing the StakeHistory sysvar
        // Create an array to store the StakeHistory account
        let arr = [ctx.accounts.stake_history.clone()];

        // Create an iterator for the array
        let accounts_iter = &mut arr.iter();

        // Get the next account info from the iterator (still StakeHistory)
        let sh_sysvar_info = next_account_info(accounts_iter)?;

        // Create a StakeHistory instance from the account info
        let stake_history = StakeHistory::from_account_info(sh_sysvar_info)?;

        msg!("stake_history: {:?}", stake_history);

        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    /// CHECK: 
    pub stake_history: AccountInfo<'info>,
}

更新测试 tests/sysvars.ts:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Sysvars } from "../target/types/sysvars";

describe("sysvars", () => {
  anchor.setProvider(anchor.AnchorProvider.env());
  const program = anchor.workspace.Sysvars as Program<Sysvars>;

  const StakeHistory_PublicKey = new anchor.web3.PublicKey(
    "SysvarStakeHistory1111111111111111111111111"
  );

  it("Is initialized!", async () => {
    const tx = await program.methods
      .initialize()
      .accounts({
        stakeHistory: StakeHistory_PublicKey,
      })
      .rpc();
    console.log("Your transaction signature", tx);
  });
});

日志显示空数据(本地验证器未记录质押历史)。

Transaction executed in slot 3:
  Signature: 3viDgr1ycjNsQSxS2iC55RdQu19qpbuF7cAcVxfeLjeiHCEzS4mdMqiCiKPDpJqFqJ5j3RFaHysS7zBh9eUNeKhr
  Status: Ok
  Log Messages:
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL invoke [1]
    Program log: Instruction: Initialize
    Program log: stake_history: StakeHistory([])
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL consumed 1576 of 200000 compute units
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL success

Instructions 系统变量

提供当前交易的序列化指令及元数据。更新 lib.rs:

use anchor_lang::prelude::*;
use anchor_lang::solana_program::sysvar::{instructions, fees::Fees, recent_blockhashes::RecentBlockhashes};

declare_id!("DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL");

#[program]
pub mod sysvars {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, number: u32) -> Result<()> {
        let arr = [ctx.accounts.instruction_sysvar.clone()];
    
        let account_info_iter = &mut arr.iter();
    
        let instructions_sysvar_account = next_account_info(account_info_iter)?;
    
        let instruction_details =
            instructions::load_instruction_at_checked(0, instructions_sysvar_account)?;
    
        msg!(
            "Instruction details of this transaction: {:?}",
            instruction_details
        );
        msg!("Number is: {}", number);
    
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    /// CHECK: 
    pub stake_history: AccountInfo<'info>, 
    /// CHECK: 
    pub recent_blockhashes: AccountInfo<'info>,
    /// CHECK: 
    pub instruction_sysvar: AccountInfo<'info>,
}

更新测试:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Sysvars } from "../target/types/sysvars";

describe("sysvars", () => {
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.Sysvars as Program<Sysvars>;

  const StakeHistory_PublicKey = new anchor.web3.PublicKey(
    "SysvarStakeHistory1111111111111111111111111"
  );

  it("Is initialized!", async () => {
    const tx = await program.methods
      .initialize(3)
      .accounts({
        stakeHistory: StakeHistory_PublicKey,
        recentBlockhashes: anchor.web3.SYSVAR_RECENT_BLOCKHASHES_PUBKEY, 
        instructionSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY, 
      })
      .rpc();
    console.log("Your transaction signature", tx);
  });
});

日志显示程序 ID、指令数据及输入参数(如 number: 3)。

仔细查看日志,可以观察到程序 ID、系统变量指令的公钥、序列化数据以及其他元数据。
此外,在序列化指令数据和我们自定义的程序日志中,能够看到数字 3。而前面的序列化数据是 Anchor 注入的鉴别器。

Transaction executed in slot 3:
  Signature: 3F1jxLj3phq58F1Yi4y8mTWZLE156di1AH7asNzy5rdbNdNctrGczo1oc9QMWfKGPc6eZFpE3j2NtqN8nd7Qy4rM
  Status: Ok
  Log Messages:
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL invoke [1]
    Program log: Instruction: Initialize
    Program log: Instruction details of this transaction: Instruction { program_id: DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL, accounts: [AccountMeta { pubkey: SysvarStakeHistory1111111111111111111111111, is_signer: false, is_writable: false }, AccountMeta { pubkey: SysvarRecentB1ockHashes11111111111111111111, is_signer: false, is_writable: false }, AccountMeta { pubkey: Sysvar1nstructions1111111111111111111111111, is_signer: false, is_writable: false }], data: [175, 175, 109, 31, 13, 152, 155, 237, 3, 0, 0, 0] }
    Program log: Number is: 3
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL consumed 55224 of 200000 compute units
    Program DfnoLKzEct937xzk3a3gW2WSwD2tGidFNq9xxuN76QvL success

当前 Anchor 中不可访问的系统变量

EpochRewards、SlotHistory 和 SlotHashes 在最新 Anchor 版本中无法访问,尝试调用会报错。


【笔记配套代码】
https://github.com/0xE1337/rareskills_evm_to_solana
【参考资料】
https://learnblockchain.cn/column/119
https://www.rareskills.io/solana-tutorial

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论