Solana[part2]_Solana开发入门

Solana[part2]_Solana开发入门
SoniaChenSolana[part2]_Solana开发入门
虚拟机
Solana的执行环境与其他区块链存在显著差异,其虚拟机架构是实现高吞吐量的核心基础之一:
- EVM(Ethereum Virtual Machine):以太坊等区块链采用的虚拟机,基于栈式架构,Solana不直接支持EVM,但可通过跨链桥或兼容层(如Neon EVM)实现EVM合约迁移。
- WASM(WebAssembly):通用二进制指令格式,Solana早期曾考虑采用,但最终选择了更轻量的方案。
- Sealevel VM:Solana原生虚拟机,支持并行执行(区别于EVM的串行执行),是Solana高TPS(每秒交易数)的关键,可同时处理数千个独立合约调用。
- BPF扩展指令集:Sealevel VM基于BPF(Berkeley Packet Filter)扩展,具有高效、安全、低资源占用的特点,适合区块链场景的沙箱执行环境。
项目搭建
1. Native Rust开发(原生开发方式)
直接使用Rust编写Solana合约,适合深入理解底层机制。
环境准备
- 安装Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- 安装Solana CLI:
sh -c "$(curl -sSfL https://release.solana.com/v1.18.4/install)"
(版本号可按需更新) - 配置网络:
solana config set --url https://api.devnet.solana.com
(使用devnet测试网) - 获取测试网SOL(用于支付gas):
solana airdrop 1
(每次最多1 SOL,不足可重复申请)
步骤详解
-
创建项目
cargo new --lib native-rust && cd native-rust cargo add solana-program # 添加Solana合约核心依赖
-
修改编译配置
编辑Cargo.toml
,指定输出为动态链接库(Solana合约要求):# 顶部添加(解决edition2024兼容问题) cargo-features = ["edition2024"] [package] name = "native-rust" version = "0.1.0" edition = "2024" # 需与cargo-features对应 [lib] crate-type = ["cdylib", "lib"] # 输出为动态链接库 [dependencies] solana-program = "1.18.0" # 建议指定明确版本
-
编写基础合约
编辑src/lib.rs
,实现合约入口函数(Solana合约必须包含entrypoint!
宏定义的入口):use solana_program::{ entrypoint, account_info::AccountInfo, entrypoint::ProgramResult, msg, // 用于日志输出(链上可见) pubkey::Pubkey, }; // 定义入口函数 entrypoint!(process_instruction); /// 合约核心逻辑 /// 参数说明: /// - program_id: 合约本身的公钥 /// - accounts: 本次交互涉及的账户列表 /// - instruction_data: 传入的指令数据(二进制) fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { // 链上日志(可通过solana logs查看) msg!("Hello Solana!"); msg!("Program ID: {}", program_id); msg!("Accounts involved: {:?}", accounts.len()); msg!("Instruction data length: {}", instruction_data.len()); Ok(()) // 返回成功结果 }
-
构建合约
使用Solana的BPF编译器编译(生成.so
文件):cargo build-sbf # 编译为Solana兼容的BPF格式
编译成功后,输出文件路径:
target/sbpf-solana-solana/release/native_rust.so
-
部署与验证
-
部署到测试网:
solana program deploy target/sbpf-solana-solana/release/native_rust.so
部署成功后会返回
Program Id: <YOUR_PROGRAM_ID>
(记录此ID用于后续交互)。 -
验证部署:
solana program show <YOUR_PROGRAM_ID> # 查看合约状态
-
-
合约管理
- 升级合约(需提前配置升级权限):
solana program upgrade <new_so_file> <YOUR_PROGRAM_ID> --upgrade-authority <AUTHORITY_KEYPAIR>
- 关闭合约(回收存储空间,需合约所有者权限):
solana program close <YOUR_PROGRAM_ID>
- 升级合约(需提前配置升级权限):
常见问题
- 编译报错
feature edition2024 is required
:确保Cargo.toml
顶部添加了cargo-features = ["edition2024"]
。 - 部署失败
Insufficient funds
:通过solana airdrop 1
获取更多测试网SOL。 - 日志无法查看:使用
solana logs --url https://api.devnet.solana.com
监听链上日志。
2. Playground(在线开发工具)
适合快速原型开发,无需本地环境配置,推荐新手入门使用。
操作步骤
- 访问在线IDE:Solana Playground
- 点击
New Project
→ 选择Solana Program
,自动生成基础框架。 - 编辑
src/lib.rs
(同Native方式的合约逻辑)。 - 连接钱包:点击左侧
Build & Deploy
→Connect Wallet
(推荐使用Phantom钱包,切换到devnet)。 - 构建部署:点击
Build
编译,成功后点击Deploy
,等待部署完成(需支付少量gas)。 - 查看部署详情:部署成功后,左下角会显示
Program ID
,点击链接可在Solana Explorer中查看合约信息。
交互示例(客户端)
通过自动生成的client.ts
(TypeScript)与合约交互:
import { Connection, PublicKey, Transaction, SystemProgram } from "@solana/web3.js";
import { sendTransaction } from "./utils/sendTransaction";
// 合约地址(替换为你的Program ID)
const PROGRAM_ID = new PublicKey("YOUR_PROGRAM_ID");
async function main() {
const connection = new Connection("https://api.devnet.solana.com");
const payer = (await window.solana.connect()).publicKey;
// 构建交易
const transaction = new Transaction().add({
programId: PROGRAM_ID,
keys: [{ pubkey: payer, isSigner: true, isWritable: false }], // 传入 payer 账户
data: Buffer.from([]), // 空指令数据
});
// 发送交易
const signature = await sendTransaction(transaction, connection);
console.log("Transaction signature:", signature);
console.log("View on Explorer: https://explorer.solana.com/tx/" + signature + "?cluster=devnet");
}
main();
点击Run
执行客户端代码,在控制台查看交易结果。
3. Anchor(开发框架)
Anchor是Solana生态最流行的开发框架,简化了合约编写、测试和部署流程,内置类型安全和序列化工具。
环境安装
# 安装Anchor CLI
cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked
# 验证安装
anchor --version # 需显示0.29.0+版本
# 安装yarn
npm install -g yarn
或者
brew install yarn # 这个会默认系统配置
项目操作
-
创建项目
anchor init anchor-project && cd anchor-project
项目结构:
anchor-project/ ├── programs/ # 合约代码(Rust) │ └── anchor_project/ ├── tests/ # 测试代码(TypeScript) ├── migrations/ # 部署脚本 ├── Anchor.toml # 项目配置(网络、合约地址等) └── Cargo.toml # 依赖管理
-
编写合约(以计数器为例)
编辑programs/anchor_project/src/lib.rs
:use anchor_lang::prelude::*; // 声明程序ID(需与Anchor.toml中一致) declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program] pub mod anchor_project { use super::*; // 初始化计数器 pub fn initialize(ctx: Context<Initialize>) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count = 0; // 初始值为0 msg!("Counter initialized! Current count: {}", counter.count); Ok(()) } // 增加计数 pub fn increment(ctx: Context<Increment>) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count += 1; msg!("Count incremented! New count: {}", counter.count); Ok(()) } } // 初始化时的账户约束 #[derive(Accounts)] pub struct Initialize<'info> { // 计数器账户(需要存储数据,因此is_writable=true) #[account(init, payer = user, space = 8 + 8)] // 8字节 discriminator + 8字节u64 pub counter: Account<'info, Counter>, // 支付者(签名者) #[account(mut)] pub user: Signer<'info>, // 系统程序(用于创建账户) pub system_program: Program<'info, System>, } // 增加计数时的账户约束 #[derive(Accounts)] pub struct Increment<'info> { #[account(mut)] // 可写(需要修改count) pub counter: Account<'info, Counter>, } // 计数器数据结构 #[account] pub struct Counter { pub count: u64, }
-
配置网络
编辑Anchor.toml
,指定部署网络:[provider] cluster = "devnet" # 可选:localnet, testnet, mainnet-beta wallet = "~/.config/solana/id.json" # 本地钱包路径 [programs.devnet] anchor_project = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" # 程序ID(需与合约中一致)
-
测试合约
编辑tests/anchor_project.ts
编写测试用例:import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { AnchorProject } from "../target/types/anchor_project"; import { expect } from "chai"; describe("anchor-project", () => { // 配置Anchor provider和program const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); const program = anchor.workspace.AnchorProject as Program<AnchorProject>; // 为每个测试生成一个新的账户密钥对 let counterKeypair: anchor.web3.Keypair; beforeEach(() => { counterKeypair = anchor.web3.Keypair.generate(); }); it("Initialize counter", async () => { // 调用initialize方法 const tx = await program.methods.initialize() .accounts({ counter: counterKeypair.publicKey, user: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .signers([counterKeypair]) .rpc(); console.log("Your transaction signature", tx); // 获取counter账户并验证初始值 const counter = await program.account.counter.fetch(counterKeypair.publicKey); expect(counter.count.toNumber()).to.equal(0); console.log("Initial count:", counter.count.toString()); }); it("Increment counter", async () => { // 首先初始化账户 await program.methods.initialize() .accounts({ counter: counterKeypair.publicKey, user: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .signers([counterKeypair]) .rpc(); // 调用increment方法 const tx = await program.methods.increment() .accounts({ counter: counterKeypair.publicKey }) .rpc(); console.log("Your transaction signature", tx); // 获取counter账户并验证计数增加 const counter = await program.account.counter.fetch(counterKeypair.publicKey); expect(counter.count.toNumber()).to.equal(1); console.log("After increment:", counter.count.toString()); }); it("Increment counter multiple times", async () => { // 首先初始化账户 await program.methods.initialize() .accounts({ counter: counterKeypair.publicKey, user: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .signers([counterKeypair]) .rpc(); // 多次调用increment方法 await program.methods.increment() .accounts({ counter: counterKeypair.publicKey }) .rpc(); await program.methods.increment() .accounts({ counter: counterKeypair.publicKey }) .rpc(); await program.methods.increment() .accounts({ counter: counterKeypair.publicKey }) .rpc(); // 获取counter账户并验证计数 const counter = await program.account.counter.fetch(counterKeypair.publicKey); expect(counter.count.toNumber()).to.equal(3); console.log("After 3 increments:", counter.count.toString()); }); });
运行测试:
anchor test # 自动启动本地节点,运行测试后关闭
测试完成:
anchor-project Your transaction signature 3DBSMCVTW65CDsuPLx4jB3CosozZSjQbCurXo2KcfMSkfr2nZRHz5XQuEzEXr56bf1qY2MVsoYuQsYePwAuLLY4F Initial count: 0 ✔ Initialize counter (592ms) Your transaction signature 36wPyPxho2rGRiRxM2ihF2cvfKENbNFKzNEr3ida4mEVeiRSmnEMbKqeg7BuTWdiDeKzd7dktn5WjbpNv4mWhNnF After increment: 1 ✔ Increment counter (915ms) After 3 increments: 3 ✔ Increment counter multiple times (1905ms) 3 passing (3s)
-
部署合约
anchor deploy # 部署到Anchor.toml指定的网络 ------------- ➜ anchor-project anchor deploy Error: error sending request for url (http://127.0.0.1:8899/): error trying to connect: tcp connect error: Connection refused (os error 61) Caused by: 0: error sending request for url (http://127.0.0.1:8899/): error trying to connect: tcp connect error: Connection refused (os error 61) 1: error trying to connect: tcp connect error: Connection refused (os error 61) 2: tcp connect error: Connection refused (os error 61) 3: Connection refused (os error 61)
部署失败,发现没有切换 devnet
[provider] cluster = "devnet" # 可选:localnet, testnet, mainnet-beta wallet = "~/.config/solana/id.json" # 本地钱包路径
部署成功后,会返回
Program Id: <DEPLOYED_PROGRAM_ID>
,可在Solana Explorer中查看。➜ anchor-project anchor deploy Deploying cluster: https://api.devnet.solana.com Upgrade authority: /Users/tinachan/.config/solana/id.json Deploying program "anchor_project"... Program path: /Users/tinachan/rust/anchor-project/target/deploy/anchor_project.so... Program Id: HDvugLuzT7XtdaskGkYw9DJZqNLtDLeVXfMeET9bne9y Signature: 4ArrCsQQu4zCU7455yeoo3L5LvTiWngXq3gMCHoA42Z25tSHgMrVKPiQd4yhSvu37n28cwWPysrDiMAMfA21BNfF Deploy success
优势总结
- 自动处理账户序列化/反序列化(无需手动处理二进制数据)。
- 内置测试框架,支持TypeScript测试合约。
- 简化账户权限管理(通过
#[derive(Accounts)]
约束)。 - 自动生成客户端SDK(
target/types/
目录下),方便前端集成。
踩坑
- 程序ID不匹配问题:
- Rust代码中的[declare_id!](javascript:void(0))与Anchor.toml中的程序ID不一致
- 我们将Rust代码中的程序ID从
Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS
更新为5vhtLSUBXoqe3a2Kxtx8tcNdTNTaXaSgFT7MGN1bWxz5
,与Anchor.toml保持一致
- 测试环境配置问题:
- 将Anchor.toml中的[cluster](javascript:void(0))从
devnet
改为localnet
,这样测试可以在本地运行而不需要连接到Devnet
- 将Anchor.toml中的[cluster](javascript:void(0))从
- 断言比较问题:
- Solana程序返回的数字是BN(BigNumber)类型,而我们之前直接与JavaScript数字比较
- 通过使用
.toNumber()
方法将BN类型转换为普通数字进行比较,解决了断言失败问题
- 依赖安装:
- 确保安装了chai依赖以支持测试断言
npm install chai
- 确保安装了chai依赖以支持测试断言
总结
- Native Rust:适合深入理解Solana底层,灵活性高但开发效率低。
- Playground:适合快速验证想法,无需配置环境,适合新手入门。
- Anchor:生产环境首选,大幅提升开发效率,生态工具丰富。
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果