Solana 的 SVM(Solana Virtual Machine)是其核心虚拟机系统,专门设计用于支持高性能的去中心化应用(DApp)运行。与以太坊的EVM(Ethereum Virtual Machine)相比,SVM 在设计上专注于并行处理、低延迟和高吞吐量,以支持大规模的去中心化应用和交易处理。不过 SVM 不支持 Solidity,只支持 Rust、C/C++ 和 Python 编写的智能合约。
SVM 的主要特点
并行处理能力
Solana 的 SVM 与 EVM 不同,它专注于并行执行事务。它通过分析每个交易的数据依赖关系,将没有依赖关系的交易分组并行执行。有依赖关系的交易根据 Leader 给出的 PoH 顺序执行。
在 SVM 中,客户端调用智能合约时,必须声明哪些账户会在交易中被使用。因此 SVM 可以在不同的线程中并行执行互不相干的交易,从而显著提高了每秒交易量(TPS)。
数据分区和账本设计
Solana 利用“账户模型”(Account Model),每个账户存储其自身的状态数据。交易可以访问多个账户,每个账户的数据独立存储。与以太坊使用全局账户状态树根不同,这样减少了全局状态的依赖性。
由于这种分区和独立账户设计,SVM 可以更灵活地管理交易依赖,允许大多数交易在并行的环境下运行,提高了网络效率。
示例:简单代币转账程序(创建、调用)
创建智能合约
以下是一个用 Rust 编写的简单 Solana 程序,实现从一个账户向另一个账户转移代币的功能。
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
msg,
program_error::ProgramError,
sysvar::{rent::Rent, Sysvar},
};
// 程序的入口点
entrypoint!(process_instruction);
// 定义处理指令的核心函数
fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// 创建账户信息的迭代器
let account_info_iter = &mut accounts.iter();
// 获取发送方和接收方账户
let sender_account = next_account_info(account_info_iter)?;
let receiver_account = next_account_info(account_info_iter)?;
// 获取租约,确保账户是有租金的账户
let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
// 检查账户是否可写
if !sender_account.is_writable || !receiver_account.is_writable {
return Err(ProgramError::InvalidAccountData);
}
// 从指令数据中提取转账金额(以 u64 表示)
let amount = instruction_data
.get(..8)
.and_then(|bytes| bytes.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(ProgramError::InvalidInstructionData)?;
// 检查账户余额是否足够
if **sender_account.try_borrow_lamports()? < amount {
return Err(ProgramError::InsufficientFunds);
}
// 执行代币转账
**sender_account.try_borrow_mut_lamports()? -= amount;
**receiver_account.try_borrow_mut_lamports()? += amount;
msg!("Transferred {} lamports from {} to {}", amount, sender_account.key, receiver_account.key);
Ok(())
}
在上述代码中,process_instruction
函数接收一个账户列表 accounts
,这些账户是在交易中由客户端指定的。程序通过迭代这个列表,获取需要操作的账户。
sender_account
:发送方账户,代币的来源。receiver_account
:接收方账户,代币的目标。
调用智能合约
在客户端(如钱包或应用程序)中,构建交易时需要明确指定程序和涉及的账户。
use solana_sdk::{
instruction::{Instruction, AccountMeta},
pubkey::Pubkey,
transaction::Transaction,
signer::Signer,
};
// 假设已经有发送方的密钥对和最近的区块哈希
let sender_keypair = ...;
let recent_blockhash = ...;
// 定义程序 ID
let program_id = Pubkey::new_unique();
// 定义账户
let sender_pubkey = sender_keypair.pubkey();
let receiver_pubkey = Pubkey::new_unique();
// 设置转账金额
let amount: u64 = 1_000_000; // 例如,转账 1,000,000 lamports
let instruction_data = amount.to_le_bytes().to_vec();
// 构建指令,指定程序 ID 和账户列表
let instruction = Instruction::new_with_bincode(
program_id,
&instruction_data, // 传入指令数据,即转账金额
vec![
AccountMeta::new(sender_pubkey, true), // 发送方账户,需要签名
AccountMeta::new(receiver_pubkey, false), // 接收方账户,不需要签名
],
);
// 创建交易
let transaction = Transaction::new_signed_with_payer(
&[instruction],
Some(&sender_pubkey),
&[&sender_keypair],
recent_blockhash,
);
// 发送交易到 Solana 网络
由于在交易中明确声明了将被使用的账户,Solana 可以在执行前分析交易,确定哪些交易之间没有账户冲突,从而并行执行,例如
- 交易 A:从账户 X 向账户 Y 转账。
- 交易 B:从账户 Z 向账户 W 转账。
因为交易 A 操作的账户是 [X, Y],交易 B 操作的账户是 [Z, W],且 X、Y、Z、W 互不相同,因此这两个交易没有依赖关系,Solana 可以在不同的线程中同时执行它们。
SVM 局限性
当交易之间存在相同的依赖(即操作相同的账户,尤其是需要写入的账户)时,这些交易只能按 PoH 的顺序执行,以确保数据一致性和防止冲突。
Solana 在交易执行前会进行账户依赖分析,根据交易依赖的账户关系安排并行执行或顺序执行。如果一个交易涉及某个账户的写操作,其他涉及相同账户的交易会依据PoH的时间顺序排定,以确保数据一致性。这种依赖分析机制避免了多个交易同时修改同一账户的数据,从而确保系统的一致性。
例如,交易 A 和交易 B 都依赖账户 X 的写操作。系统会先执行交易 A,释放账户 X 的锁后再执行交易 B。