Web3 开发实战:用 Anchor 打造 Solana 猜数游戏

Web3开发实战:用Anchor打造Solana猜数游戏在Web3浪潮席卷全球的今天,Solana以其超高的交易速度和低廉的成本,成为区块链开发者的热门选择。而Anchor框架作为Solana生态的利器,让智能合约开发变得简单又高效。本文将带你走进Web3开发的世界,通

Web3 开发实战:用 Anchor 打造 Solana 猜数游戏

在 Web3 浪潮席卷全球的今天,Solana 以其超高的交易速度和低廉的成本,成为区块链开发者的热门选择。而 Anchor 框架作为 Solana 生态的利器,让智能合约开发变得简单又高效。本文将带你走进 Web3 开发的世界,通过一个有趣的猜数游戏实战案例,手把手教你如何利用 Anchor 在 Solana 上构建链上应用。无论你是想入门 Web3,还是希望掌握 Solana 开发的实用技巧,这篇文章都将是你开启区块链之旅的绝佳起点!

本文通过一个完整的 Web3 开发实战案例,详细展示了如何使用 Anchor 框架在 Solana 区块链上构建并部署一个猜数游戏。内容涵盖 Anchor 的安装与核心命令(如 init、build、deploy 等)的使用方法,深入解析高级功能(如 verify 和 upgrade),并提供从代码编写到本地测试网络部署的全流程指导。此外,还分享了开发中常见问题的解决方案,帮助读者快速上手。通过这个案例,你将掌握 Web3 开发的关键技能,解锁 Solana 的无限可能。

安装

要在 Solana 上使用 Anchor 开发,首先需要安装必要的工具。以下是简明的安装步骤,更多细节可参考官方文档:Anchor 安装指南:
https://www.anchor-lang.com/docs/installation

具体步骤

  1. 安装 Rust 和 Cargo

Anchor 依赖 Rust 编程语言,运行以下命令安装最新版本:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装完成后,检查版本:

rustc --version  # 示例输出:rustc 1.87.0-nightly (be73c1f46 2025-03-21)
cargo --version  # 示例输出:cargo 1.87.0-nightly (6cf826701 2025-03-14)
  1. 安装 Solana CLI

Solana 工具链是部署和测试的基础,执行以下命令:

sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

验证安装:

solana --version  # 示例输出:solana-cli 2.1.16
  1. 安装 Anchor CLI

使用 Cargo 安装 Anchor 的命令行工具:

cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked

检查版本:

anchor --version  # 示例输出:anchor-cli 0.30.1

注意事项

  • 确保网络畅通,安装过程中可能需要科学上网。
  • 如果遇到版本兼容问题,可参考官方文档调整工具版本。
  1. 检查安装情况
rustc --version
rustc 1.87.0-nightly (be73c1f46 2025-03-21)

cargo --version
cargo 1.87.0-nightly (6cf826701 2025-03-14)

anchor --version
anchor-cli 0.30.1

Anchor 常用命令详解

创建新项目

anchor init my_project

构建编译程序

anchor build [my_project]

它会在target/deploy目录下生成编译后的合约二进制文件。如果在项目目录下可以省略项目名称。

测试

anchor test

部署

anchor deploy

查看帮助信息

anchor help
Usage: anchor [OPTIONS] <COMMAND>

Commands:
  init      Initializes a workspace
  build     Builds the workspace
  expand    Expands macros (wrapper around cargo expand)
  verify    Verifies the on-chain bytecode matches the locally compiled artifact. Run this command inside a program subdirectory, i.e., in the dir containing the program's Cargo.toml
  test      Runs integration tests
  new       Creates a new program
  idl       Commands for interacting with interface definitions
  clean     Remove all artifacts from the target directory except program keypairs
  deploy    Deploys each program in the workspace
  migrate   Runs the deploy migration script
  upgrade   Deploys, initializes an IDL, and migrates all in one command. Upgrades a single program. The configured wallet must be the upgrade authority
  cluster   Cluster commands
  shell     Starts a node shell with an Anchor client setup according to the local config
  run       Runs the script defined by the current workspace's Anchor.toml
  login     Saves an api token from the registry locally
  publish   Publishes a verified build to the Anchor registry
  keys      Keypair commands
  localnet  Localnet commands
  account   Fetch and deserialize an account using the IDL provided
  help      Print this message or the help of the given subcommand(s)

