Solana - 架构总览 - Account 模型/Program/Instruction/Transaction + EVM 对比
### 一、Solana 架构总览
日期: 2026-05-03 方向: Rust/Solana 阶段: 第二阶段:框架实战 标签: #Solana #AccountModel #Program #Instruction #Transaction #EVM对比
今日目标
- 理解 Solana 独特的 Account 模型(Data Accounts vs Program Accounts)
- 掌握 Program、Instruction、Transaction 三者的关系
- 理解 Signers 和 PDA(Program Derived Address)的基本概念
- 通过 EVM 对比表深入理解 Solana 与 Ethereum 的架构差异
- 实操 Solana CLI 基础命令(keygen、devnet SOL、查询账户)
核心概念
一、Solana 架构总览
Solana 的架构与 Ethereum 有根本性差异。理解这些差异是后续 Solana 开发的基础。
Ethereum 模型:
┌──────────────────┐
│ Smart Contract │ ← 代码 + 状态 存储在同一个地方
│ ├── code │
│ ├── storage │
│ └── balance │
└──────────────────┘
Solana 模型:
┌──────────────┐ ┌──────────────┐
│ Program │ │ Data Account │
│ (只有代码) │ ←→ │ (只有数据) │
│ 不可变执行码 │ │ 可变状态 │
└──────────────┘ └──────────────┘
↑ ↑
代码与数据分离!
二、Account 模型深度解析
在 Solana 中,一切都是 Account。每个 Account 都有如下结构:
pub struct Account {
/// 这个账户有多少 lamports(SOL 的最小单位,1 SOL = 1e9 lamports)
pub lamports: u64,
/// 这个账户存储的数据(字节数组)
pub data: Vec<u8>,
/// 这个账户的"拥有者"程序
pub owner: Pubkey,
/// 这个账户是否可执行(是否是程序)
pub executable: bool,
/// 这个账户开始能使用的 epoch
pub rent_epoch: u64,
}
2.1 两种账户类型
| 特性 | Data Account(数据账户) | Program Account(程序账户) |
|---|---|---|
| executable | false | true |
| data 字段 | 存储状态数据 | 存储编译后的 BPF 字节码 |
| 可修改 data | 是(由 owner 程序修改) | 否(升级需要特殊机制) |
| 类比 EVM | Storage slots | Contract bytecode |
| 创建成本 | 需要支付 rent | 需要支付 rent + 部署费用 |
2.2 所有权模型(Ownership)
这是 Solana 最核心的安全模型之一:
规则 1: 只有账户的 owner 程序才能修改其 data
规则 2: 只有账户的 owner 程序才能扣减其 lamports
规则 3: 任何程序都可以给账户增加 lamports
规则 4: System Program 拥有所有新创建的普通账户
创建流程:
System Program → 创建账户 → 设置 owner 为你的程序
状态修改:
用户签名 → Transaction → 你的程序(owner)→ 修改 data account
2.3 Rent(租金机制)
Solana 上存储数据需要支付"租金"。如果账户余额足以覆盖 2 年租金,则 Rent Exempt(免租),数据永久存储。
Rent 计算:
rent_per_byte_per_year ≈ 0.00000348 SOL
例如存储 165 bytes 的 Token Account:
rent = 165 * 0.00000348 * 2 = ~0.00114840 SOL ≈ 0.00203928 SOL (含安全余量)
实际使用 solana CLI 查询:
solana rent 165
→ Rent per byte-year: 0.00000348 SOL
→ Rent per epoch: ...
→ Rent-exempt minimum: 0.00203928 SOL
三、Program(程序)
Solana 的 Program 就是其他链上的 "Smart Contract",但有关键区别:
// Solana Program 的入口函数签名
pub fn process_instruction(
program_id: &Pubkey, // 当前程序的 ID(地址)
accounts: &[AccountInfo], // 这条指令需要的所有账户
instruction_data: &[u8], // 指令附带的数据
) -> ProgramResult {
// 解析 instruction_data 确定要执行什么操作
// 修改 accounts 中的数据
Ok(())
}
3.1 系统内置程序
| 程序 | Program ID | 功能 |
|---|---|---|
| System Program | 11111111111111111111111111111111 | 创建账户、转 SOL |
| Token Program | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA | SPL Token 操作 |
| Token 2022 | TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb | 新版 Token 标准 |
| Associated Token | ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL | 管理 ATA |
| BPF Loader | BPFLoaderUpgradeab1e11111111111111111111111 | 部署/升级程序 |
| Sysvar | 多个地址 | 提供链上时间、费用等信息 |
3.2 Program 的关键特性
1. 无状态: Program 自身不存储数据,数据在 Data Accounts 中
2. 确定性: 同样的输入一定产生同样的输出
3. 并行执行: 不同 accounts 的 transactions 可以并行处理
(这是 Solana 高性能的关键!)
4. CPI: Cross-Program Invocation,程序之间可以互相调用
四、Instruction(指令)与 Transaction(交易)
4.1 层次结构
Transaction(交易)
├── Message
│ ├── Header(签名者数量等元信息)
│ ├── Account Keys(这笔交易涉及的所有账户地址列表)
│ ├── Recent Blockhash(防止重放)
│ └── Instructions[](指令列表)
│ ├── Instruction #1
│ │ ├── program_id_index(调用哪个程序)
│ │ ├── account_indices[](使用 keys 中的哪些账户)
│ │ └── data(传给程序的数据)
│ ├── Instruction #2
│ │ └── ...
│ └── ...
└── Signatures[](所有签名者的签名)
4.2 Instruction 详解
每个 Instruction 包含三部分:
pub struct Instruction {
/// 要调用的程序地址
pub program_id: Pubkey,
/// 这条指令需要的账户列表及其权限
pub accounts: Vec<AccountMeta>,
/// 传给程序的数据(编码后的参数)
pub data: Vec<u8>,
}
pub struct AccountMeta {
pub pubkey: Pubkey,
pub is_signer: bool, // 是否需要签名
pub is_writable: bool, // 是否可写
}
4.3 一个实际的 Transaction 例子
场景: Alice 向 Bob 转 1 SOL
Transaction:
Signatures: [Alice 的签名]
Message:
Account Keys: [Alice, Bob, System Program]
Instructions:
- Instruction #0:
program_id: System Program
accounts: [
{ pubkey: Alice, is_signer: true, is_writable: true },
{ pubkey: Bob, is_signer: false, is_writable: true }
]
data: Transfer { lamports: 1_000_000_000 }
4.4 多指令 Transaction(原子性)
这是 Solana 的一个巨大优势——一个 Transaction 可以包含多条 Instructions,它们原子执行:
场景: 在 DEX 中先 approve 再 swap(在 Ethereum 需要两笔交易)
Solana Transaction:
Instructions:
#1: Token Program → approve(user, dex, 1000 USDC)
#2: DEX Program → swap(USDC → SOL)
→ 两条指令原子执行:要么都成功,要么都失败
→ 不存在 "approve 了但 swap 失败" 的状态
五、Signers 与 PDA
5.1 Signers(签名者)
在 Transaction 中标记为 is_signer: true 的账户必须在 Signatures 中提供签名。这保证了只有私钥持有者才能授权操作。
5.2 PDA(Program Derived Address)初探
PDA 是 Solana 中没有私钥的地址,由程序控制:
// PDA 的生成
let (pda, bump) = Pubkey::find_program_address(
&[b"vault", user.key.as_ref()], // seeds
program_id, // 程序 ID
);
PDA 的特点:
1. 不在 ed25519 曲线上 → 没有对应的私钥
2. 只有生成它的程序可以"代签" → 程序控制的账户
3. 由 seeds + program_id 确定性生成 → 可重复查找
4. bump seed 保证地址不在曲线上
用例:
- Vault 金库账户(程序控制的资金池)
- 用户的程序专属状态账户
- 权限管理(PDA 作为 authority)
六、EVM vs Solana 对比表
| 维度 | Ethereum (EVM) | Solana (SVM) |
|---|---|---|
| 账户模型 | 代码+状态绑定在合约中 | 代码与数据分离 |
| 状态存储 | Contract storage slots | 独立的 Data Accounts |
| 程序/合约 | Smart Contract(有状态) | Program(无状态) |
| 执行模型 | 串行执行 | 并行执行(Sealevel) |
| Gas/费用 | Gas(动态,可能很贵) | 固定低费用 + priority fee |
| 最小货币单位 | wei (1 ETH = 1e18 wei) | lamport (1 SOL = 1e9 lamports) |
| 区块时间 | ~12 秒 | ~400 毫秒 |
| 确认时间 | ~12 秒(1 确认) | ~400ms(乐观确认) |
| 编程语言 | Solidity / Vyper | Rust / C / Python(Seahorse) |
| 代币标准 | ERC20 (合约) | SPL Token (系统程序) |
| NFT 标准 | ERC721 / ERC1155 | Metaplex Token Metadata |
| 合约交互 | 直接调用函数 | Instruction + Accounts |
| 合约间调用 | External Call | CPI (Cross-Program Invocation) |
| 地址格式 | 0x + 40 hex chars (20 bytes) | Base58 编码 (32 bytes) |
| 重放保护 | nonce(递增) | recent_blockhash(时效) |
| 原子操作 | 单合约调用原子 | 整个 Transaction 原子 |
| 存储成本 | 一次性 Gas | Rent(或 Rent-exempt 永久) |
| 升级机制 | Proxy 模式 | BPF Loader 原生支持 |
关键架构差异深度解读
差异 1: 代码与数据分离
Ethereum:
Contract A 的所有 storage 都在 Contract A 的 storage trie 下
→ 读写 storage 只需要知道 slot number
→ 但不同合约的 storage 无法并行处理
Solana:
Program A 管理多个 Data Accounts
→ 每个 account 是独立的
→ 操作不同 accounts 的 transactions 可以并行
→ 但需要提前声明使用哪些 accounts
差异 2: Transaction 中声明所有账户
Ethereum:
tx.to = Contract Address
→ 运行时才知道会读写哪些 storage slots
→ 无法提前调度并行执行
Solana:
Transaction 中必须列出所有要用到的 accounts
→ Runtime 在执行前就知道哪些 transactions 冲突
→ 不冲突的 transactions 可以并行执行
→ 这就是 Sealevel 并行执行引擎的基础!
差异 3: 费用模型
Ethereum:
费用 = gasUsed × gasPrice
动态定价,网络拥堵时暴涨(可能 $50-200 一笔交易)
EIP-1559: baseFee(销毁)+ priorityFee(给矿工)
Solana:
基础费用 = 5000 lamports / signature ≈ $0.00025
+ 计算单元费用(通常很低)
+ priority fee(可选,给验证者小费加速)
总计通常 < $0.01
代码实战
Solana CLI 基础操作
安装 Solana CLI
# macOS / Linux
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
# 或使用 agave (新版 Solana 客户端)
sh -c "$(curl -sSfL https://release.anza.xyz/v2.1.0/install)"
# 验证安装
solana --version
生成密钥对
# 生成新密钥对(默认存储在 ~/.config/solana/id.json)
solana-keygen new
# 输出:
# Wrote new keypair to /home/user/.config/solana/id.json
# pubkey: 7nQz3...
# Save this seed phrase to recover your keypair:
# abandon abandon abandon ...
# 查看当前公钥
solana-keygen pubkey
# 从种子短语恢复
solana-keygen recover
# 生成自定义前缀的地址(vanity address)
solana-keygen grind --starts-with abc:1
配置网络
# 查看当前配置
solana config get
# 切换到 devnet(开发测试用)
solana config set --url https://api.devnet.solana.com
# 切换到 testnet
solana config set --url https://api.testnet.solana.com
# 切换到 mainnet
solana config set --url https://api.mainnet-beta.solana.com
# 使用自定义 RPC
solana config set --url https://your-rpc-provider.com
获取 Devnet SOL
# 空投 devnet SOL(每次最多 2 SOL)
solana airdrop 2
# 指定地址空投
solana airdrop 2 <PUBKEY>
# 查看余额
solana balance
# 查看指定地址余额
solana balance <PUBKEY>
查询账户信息
# 查看账户详细信息
solana account <PUBKEY>
# 输出示例:
# Public Key: 7nQz3...
# Balance: 2 SOL
# Owner: 11111111111111111111111111111111 (System Program)
# Executable: false
# Rent Epoch: 361
# 查看程序账户
solana account TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
# Owner: BPFLoaderUpgradeab1e11111111111111111111111
# Executable: true
# 查看交易详情
solana confirm <TX_SIGNATURE> -v
# 查看最近的区块
solana block-height
solana slot
转账
# 转 SOL
solana transfer <RECIPIENT_PUBKEY> 0.5
# 带 memo
solana transfer <RECIPIENT_PUBKEY> 0.1 --with-memo "Hello Solana!"
# 查看交易记录
solana transaction-history <PUBKEY>
用 Rust 查询 Solana 账户(预览)
use solana_client::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 连接 devnet
let client = RpcClient::new("https://api.devnet.solana.com".to_string());
// 查询账户信息
let pubkey = Pubkey::from_str("Your_Pubkey_Here")?;
let account = client.get_account(&pubkey)?;
println!("Balance: {} lamports ({} SOL)",
account.lamports,
account.lamports as f64 / 1e9
);
println!("Owner: {}", account.owner);
println!("Data length: {} bytes", account.data.len());
println!("Executable: {}", account.executable);
// 查询余额
let balance = client.get_balance(&pubkey)?;
println!("Balance via get_balance: {} SOL", balance as f64 / 1e9);
// 获取最新 blockhash
let blockhash = client.get_latest_blockhash()?;
println!("Latest blockhash: {}", blockhash);
// 获取 slot
let slot = client.get_slot()?;
println!("Current slot: {}", slot);
Ok(())
}
Cargo.toml 依赖:
[dependencies]
solana-client = "2.1"
solana-sdk = "2.1"
关键要点总结
Solana 架构核心三要素
1. Account Model(账户模型)
→ 一切皆账户,代码和数据分离
→ Owner 机制保证安全性
→ Rent 机制管理存储成本
2. Program(程序)
→ 无状态,只有逻辑
→ 通过 CPI 互相调用
→ 系统程序提供基础功能
3. Transaction(交易)
→ 必须声明所有涉及的账户
→ 可包含多条 Instructions(原子执行)
→ 不冲突的 Transactions 并行处理
Solana 高性能的秘密
提前声明 Accounts → Runtime 知道 Tx 冲突图 → Sealevel 并行执行
Ethereum: Tx1 → Tx2 → Tx3 → Tx4 (串行)
Solana: Tx1 ─→ ─→ ─→
Tx2 ─→ ─→ (不冲突的并行执行)
Tx3 ─→ ─→ ─→
常见误区
-
误区:Solana 的 Program 就是 Ethereum 的 Smart Contract
- 关键区别:Solana Program 是无状态的,所有状态存在独立的 Data Accounts 中
- 一个 Program 可以管理百万个 Data Accounts
-
误区:Solana 只是"更快的 Ethereum"
- 架构层面完全不同(Account model、并行执行、fees)
- 开发范式也完全不同(需要提前声明所有 accounts)
-
误区:Solana 不需要考虑 Gas 优化
- 虽然费用低,但有 Compute Unit 限制(默认 200K CU per instruction)
- 复杂操作可能超出 CU 限制需要优化
-
误区:PDA 是可选的高级功能
- 事实:PDA 是 Solana 开发的基础,几乎每个程序都会用到
- 没有 PDA 就无法实现程序控制的账户
面试关联
Q1: Solana 与 Ethereum 的账户模型有什么区别?
简短回答:Ethereum 把代码和状态绑定在一起(Smart Contract = code + storage),Solana 把它们分开(Program = code, Data Account = state)。这种分离使 Solana 能实现交易并行处理。
详细回答:
- Ethereum:每个合约有自己的 storage trie,状态和代码紧耦合。EVM 串行执行交易。
- Solana:Program 只包含代码,状态分布在独立的 Data Accounts 中。Transaction 必须声明所有涉及的 accounts,使 runtime 知道哪些交易冲突,从而并行执行不冲突的交易。
- trade-off:Solana 的并行性带来性能优势,但开发者需要手动管理 accounts,增加了开发复杂度。
Q2: 为什么 Solana 的 Transaction 需要提前声明所有 Accounts?
答案:这是 Sealevel 并行执行引擎的前提。通过提前知道每笔交易会读写哪些 accounts,runtime 可以构建冲突图,把不冲突的交易分配到不同线程并行执行。如果像 EVM 一样运行时才知道访问了哪些状态,就无法实现这种并行化。
Q3: 解释 Solana 中 Rent 的作用和 Rent Exempt 的含义。
答案:Solana 账户需要支付"租金"来维持链上存储。如果账户余额不足以覆盖 2 年租金,则可能在 epoch 结束时被垃圾回收。Rent Exempt 意味着账户余额 >= 2 年租金,数据将永久保存。当前实践中,几乎所有账户都设为 Rent Exempt。
参考资源
- Solana 官方文档 - Core Concepts — 账户模型、程序、交易
- Solana Cookbook — 实用代码示例
- Solana CLI 参考 — 命令行工具文档
- Sealevel 并行执行引擎 — 技术深度
- Solana vs Ethereum 对比 — 开发者入口
- Helius - Solana 开发教程 — 高质量 Solana 博客
- Solana Playground — 浏览器内 Solana 开发环境