Skip to content

SOL #2:Solana Transaction(交易)

Solana 交易由指令构成,原子性执行。理解交易数据结构、手续费机制和并行执行引擎,是 Solana 开发的核心。


交易流程

验证者网络当前 LeaderRPC 节点钱包Dapp验证者网络当前 LeaderRPC 节点钱包Dapp创建交易(含指令)传递交易信息用私钥签名返回已签名交易sendTransaction APIUDP 发送给当前+下一个 Leader验证签名 → 执行 → 出块广播区块交易确认 ✅

交易大小限制

  • 数据包限制:1232 字节
  • 签名 64 字节 + 公钥 32 字节
  • 单笔交易最多约 12 个签名
  • 一个 Transaction 可包含多个 Instruction,按顺序执行
  • 整个交易是原子性的:要么全成功,要么全失败

交易数据结构

📝 Solana Transaction

signatures
签名数组

message 消息体

header
权限定义

account_keys
所有涉及的账户

recent_blockhash
近期区块哈希

instructions
指令列表

num_required_signatures

num_readonly_signed_accounts

num_readonly_unsigned_accounts

program_id
(索引引用)

accounts
(索引列表)

data
(参数数据)

实际示例:SOL 转账

json
{
  "signatures": ["..."],
  "message": {
    "header": {
      "num_required_signatures": 1,
      "num_readonly_signed_accounts": 0,
      "num_readonly_unsigned_accounts": 1
    },
    "account_keys": [
      "F7wnJc5wiBGy1k87jv6gyNwE3jMEWd18oTQiYsF1xVG7",  // 索引0:签名且可写(发送方)
      "4mpBfqutcvpfsF1xt6SAnqfFJcE1aGC1EjhcNRyoLSM8",  // 索引1:可写(接收方)
      "11111111111111111111111111111111"                    // 索引2:只读(System Program)
    ],
    "instructions": [
      {
        "program_id": 2,      // → account_keys[2] = System Program
        "accounts": [0, 1],   // → 发送方和接收方
        "data": "3Bxs4ThwQbE4vyj5"  // 转账指令 + 金额
      }
    ]
  }
}

关键字段说明

字段说明
account_keys所有涉及的账户地址,instructions 通过索引引用,避免重复
recent_blockhash兼具 nonce(防重放)和过期时间(约 1分19秒有效期)
instructions每个指令含 program_id(程序索引)、accounts(账户索引)、data(参数)

Durable Nonce 机制

对于需要更长有效期的场景(如离线签名),可以用 Durable Nonce 替代 recent_blockhash:

javascript
// 1. 创建 Nonce 账户
const createNonceAccountIx = SystemProgram.createNonceAccount({
  fromPubkey: payer.publicKey,
  noncePubkey: nonceAccount.publicKey,
  authorizedPubkey: authority.publicKey,
  lamports: rentExemptionAmount,
});

// 2. 使用 Nonce 构建交易
tx.recentBlockhash = nonceAccount.nonce; // 用 Nonce 代替区块哈希

// 3. 每次使用后递增 Nonce(必须放在交易第一位)
const advanceNonceIx = SystemProgram.nonceAdvance({
  noncePubkey: nonceAccount.publicKey,
  authorizedPubkey: authority.publicKey,
});

注意:nonceAdvance 指令通常要放在交易的第一位

常见系统程序

程序Program ID功能
系统程序1111...1111创建账户、转账、设置所有者
质押程序Stake1111...1111SOL 质押与委托
投票程序Vote1111...1111验证者投票和奖励
计算预算程序ComputeBudget1111...1111计算资源分配和优先费用

手续费

费用组成

💰 Solana 手续费

基础手续费
每个签名固定 5,000 lamports
(0.000005 SOL)

优先手续费(可选)
CU × 单价

50% → 验证者
50% → 销毁

100% → 验证者

基础手续费

  • 固定费用:每个签名 5,000 lamports(0.000005 SOL)
  • 无论成功与否都收取
  • 50% 给验证者,50% 被销毁

优先手续费

优先费 = 计算单元限制(CU) × 计算单元价格(μ-lamport/单位)

设置代码:

javascript
// 设置单价(放在交易指令最前面)
const unitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: unitPrice,
});
// 设置上限
const unitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: unitLimit,
});
// 必须放在最前面
newArr.unshift(unitLimitIx);
newArr.unshift(unitPriceIx);

优先费的局限性

优先手续费仅在线程内起作用,不能保证跨线程的优先级。当前调度器有 4 个执行核心,每个线程独立排队。更高的优先费只是"更有可能"被包含在区块中,不是绝对保证。

如果 nonceAdvance 和小费指令都存在,推荐顺序:[小费 → nonceAdvance → 业务逻辑]


Sealevel(并行执行引擎)

Sealevel 是 Solana 的核心创新之一——并行智能合约运行时

SVM / Sealevel(并行)

交易1
操作账户 A

CPU 核心 1

交易2
操作账户 B

CPU 核心 2

交易3
操作账户 C

CPU 核心 3

⚡ 同时执行

EVM(串行)

交易1

交易2

交易3

⏰ 逐个执行

核心原理:每个交易需提前声明将访问的账户(读/写权限),虚拟机据此调度无冲突的交易并行执行。

  • 两笔修改不同账户的交易 → 可以同时处理
  • 两笔修改同一账户的交易 → 必须串行

Sealevel 利用多核 CPU 的 SIMD 指令集AVX 扩展,将交易分配到不同核心处理。


Anchor 框架

简介

Anchor 是基于 Rust 的 Solana 开发框架,简化合约开发。

常用命令

bash
anchor init my_project    # 创建新项目(下蛇形命名)
anchor build              # 构建合约
anchor test               # 运行测试
anchor deploy             # 部署到指定网络

合约模板

rust
引入依赖
定义常量

#[program]
pub mod 模块名 {
    定义指令方法
    定义账户 struct(签名者、付费者、PDA 等)
    定义 PDA 账户的存储结构 struct
}

开发案例:存储用户偏好

rust
use anchor_lang::prelude::*;

declare_id!("4Pm9xVzVsQJMmodRdANm28UapsES4Ffy13AKeSPuEtqy");

pub const ANCHOR_DISCRIMINATOR_SIZE: usize = 8;

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

    pub fn set_favorites(
        context: Context<SetFavorites>, number: u64, color: String
    ) -> Result<()> {
        let user_public_key = context.accounts.user.key();
        msg!("User {user_public_key}'s favorite number is {number}, color is: {color}");
        context.accounts.favorites.set_inner(Favorites { number, color });
        Ok(())
    }
}

#[account]
#[derive(InitSpace)]
pub struct Favorites {
    pub number: u64,
    #[max_len(50)]
    pub color: String,
}

#[derive(Accounts)]
pub struct SetFavorites<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

    #[account(
        init_if_needed,
        payer = user,
        space = ANCHOR_DISCRIMINATOR_SIZE + Favorites::INIT_SPACE,
        seeds = [b"favorites", user.key().as_ref()],
        bump
    )]
    pub favorites: Account<'info, Favorites>,

    pub system_program: Program<'info, System>,
}

在线 IDE:https://beta.solpg.io/


学习资源