Options:
      --provider.cluster <CLUSTER>  Cluster override
      --provider.wallet <WALLET>    Wallet override
  -h, --help                        Print help
  -V, --version                     Print version

查看 migrate指令的详细信息

anchor help migrate
Runs the deploy migration script

Usage: anchor migrate [OPTIONS]

Options:
      --provider.cluster <CLUSTER>  Cluster override
      --provider.wallet <WALLET>    Wallet override
  -h, --help                        Print help

查看 migrate指令的详细信息

anchor help deploy
Deploys each program in the workspace

Usage: anchor deploy [OPTIONS] [-- <SOLANA_ARGS>...]

Arguments:
  [SOLANA_ARGS]...  Arguments to pass to the underlying `solana program deploy` command

Options:
  -p, --program-name <PROGRAM_NAME>        Only deploy this program
      --provider.cluster <CLUSTER>         Cluster override
      --program-keypair <PROGRAM_KEYPAIR>  Keypair of the program (filepath) (requires program-name)
      --provider.wallet <WALLET>           Wallet override
  -v, --verifiable                         If true, deploy from path target/verifiable
  -h, --help                               Print help

anchor init anchor new 有什么区别?

在 Anchor 框架中,anchor init 和 anchor new 都是用于创建项目的命令,但它们的用途和使用场景有所不同:

  1. anchor init Initializes a workspace

    该命令用于初始化一个新的 Anchor 工作空间。它会生成一个包含基本项目结构和必要配置文件(如 Anchor.toml)的工作空间。使用时,你需要指定工作空间的名称,例如:

    anchor init my_workspace
    

    其中,my_workspace 是你定义的工作空间名称。执行后,将创建一个基础目录,方便后续开发。

  2. anchor new Creates a new program
    该命令用于在已有工作空间内创建一个新的 Anchor 程序(即 Solana 智能合约)。它会生成程序相关的目录和文件(如 Rust 源代码和测试文件)。使用时,你需要指定程序名称,例如:

    bash

    anchor new my_program
    

    其中,my_program 是新程序的名称。注意,此命令需在已初始化的工作空间中运行。

典型用法:通常,你会先使用 anchor init 初始化一个工作空间,然后在该工作空间内通过 anchor new 创建一个或多个程序。这种分步操作能有效组织项目结构,确保开发流程清晰有序。

anchor verify 指令的作用与用法

anchor verify 是 Anchor 框架中的一个命令行工具,用于验证已部署到 Solana 区块链上的程序字节码是否与本地编译的构件(artifact)一致。其主要目的是确保链上程序与开发者本地代码版本匹配,从而提升程序的安全性与透明度。该命令通常在程序部署后使用,尤其适用于需要审计或向社区证明代码一致性的场景。

作用

  • 一致性验证:比较链上程序字节码与本地编译结果,确保两者完全相同,避免部署错误或篡改。
  • 可验证构建支持:配合 anchor build --verifiable 使用,验证链上程序是否来自可重现的构建。
  • 增强信任:为开发者提供工具,确保本地开发的智能合约与链上版本一致,适用于开源项目或高安全性需求场景。

使用方法

基本语法:

anchor verify --provider.cluster <CLUSTER> -p <PROGRAM_NAME> <PROGRAM_ID>
  • --provider.cluster :指定目标集群(如 devnet、mainnet)。
  • -p <PROGRAM_NAME>:指定程序名称(与 Cargo.toml 中的库名一致)。
  • <PROGRAM_ID>:链上程序的公钥地址。

运行前提:需在包含 Cargo.toml 的程序目录中执行。

示例

anchor verify --provider.cluster devnet -p my_program 3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg
执行步骤
  1. 检查程序 ID:确认链上程序 ID 与本地预期一致。
  2. 重新编译:在 Docker 容器中基于本地代码重新构建程序,确保环境标准化。
  3. 字节码对比:将本地编译的 .so 文件与链上字节码逐一比较。
  4. 结果输出:若匹配,验证通过;否则提示失败。

