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: 交易确认 ✅
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/>(参数数据)"]
{
"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 替代 recent_blockhash:
// 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...1111 | SOL 质押与委托 |
| 投票程序 | 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% → 验证者"]
优先费 = 计算单元限制(CU) × 计算单元价格(μ-lamport/单位)设置代码:
// 设置单价(放在交易指令最前面)
const unitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: unitPrice,
});
// 设置上限
const unitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: unitLimit,
});
// 必须放在最前面
newArr.unshift(unitLimitIx);
newArr.unshift(unitPriceIx);优先手续费仅在线程内起作用,不能保证跨线程的优先级。当前调度器有 4 个执行核心,每个线程独立排队。更高的优先费只是"更有可能"被包含在区块中,不是绝对保证。
如果 nonceAdvance 和小费指令都存在,推荐顺序:
[小费 → nonceAdvance → 业务逻辑]
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 是基于 Rust 的 Solana 开发框架,简化合约开发。
anchor init my_project # 创建新项目(下蛇形命名)
anchor build # 构建合约
anchor test # 运行测试
anchor deploy # 部署到指定网络引入依赖
定义常量
#[program]
pub mod 模块名 {
定义指令方法
定义账户 struct(签名者、付费者、PDA 等)
定义 PDA 账户的存储结构 struct
}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/