Solana时钟及其他“区块”变量

文章详细介绍了 Solidity 中的块变量在 Solana 中的类比实现,包括 block.timestamp、block.number、block.coinbase 等,并提供了具体的代码示例和说明。

solana clock

今天我们将覆盖 Solidity 中所有区块变量的类比。并不是所有的变量都有一一对应的类比。在 Solidity 中,我们有以下常用的区块变量:

  • block.timestamp
  • block.number
  • blockhash()

以及一些较少使用的变量:

  • block.coinbase
  • block.basefee
  • block.chainid
  • block.difficulty / block.prevrandao

我们假设你已经知道它们的作用,但如果你需要复习,可以查看 Solidity 全局变量文档

Solana 中的 block.timestamp

通过利用 Clock sysvar 中的 unix_timestamp 字段,我们可以访问 Solana 的区块时间戳。

首先,我们初始化一个新的 Anchor 项目:

anchor init sysvar

将初始化函数替换为以下内容:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    let clock: Clock = Clock::get()?;
    msg!(
        "区块时间戳: {}",
        // 获取 block.timestamp
        clock.unix_timestamp,
    );
    Ok(())
}

Anchor 的预导入模块包含 Clock 结构,默认情况下会自动导入:

use anchor_lang::prelude::*;

有些令人困惑的是,unix_timestamp 返回的类型是 i64,而不是 u64,这意味着它支持负数,尽管时间不能为负数。然而,时间间隔可以是负数。

获取星期几

现在让我们创建一个程序,使用 Clock sysvar 中的 unix_timestamp 告诉我们当前是星期几。

chrono crate 提供了 Rust 中的日期和时间操作功能。

在程序目录 ./sysvar/Cargo.toml 中将 chrono crate 添加为依赖项:

[dependencies]
chrono = "0.4.31"

sysvar 模块中导入 chrono crate:

// ...其他代码

#[program]
pub mod sysvar {
    use super::*;
    use chrono::*;  // 新增这一行

    // ...
}

现在,我们在程序下方添加这个函数:

pub fn get_day_of_the_week(
    _ctx: Context<Initialize>) -> Result<()> {

    let clock = Clock::get()?;
    let time_stamp = clock.unix_timestamp; // 当前时间戳

    let date_time = chrono::NaiveDateTime::from_timestamp_opt(time_stamp, 0).unwrap();
    let day_of_the_week = date_time.weekday();

    msg!("星期几是: {}", day_of_the_week);

    Ok(())
}

我们将从 Clock sysvar 获得的当前 unix 时间戳作为参数传递给 from_timestamp_opt 函数,返回一个包含日期和时间的 NaiveDateTime 结构。然后,我们调用星期几方法以根据传递的时间戳获取当前星期几。

并更新我们的测试:

it("获取星期几", async () => {
    const tx = await program.methods.getDayOfTheWeek().rpc();
    console.log("你的交易签名", tx);
});

再次运行测试并获取以下日志:

在插槽 36 中执行交易:
  签名: 5HVAjmo85Yi3yeQX5t6fNorU1da4H1zvgcJN7BaiPGnRwQhjbKd5YHsVE8bppU9Bg2toF4iVBvhbwkAtMo4NJm7V
  状态: Ok
  日志信息:
    程序 H52ppiSyiZyYVn1Yr9DgeUKeChktUiPwDfuuo932Uqxy 调用 [1]
    程序日志: 指令: GetDayOfTheWeek
    程序日志: 星期几是: Wed
    程序 H52ppiSyiZyYVn1Yr9DgeUKeChktUiPwDfuuo932Uqxy 消耗了 1597 个计算单位(总计 200000 个计算单位)

注意日志中的“星期几是: Wed”。

Solana 中的 block.number

Solana 有一个“插槽编号”的概念,团队相关于“区块编号”,但并不相同。关于这二者的区别将在以下教程中讨论,因此我们暂时推迟关于如何获取“区块编号”的完整讨论。

block.coinbase

在以太坊中,“块 coinbase”代表已成功挖掘一个区块的矿工的地址,而 Solana 使用一种基于领导者的共识机制,它结合了历史证明(PoH)和权益证明(PoS),取消了挖矿的概念。相反,一名 区块或插槽领导者 被任命来验证交易并在某些时间间隔内提出区块,依据一种被称为 领导者计划 的系统。该计划确定了在特定时间内的区块生产者。