注意事项

  • 构建模式:程序需以 --verifiable 模式构建并部署,否则验证可能因环境差异失败。
  • 环境依赖:需安装 Docker,并确保网络可访问指定集群。
  • 代码版本:本地代码需与部署时的提交版本一致(如通过 git checkout <COMMIT_HASH> 切换)。

实际应用

  • 调试与排查:检查链上程序异常时,验证是否部署了正确版本。
  • 审计与透明度:便于审计人员或用户独立验证链上程序与源代码的一致性。
  • 部署后检查:减少因编译或环境问题导致的潜在错误。

通过 anchor verify,开发者可以确保本地开发的 Solana 程序与链上部署版本保持一致,是维护项目可信度和安全性的重要工具。

anchor upgrade 指令的作用

anchor upgrade 是 Anchor 框架中的一个命令,用于一次性升级单个 Solana 程序。它集成了程序部署、接口定义(IDL)初始化以及迁移执行的全过程,主要针对特定程序的更新,而非整个工作空间。以下是其核心功能和执行步骤:

作用

  • 程序升级:将最新版本的程序部署到 Solana 区块链,替换旧版本。
  • 接口初始化:更新链上的接口定义(IDL),确保客户端能正确交互。
  • 数据迁移:执行迁移脚本,实现新旧版本间的状态过渡和兼容性。

执行步骤

  1. 部署最新程序版本
    将本地编译的程序二进制文件上传到 Solana 区块链,覆盖之前的程序实例,完成链上代码更新。
  2. 初始化接口定义(IDL)
    部署程序的接口定义语言(IDL),定义数据结构、方法和事件,以便客户端解析并与程序交互。
  3. 执行迁移脚本
    运行预定义的迁移逻辑(如更新存储结构或迁移数据),确保新版本程序与旧版状态无缝衔接。

使用场景

anchor upgrade 适用于需要更新单一程序的情况,例如修复 Bug、添加功能或优化性能时。通过集成部署、IDL 初始化和迁移步骤,该命令简化了升级流程,确保程序在链上的平滑过渡。

实操 Solana 猜数游戏

Solana 程序初始化

  • 导入 Anchor 依赖
  • 声明程序ID:使用 declare_id! 宏来指定程序的链上地址,即程序 ID(Program ID)
  • 定义 Solana 程序模块
use anchor_lang::prelude::*;

declare_id!("4RzWwFZU8c4iPgRqioET5AcKXNmVdNwJ36SGZqhsJ8v5");

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

实现生成随机数功能

  • 定义随机数生成函数
  • 导入 Solana 程序库中的 Clock 模块
  • 获取当前时间
  • 生成随机数
  • 处理随机数

注意:Solana 的 Clock 模块并不是绝对精确的现实时间反映,而是网络共识下的估计值。

use solana_program::clock::Clock;

fn generate_random_number() -> u32 {
    let clock = Clock::get().expect("Failed to get clock");
    let last_digit = (clock.unix_timestamp % 10) as u8;
    let result = (last_digit + 1) as u32;
    result
}

%:取模运算。

as:类型转换

定义玩家账户

  • 定义猜数结构体
  • 添加随机数字段
  • 定义程序账户结构体
  • 添加玩家猜数字段
  • 完善程序账户结构体的其他字段

#[account]Anchor 框架中用于标注结构体(struct)的宏,表示这个结构体代表一个 Solana 账户。它会自动生成账户的序列化、反序列化逻辑,并支持账户的初始化和约束。

#[derive(Accounts)] 是一个 Anchor 提供的宏,作用于结构体(struct),用于声明某个指令(instruction)需要的账户列表,并自动生成反序列化和验证逻辑。

#[account]
pub struct GuessingAccount {
    pub number: u32,
}

#[derive(Accounts)]
pub struct AccountContext<'info> {
    #[account(
        init_if_needed, // init_if_needed:如果账户不存在则创建它
        space=8+4, // space=8+4:分配 12 个字节(8 字节用于区分符 + 4 字节用于数据)
        payer=payer, // payer=payer:指定谁为账户创建付费
        seeds = [b"guessing pda"], // seeds = [b"guessing pda"]:定义程序派生地址(PDA)的种子
        bump // bump:使用一个 bump 种子来派生 PDA
    )]
    pub guessing_account: Account<'info, GuessingAccount>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

