Solana

2026年01月19日更新 17 人订阅
原价: ¥ 8.6 限时优惠
专栏简介 Solana 开发学习之Solana 基础知识 Solana 开发学习之通过RPC与Solana交互 Web3与Solana实操指南:如何签名与发送交易 Web3 新玩法:Solana Nonce Account 让你交易无忧 Web3 快上手:Solana 造你的链上名片 Web3 开发实战:用 Anchor 打造 Solana 猜数游戏 @solana/web3.js 2.0:Solana 转账全流程解析 玩转 Web3 Solana:从零到代币开发 Web3 开发入门:Solana CLI 配置与本地验证器实战 Web3 Eclipse 开发环境搭建与资产跨链桥接实战指南 用 Gill 库解锁 Web3:Solana 代币转账实战 Web3开发:用Rust实现Solana SOL转账教程 用 Rust 开发 Solana:解锁 Web3 交易费用计算 Web3开发入门:Solana账户创建与Rust实践全攻略 Web3 实战:用 Anchor 打造 Solana 智能合约全流程 Solana Web3 快速入门:创建并获取钱包账户的完整指南 Web3 开发实操:用 Anchor 在 Solana 创建代币 Mint Account 从零到 Web3:使用 @solana/kit 快速查询 Solana 账户余额 快速上手 Web3:用 @solana/kit 在 Solana 上创建钱包并查询余额 Web3实战:使用Anchor与Rust开发和调用Solana智能合约 Web3实战:Solana CPI全解析,从Anchor封装到PDA转账 用 Rust 在 Solana 上打造你的专属代币:从零到一的 Web3 实践 探索Solana SDK实战:Web3开发的双路径与轻量模块化 手把手教你用 Solana Token-2022 创建支持元数据的区块链代币 Solana 开发实战:Rust 客户端调用链上程序全流程 Solana 开发进阶:在 Devnet 上实现链上程序部署、调用与更新 Solana 开发进阶:链上事件到链下解析全攻略 从零打造Solana空投工具库:Rust开发实战指南 从零开始:用 Rust 开发 Solana 链上 Token 元数据查询工具 Solana 智能合约终极部署指南:从入门到主网,定制你的专属靓号 Program ID 【Solana 开发实战】轻松搞定链上 IDL:从上传到获取全解析 Solana 投票 DApp 开发实战:从合约到部署的完整指南 Surfpool:Solana 上的 Anvil,本地开发闪电般⚡️ 【Solana实操】64字节私钥文件解析难题:用三种姿势安全获取钱包地址 Solana 密钥实战:一文搞懂私钥、公钥、PDA 的底层关系与 CLI 操作 Solana 地址进阶:从 TS/JS 到 Rust SDK V3,完全掌握公钥与 PDA 的底层逻辑 Solana 开发者笔记:PDA 与账户操作的10个关键要点 拒绝“版本代差”:基于 Solana SDK V3 的「链上动态存储器」工业级实现

拒绝“版本代差”:基于 Solana SDK V3 的「链上动态存储器」工业级实现

拒绝“版本代差”:基于SolanaSDKV3的「链上动态存储器」工业级实现在Solana生态快速更迭的今天,开发者面临最大的技术风险在于“代码版本代差”。目前中文社区多数教程仍停留在SDKv1.x阶段,导致开发者在处理账户扩容与指针逻辑时,往往采用过时且高风险的实现方式。本文将

拒绝“版本代差”:基于 Solana SDK V3 的「链上动态存储器」工业级实现

在 Solana 生态快速更迭的今天,开发者面临最大的技术风险在于“代码版本代差”。目前中文社区多数教程仍停留在 SDK v1.x 阶段,导致开发者在处理账户扩容与指针逻辑时,往往采用过时且高风险的实现方式。

本文将跳过基础的静态示例,直接切入工业级动态存储方案。我们将利用 SDK V3 提供的 AccountInfo::resize 与标准 CPI 接口,构建一个能够随数据量变化而自动调整空间及租金的智能合约,这才是适配现代 Solana 应用(如游戏存档、动态元数据存储)的标准实践。

假设你正在开发一个去中心化应用,需要让用户在链上存储数据——可能是游戏存档、用户配置、文档哈希或任何需要持久化的信息。这个数据应该:

  • 属于用户本人:其他人无法覆盖或篡改。
  • 支持随时更新:数据长度可以变化(字符串变长或变短)。
  • 按需付费:不浪费存储空间和租金。

我们要构建的「链上数据存储器」正是为了满足这些需求。每个用户拥有一个专属的数据账户,可以自由地写入和更新数据。

功能设计

