Skip to content

SOL #2:Solana Transaction(交易)

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


交易流程

sequenceDiagram
    participant Dapp as Dapp
    participant Wallet as 钱包
    participant RPC as RPC 节点
    participant Leader as 当前 Leader
    participant Net as 验证者网络

    Dapp->>Dapp: 创建交易(含指令)
    Dapp->>Wallet: 传递交易信息
    Wallet->>Wallet: 用私钥签名
    Wallet-->>Dapp: 返回已签名交易
    Dapp->>RPC: sendTransaction API
    RPC->>Leader: UDP 发送给当前+下一个 Leader
    Leader->>Leader: 验证签名 → 执行 → 出块
    Leader->>Net: 广播区块
    Net-->>Dapp: 交易确认 ✅

交易大小限制

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

交易数据结构

graph TB
    TX["📝 Solana Transaction"]
    TX --> SIG["signatures<br/>签名数组"]
    TX --> MSG["message 消息体"]

    MSG --> HDR["header<br/>权限定义"]
    MSG --> KEYS["account_keys<br/>所有涉及的账户"]
    MSG --> HASH["recent_blockhash<br/>近期区块哈希"]
    MSG --> INST["instructions<br/>指令列表"]

    HDR --> H1["num_required_signatures"]
    HDR --> H2["num_readonly_signed_accounts"]
    HDR --> H3["num_readonly_unsigned_accounts"]

    INST --> I1["program_id<br/>(索引引用)"]
    INST --> I2["accounts<br/>(索引列表)"]
    INST --> I3["data<br/>(参数数据)"]

实际示例: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计算资源分配和优先费用

手续费

费用组成

graph TB
    FEE["💰 Solana 手续费"]

    FEE --> BASE["基础手续费<br/>每个签名固定 5,000 lamports<br/>(0.000005 SOL)"]
    FEE --> PRIORITY["优先手续费(可选)<br/>CU × 单价"]

    BASE --> BD["50% → 验证者<br/>50% → 销毁"]
    PRIORITY --> PD["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 的核心创新之一——并行智能合约运行时

graph TB
    subgraph "EVM(串行)"
        ET1["交易1"] --> ET2["交易2"] --> ET3["交易3"]
        ET3 --> SLOW["⏰ 逐个执行"]
    end

    subgraph "SVM / Sealevel(并行)"
        direction LR
        ST1["交易1<br/>操作账户 A"]
        ST2["交易2<br/>操作账户 B"]
        ST3["交易3<br/>操作账户 C"]

        ST1 --> CORE1["CPU 核心 1"]
        ST2 --> CORE2["CPU 核心 2"]
        ST3 --> CORE3["CPU 核心 3"]

        CORE1 --> FAST["⚡ 同时执行"]
        CORE2 --> FAST
        CORE3 --> FAST
    end

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

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

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/


学习资源