实现玩家猜数功能

  • 编写初始化随机数的函数
  • 为游戏生成一个随机目标数字
  • 实现猜数函数
use std::cmp::Ordering;

pub fn initialize(ctx: Context<AccountContext>) -> Result<()> {
    let guessing_account = &mut ctx.accounts.guessing_account;
    guessing_account.number = generate_random_number();

    Ok(())
}

pub fn guess(ctx: Context<AccountContext>, number: u32) -> Result<()> {
    let guessing_account = &mut ctx.accounts.guessing_account;
    let target = guessing_account.number;

    match number.cmp(&target) {
        Ordering::Less => return err!(MyError::NumberTooSmall),
        Ordering::Greater => {
            return err!(MyError::NumberTooLarge);
        }
        Ordering::Equal => return Ok(()),
    }
}

#[error_code]
pub enum MyError {
    #[msg("Too small")]
    NumberTooSmall,
    #[msg("Too largest")]
    NumberTooLarge,
}

完整代码

use anchor_lang::prelude::*;
use anchor_lang::solana_program::clock::Clock;
use anchor_lang::solana_program::sysvar::Sysvar;

declare_id!("DscJp1fdxHqWKhAu8zhkBiUhhbAmG8BU5QcjRyALcSgL");

#[program]
pub mod sol_guess {
    use super::*;
    use std::cmp::Ordering;

    pub fn initialize(ctx: Context<AccountContext>) -> Result<()> {
        let guessing_account = &mut ctx.accounts.guessing_account;
        guessing_account.number = generate_random_number();

        Ok(())
    }

    pub fn guess(ctx: Context<AccountContext>, number: u32) -> Result<()> {
        let guessing_account = &mut ctx.accounts.guessing_account;
        let target = guessing_account.number;

        match number.cmp(&target) {
            Ordering::Less => return err!(MyError::NumberTooSmall),
            Ordering::Greater => {
                return err!(MyError::NumberTooLarge);
            }
            Ordering::Equal => return Ok(()),
        }
    }
}

fn generate_random_number() -> u32 {
    let clock = Clock::get().expect("Failed to get clock");
    let last_digit = (clock.unix_timestamp % 10) as u8;
    let result = (last_digit + 1) as u32;
    result
}

#[account]
pub struct GuessingAccount {
    pub number: u32,
}

#[derive(Accounts)]
pub struct AccountContext<'info> {
    #[account(
        init_if_needed,
        space=8+4,
        payer=payer,
        seeds = [b"guessing pda"],
        bump
    )]
    pub guessing_account: Account<'info, GuessingAccount>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[error_code]
pub enum MyError {
    #[msg("Too small")]
    NumberTooSmall,
    #[msg("Too largest")]
    NumberTooLarge,
}

将游戏部署到 Solana 本地测试网络

启动本地节点

solana-test-validator
--faucet-sol argument ignored, ledger already exists
Ledger location: test-ledger
Log: test-ledger/validator.log
⠠ Initializing...                                                                                                                           Waiting for fees to stabilize 1...
Identity: 2WTRcDAP2KYRwMLYHowYEeCqRcx1VHbW6ZmUzAGASt54
Genesis Hash: 6nsaMUBm8mQGy9XRNnd8Vjkr7eiQhivnVdSsLYQfDn2j
Version: 2.2.3
Shred Version: 50437
Gossip Address: 127.0.0.1:1024
TPU Address: 127.0.0.1:1027
JSON RPC URL: http://127.0.0.1:8899
WebSocket PubSub URL: ws://127.0.0.1:8900
⠚ 00:00:43 | Processed Slot: 584800 | Confirmed Slot: 584800 | Finalized Slot: 584800 | Full Snapshot Slot: 584800 | Incremental Snapshot Slot                                                                                                                                          

deploy program

sol-guess on  master [?] via ⬢ v22.1.0 via 🦀 1.85.1 via 🅒 base 
➜ anchor deploy   