程序提供两个核心功能(指令):

1. 初始化数据账户 (Initialize)

用户首次使用时,程序会为其创建一个 PDA (Program Derived Address) 作为数据存储账户。

  • 寻址:使用 [User_PublicKey, "storage"] 作为种子,确保每个用户有且仅有一个对应的存储账户。
  • 租金:系统根据初始数据的长度,自动计算所需的 Lamports,并从用户钱包扣除,存入该 PDA 以达成租赁豁免。

2. 更新数据内容 (Update)

  • 这是 SDK V3 的精髓所在。程序利用 resize 功能动态调整账户大小:
    • 扩容 (Resize):新数据更长,程序计算差额并要求用户补交租金。
    • 缩容 (Refund):新数据更短,程序释放空间并将多余租金退还用户。

原理提示:

  • 扩容补钱:必须通过 System Program Transfer(需要用户签名)。

  • 缩容退钱:可直接修改 lamports 余额(因为 PDA 的所有者是本程序)。

实操

初始化 Rust 项目

cargo new --lib solana-storage
cd solana-storage

# 实操
cargo new --lib solana-storage
    Creating library `solana-storage` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

cd solana-storage

查看项目目录结构

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ tree . -L 6 -I "docs|target|node_modules|build"
.
├── Cargo.lock
├── Cargo.toml
├── rust-toolchain.toml
└── src
    └── lib.rs

2 directories, 4 files

🛠 开发环境与配置

Cargo.toml 文件

现代化的 Cargo.toml

注意:我们启用了 edition = "2024" 以及 Solana SDK 3.0 系列组件。

cargo-features = ["edition2024"]

[package]
name = "solana-storage"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib", "lib"]

[dependencies]
solana-cpi = "3.1.0"
solana-program = "3.0.0"
solana-system-interface = { version = "3.0", features = ["bincode"] }

# 允许 Solana 特定的 cfg 值,避免编译警告
[lints.rust]
unexpected_cfgs = { level = "allow" }

关键配置说明:

  • crate-type = ["cdylib", "lib"]:
    • cdylib: 生成 C 兼容的动态库(.so 文件)。这是部署到 Solana BPF 虚拟机所必需的格式。
    • lib: 生成标准的 Rust 库(.rlib 文件)。这方便你在本地编写单元测试和集成测试,无需每次都部署到链上。
  • solana-program: 这是 Solana 开发的核心标准库,提供了账户信息、公钥、程序结果等基础类型的定义。

💻 核心逻辑实现 (lib.rs)

lib.rs 文件

这份代码展示了 V3 标准下处理账户伸缩的最佳实践:

use solana_program::{
    account_info::{AccountInfo, next_account_info},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program_error::ProgramError,
    pubkey::Pubkey,
    rent::Rent,
    sysvar::Sysvar,
};

use solana_cpi::{invoke, invoke_signed};
use solana_system_interface::instruction::{create_account, transfer};

// 1. 定义程序入口点
entrypoint!(process_instruction);