然而,目前没有特定的方法可以在 Solana 程序中访问区块领导者的地址。

blockhash

我们包括这一节以保持完整性,但这将很快被弃用。

对于不感兴趣的读者,可以跳过这一部分而没有后果。

Solana 有一个 RecentBlockhashes sysvar,它保存活动的最近区块哈希及其相关的费用计算器。然而,这个 sysvar 已被 弃用,并将在未来的 Solana 版本中不再支持。RecentBlockhashes sysvar 没有像 Clock sysvar 那样提供获取方法。然而,缺少此方法的 sysvar 可以使用 sysvar_name::from_account_info 访问。

我们还将介绍一些新语法,这将在稍后说明。现在,请将其视为模板:

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

以下是我们如何在 Solana 获取最新的区块哈希:

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

// 替换程序 ID
declare_id!("H52ppiSyiZyYVn1Yr9DgeUKeChktUiPwDfuuo932Uqxy");

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

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        // 最近区块哈希
        let arr = [ctx.accounts.recent_blockhashes.clone()];
        let accounts_iter = &mut arr.iter();
        let sh_sysvar_info = next_account_info(accounts_iter)?;
        let recent_blockhashes = RecentBlockhashes::from_account_info(sh_sysvar_info)?;
        let data = recent_blockhashes.last().unwrap();

        msg!("最近区块哈希是: {:?}", data.blockhash);
        Ok(())
    }
}

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

测试文件:

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

describe("sysvar", () => {
  // 配置客户端以使用本地集群。
  anchor.setProvider(anchor.AnchorProvider.env());

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

  it("已初始化!", async () => {
    // 在这里添加你的测试。
    const tx = await program.methods
      .initialize()
      .accounts({
        recentBlockhashes: anchor.web3.SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
      })
      .rpc();

    console.log("交易哈希:", tx);
  });
});

我们运行测试并获得以下日志:

日志信息:
  程序 H52ppiSyiZyYVn1Yr9DgeUKeChktUiPwDfuuo932Uqxy 调用 [1]
  程序日志: 指令: Initialize
  程序日志: 最近区块哈希是: erVQHJdJ11oL4igkQwDnv7oPZoxt88j7u8DCwHkVFnC
  程序 H52ppiSyiZyYVn1Yr9DgeUKeChktUiPwDfuuo932Uqxy 消耗了 46181 个计算单位(总计 200000 个计算单位)
  程序 H52ppiSyiZyYVn1Yr9DgeUKeChktUiPwDfuuo932Uqxy 成功

我们可以看到最新的区块哈希。请注意,由于我们正在部署到本地节点,因此我们获得的区块哈希是本地节点的,而不是 Solana 主网络的。

在时间结构方面,Solana 在固定时间线中运作,划分为插槽,每个插槽是分配给领导者提出区块的时间部分。这些插槽进一步组织为纪元,在这些预定义期间内,领导者计划保持不变。

block.gaslimit

Solana 每区块计算单位 限制为 48000000。每个交易默认限制为 200,000 计算单位,尽管可以提高到 1.4 million 计算单位(我们将在后续教程中讨论这一点,但你可以 在这里查看示例)。

从 Rust 程序中无法访问此限制。

block.basefee

在以太坊中,basefee 是动态的,根据 EIP-1559 的规定;它是前一个区块利用率的函数。而在 Solana 中,交易的基本价格是静态的,因此不需要这样的变量。

block.difficulty

区块难度是与工作量证明(PoW)区块链相关的概念。而 Solana 基于历史证明(PoH)与权益证明(PoS)共识机制运作,这不涉及区块难度的概念。

block.chainid

Solana 没有链 ID,因为它不是 EVM 兼容的区块链。block.chainid 是 Solidity 智能合约知道它们在测试网、L2、主网或其他 EVM 兼容链上的方式。

Solana 为 Devnet、Testnet 和 Mainnet 运行独立集群,但程序没有机制来知道它们所处的哪个集群。但你可以在部署时使用 Rust cfg 功能根据各个集群调整程序代码。以下是 根据集群更改程序 ID 的示例

了解更多

本教程是 Solana 课程 的一部分。

最初发布于 2024年2月18日

  • 原文链接: rareskills.io/post/solan...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/