Deploying cluster: http://127.0.0.1:8899
Upgrade authority: /Users/qiaopengjun/.config/solana/id.json
Deploying program "sol_guess"...
Program path: /Users/qiaopengjun/Code/solana-code/2025/SolanaSandbox/sol-guess/target/deploy/sol_guess.so...
Program Id: DscJp1fdxHqWKhAu8zhkBiUhhbAmG8BU5QcjRyALcSgL

Signature: 3FTdGZU8U2Ux2ofQPmprpx68vbDzF1BSpNwRuErDBXDAdmTytNwAVv12h1nSde9oG8VG7y6hCVrVj6UMyDXAjf96

Deploy success

问题解决

# 问题一
sol-guess on  master [?] via ⬢ v22.1.0 via 🦀 1.85.1 via 🅒 base 
➜ anchor build
error: not a file: '/Users/qiaopengjun/.local/share/solana/install/releases/stable-a5744e79a3d121364f937673d855c4fe3103a36c/solana-release/bin/sdk/sbf/dependencies/platform-tools/rust/bin/rustc'
# 解决一
anchor_counter on  master [?] via ⬢ v22.1.0 via 🦀 1.85.1 via 🅒 base 
➜ rm -rf ~/.cache/solana/*
zsh: sure you want to delete all 6 files in /Users/qiaopengjun/.cache/solana [yn]? y

# 问题二
sol-guess on  master [?] via ⬢ v22.1.0 via 🦀 1.85.1 via 🅒 base 
➜ anchor build
error: rustc 1.79.0-dev is not supported by the following package:

                 Note that this is the rustc version that ships with Solana tools and not your system's rustc version. Use `solana-install update` or head over to https://docs.solanalabs.com/cli/install to install a newer version.
  bytemuck_derive@1.9.2 requires rustc 1.84
Either upgrade rustc or select compatible dependency versions with
`cargo update <name>@<current-ver> --precise <compatible-ver>`
where `<compatible-ver>` is the latest version supporting rustc 1.79.0-dev

# 解决二
第一步:在 cargo.toml 中添加 bytemuck_derive = "=1.8.1"
第二步:清理缓存
cargo clean
rm -rf ~/.cargo/registry
重新 anchor build 即可

# 问题三
my-program on  master [?] via ⬢ v22.1.0 via 🦀 1.85.1 via 🅒 base took 11.5s 
➜ anchor deploy
Deploying cluster: https://api.devnet.solana.com
Upgrade authority: /Users/qiaopengjun/.config/solana/id.json
Deploying program "my_program"...
Program path: /Users/qiaopengjun/Code/solana-code/2025/SolanaSandbox/my-program/target/deploy/my_program.so...
Error: Unable to open program file: No such file or directory (os error 2)
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

# 解决三
cargo build-sbf --manifest-path=./Cargo.toml --sbf-out-dir=target/deploy

# 问题四
sol-guess on  master [?] via ⬢ v22.1.0 via 🦀 1.85.1 via 🅒 base 
➜ anchor deploy               

Deploying cluster: http://127.0.0.1:8899
Upgrade authority: /Users/qiaopengjun/.config/solana/id.json
Deploying program "sol_guess"...
Program path: /Users/qiaopengjun/Code/solana-code/2025/SolanaSandbox/sol-guess/target/deploy/sol_guess.so...
Error: No new programs can be deployed on loader-v3. Please use the program-v4 subcommand instead.
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

# 解决四
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
downloading stable installer
  ✨ stable commit a5744e7 initialized
  
 solana --version
solana-cli 2.1.16 (src:a5744e79; feat:3271415109, client:Agave)

总结

通过这场 Web3 开发实战,我们从零开始,利用 Anchor 框架在 Solana 上成功打造了一个猜数游戏。从环境搭建到代码实现,再到本地部署,每一步都展示了 Anchor 的强大与 Solana 的高效。无论你是 Web3 新手还是进阶开发者,这个案例都为你提供了一个可复制的模板,让你轻松迈入区块链开发的实战领域。未来,你可以基于此扩展更多创意功能,比如排行榜或代币奖励,在 Web3 世界中大展身手。动手试试吧,你的下一个链上杰作就在眼前!

参考

  • 原创
  • 学分: 16
  • 分类: Solana
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
该文章收录于 Solana
1 订阅 8 篇文章

0 条评论

请先 登录 后评论