#[allow(unused_variables)]
// 2. 处理指令的核心逻辑
pub fn process_instruction(
    program_id: &Pubkey,      // 这个程序自己的 ID
    accounts: &[AccountInfo], // 交易涉及的所有账户
    data: &[u8],              // 传递给程序的参数(字节数组)
) -> ProgramResult {
    msg!("Hello Solana! program_id: {:?}", program_id);

    // 1. 账户提取
    let accounts_iter = &mut accounts.iter(); // 钱包
    // 1.1 付款人 (必须签名)
    let account_user = next_account_info(accounts_iter)?;
    if !account_user.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // 1.2. 数据账户 (PDA)
    let account_data = next_account_info(accounts_iter)?; // PDA 数据账户

    // 1.3. 系统程序
    let system_program = next_account_info(accounts_iter)?; // 系统程序

    // 2. 准备工作:计算租金和 PDA 种子
    // 计算租金
    let rent_exemption = Rent::get()?.minimum_balance(data.len());

    // 派生 PDA
    let (pda_key, bump_seed) =
        Pubkey::find_program_address(&[account_user.key.as_ref()], program_id);
    if pda_key != *account_data.key {
        msg!("错误: PDA 地址不匹配");
        return Err(ProgramError::InvalidAccountData);
    }

    // 3. 分支逻辑 A:如果账户不存在 (余额为0),则创建
    // 只有当账户为空时才创建
    if account_data.lamports() == 0 {
        msg!("分支 A: 创建新 PDA 账户");
        // CPI 调用
       // 这是因为付款人是用户(非程序所能控制),所以必须通过 System Program 进行正式转账;而“退钱”可以直接修改 Lamports 是因为 PDA 的所有权属于本程序。
        invoke_signed(
            &create_account(
                account_user.key,
                account_data.key,
                rent_exemption,    // 初始租金
                data.len() as u64, // 初始空间
                program_id,
            ),
            &[
                account_user.clone(),
                account_data.clone(),
                system_program.clone(),
            ],
            &[&[account_user.key.as_ref(), &[bump_seed]]], // 签名种子:证明我是 PDA 的主人
        )?;
    } else {
        msg!("分支 B: 更新现有账户并调整空间");

        // 安全检查:只有该程序拥有的账户才能 resize
        if account_data.owner != program_id {
            return Err(ProgramError::IllegalOwner);
        }

        // 步骤 1: 物理扩容/缩容 (SDK v3 重要操作)
        account_data.resize(data.len())?;

        // 步骤 2: 租金平衡
        // 4. 分支逻辑 B:如果账户已存在,则更新
        let current_lamports = account_data.lamports();
        // 情况 B1: 新数据更长 -> 补交租金
        if rent_exemption > current_lamports {
            // 补钱:必须通过 System Program Transfer
            let diff = rent_exemption - current_lamports;
            invoke(
                &transfer(account_user.key, account_data.key, diff),
                &[
                    account_user.clone(),
                    account_data.clone(),
                    system_program.clone(),
                ],
            )?;
            // 情况 B2: 新数据更短 -> 退还租金
        } else if rent_exemption < current_lamports {
            // 退钱:手动调整(因为 PDA 归本程序管)
            let diff = current_lamports - rent_exemption;
            **account_data.try_borrow_mut_lamports()? -= diff;
            **account_user.try_borrow_mut_lamports()? += diff;
        }
    }

    // 5. 写入数据
    // 此时 resize 已经保证了空间足够,rent 平衡保证了免租金
    account_data.data.borrow_mut().copy_from_slice(data);
    msg!("数据写入成功,长度: {}", data.len());

    Ok(())
}

🚀 编译与部署全链路

编译构建

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ cargo update blake3 --precise 1.8.2
    Updating crates.io index
 Downgrading blake3 v1.8.3 -> v1.8.2
 Downgrading constant_time_eq v0.4.2 -> v0.3.1
note: pass `--verbose` to see 2 unchanged dependencies behind latest

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ cargo build-sbf
   Compiling solana-storage v0.1.0 (/Users/qiaopengjun/Code/Solana/solana-storage)
    Finished `release` profile [optimized] target(s) in 0.76s

核心区别对比

特性 cargo build-sbf cargo build-sbf -- -Znext-lockfile-bump
功能稳定性 Stable (稳定) Experimental (实验性)
依赖处理 遵循现有的依赖更新机制。 使用实验性的依赖版本提升(bump)逻辑。
适用人群 绝大多数开发者。 需要测试 Cargo 新特性或解决特定依赖锁定问题的核心开发者。
风险 低。 中(由于是 -Z 参数,可能在未来的 Cargo 版本中改变或消失)。

简单来说:除非你遇到了特定的依赖锁定问题,否则没必要加后面那一串。

正式编译

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ cargo build-sbf -- -Znext-lockfile-bump
    Finished `release` profile [optimized] target(s) in 0.28s

查看合约大小(Solana 合约越小,部署成本越低):

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ ls -lh target/deploy/*.so

-rwxr-xr-x@ 1 qiaopengjun  staff    79K Jan 17 20:12 target/deploy/solana_storage.so

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ wc -c < ./target/deploy/solana_storage.so 
   81176

计算此大小(字节)所需的 SOL:

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ solana rent 81176                                                                                          
Rent-exempt minimum: 0.56587584 SOL

部署程序

方案 A:Solana CLI (传统部署)

第一步:配置 Solana CLI 以使用本地 Solana 集群

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ solana address                                         
6MZDRo5v8K2NfdohdD76QNpSgk3GH3Aup53BeMaRAEpd

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ solana config get
Config File: /Users/qiaopengjun/.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com 
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: /Users/qiaopengjun/.config/solana/id.json 
Commitment: confirmed 

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ solana config set -ul
Config File: /Users/qiaopengjun/.config/solana/cli/config.yml
RPC URL: http://localhost:8899 
WebSocket URL: ws://localhost:8900/ (computed)
Keypair Path: /Users/qiaopengjun/.config/solana/id.json 
Commitment: confirmed 

第二步:启动 Solana 本地测试节点

solana-test-validator -r     
Ledger location: test-ledger
Log: test-ledger/validator.log
⠂ Initializing...                                                                                                                                          Waiting for fees to stabilize 1...
⠴ Initializing...                                                                                                                                          Waiting for fees to stabilize 2...
Identity: 6SuxsNGUsCnYahf5fi9u8n1tS6Ma924FXShcc2CQVaGU
Genesis Hash: DkFxoK6EBR4s7za1Pqbqfx8UrstxN9smJGMbFLB5m7T3
Version: 3.0.13
Shred Version: 36009
Gossip Address: 127.0.0.1:8000
TPU Address: 127.0.0.1:8003
JSON RPC URL: http://127.0.0.1:8899
WebSocket PubSub URL: ws://127.0.0.1:8900
⠈ 00:00:09 | Processed Slot: 20 | Confirmed Slot: 20 | Finalized Slot: 0 | Full Snapshot Slot: - | Incremental Snapshot Slot: - | Transactions: 19 | ◎499.9

第三步:执行本地部署

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 
➜ solana program deploy ./target/deploy/solana_storage.so 
Program Id: jNPVTP8iNmbJnXAa1KgLKwLxBkdcVvKLaMYaahiWxFU

Signature: qgWcQX1STrmH3C7yZ6yhUAKEEd7Z3E6PvMqUMdhvdR9K5utjFnjpBVnJwv16Q6maPoguc9ActhUUUehqKW4DbRY

image-20260117230633975.png

第四步:查看部署程序详细信息

solana-storage on  master [?] is 📦 0.1.0 via 🦀 1.94.0 took 3.1s 
➜ solana program show jNPVTP8iNmbJnXAa1KgLKwLxBkdcVvKLaMYaahiWxFU                                           

Program Id: jNPVTP8iNmbJnXAa1KgLKwLxBkdcVvKLaMYaahiWxFU
Owner: BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: 58rFQHe9roeHWUxNdnX3X7LuQymki3re1hXMEpdySbRP
Authority: 6MZDRo5v8K2NfdohdD76QNpSgk3GH3Aup53BeMaRAEpd
Last Deployed In Slot: 394123044
Data Length: 81176 (0x13d18) bytes
Balance: 0.56618904 SOL

方案 B:pxsol 分片部署 (推荐)

使用 Python 库 pxsol 的部署方式

第一步:实现部署脚本

# /// script
# dependencies = [
#   "pxsol",
# ]
# ///

import json
import pathlib
import pxsol

# 1. 基础配置:显式切换到开发网并指定本地 RPC 地址
pxsol.config.current = pxsol.config.develop
pxsol.config.current.rpc_url = "http://127.0.0.1:8899"
# 开启日志以便观察分片上传过程
pxsol.config.current.log = 1

# 2. 钱包加载
# 加载部署者的钱包 (需要有足够的 SOL 支付租金)
# 0x01 是示例私钥,实际请使用你的密钥文件
# ada = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(0x01))

# 1. 准确定位路径
wallet_path = pathlib.Path.home() / ".config/solana/id.json"

# 2. 读取文件并转换
if not wallet_path.exists():
    raise FileNotFoundError(
        f"找不到钱包文件: {wallet_path},请手动运行 solana-keygen new"
    )

with open(wallet_path, "r") as f:
    keypair_data = json.load(f)

# id.json 是 [私钥+公钥],pxsol 的 PriKey 构造函数只需要前 32 字节
raw_prikey = bytearray(keypair_data[:32])
ada = pxsol.wallet.Wallet(pxsol.core.PriKey(raw_prikey))

print(f"🔑 钱包已准备就绪: {ada.pubkey}")

# 读取编译好的二进制文件
# program_data = pathlib.Path("target/deploy/solana_storage.so").read_bytes()

# 获取脚本所在目录的上一级,即项目根目录
base_path = pathlib.Path(__file__).parent.parent
so_path = base_path / "target/deploy" / "solana_storage.so"

# 读取数据
print(f"📦 正在读取合约: {so_path}")
program_data = so_path.read_bytes()

# 执行部署
# 这会在后台自动处理:创建Buffer -> 分片写入 -> Finalize
print("🚀 正在发起分片部署交易(这可能需要几十秒)...")
try:
    program_pubkey = ada.program_deploy(bytearray(program_data))
    print("\n" + "=" * 30)
    print("✅ 部署成功!")
    print(f"📜 Program ID: {program_pubkey}")
    print("=" * 30)
except Exception as e:
    print(f"❌ 部署失败: {e}")
    print("💡 提示:请检查本地 solana-test-validator 是否在运行,且钱包余额是否充足。")

第...

剩余50%的内容订阅专栏后可查看

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

0 条评论

请先 登录 后评论