本文深入探讨了 Solana 程序的系统调用机制,特别是针对 SIMD-0512 提案中新增 sol_sha512 系统调用的讨论。文章解释了 SVM 如何通过稳定的 C ABI 实现合约与宿主机环境的通信,并分析了哈希函数在底层是如何通过内存指针进行数据传递与处理的。

最近我研究了 Dean Little 提出的 SIMD 提案,特别是 SIMD-0512,该提案建议增加 sol_sha512 系统调用(syscall)。这让我深入探索了 Solana 虚拟机(SVM)的实际运作方式,特别是因为我最近在与团队合作的 CLOB 项目中使用了 sol_sha256。
以下是 sol_sha256 在代码中的样子:
use core::any::type_name;
use pinocchio::{error::ProgramError, Address};
// 成本约为 140 CUs
// 直接调用 Solana 原生的 SHA256 实现
#[cfg(target_os = "solana")]
unsafe extern "C" {
unsafe fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64;
}
#[cfg(not(target_os = "solana"))]
pub unsafe fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) {
#[cfg(test)]
{
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
// 将指针转换回我们的 SolBytes 结构体数组
let slices = core::slice::from_raw_parts(vals as *const SolBytes, val_len as usize);
for slice in slices {
// 对切片指向的实际数据进行哈希
let data = core::slice::from_raw_parts(slice.ptr, slice.len as usize);
hasher.update(data);
}
let hash = hasher.finalize();
core::ptr::copy_nonoverlapping(hash.as_ptr(), hash_result, 32);
}
}
这里发生的事情本质上是我在调用一个内置的虚拟机函数,它将字符串哈希成一个 256 位的值(即 sol_sha256 的 [u8; 32])。其工作原理如下:
[vals]。[val_len]。[hash_result]。u64 返回类型,代表指令状态(0 = 成功,非零 = 错误)。之所以有两种 sol_sha256 的实现,是因为我的 Linux 电脑上没有原生的 sol_sha256,我需要在 Solana 虚拟机之外模拟相同的行为。
#[repr(C)]
struct SolBytes {
ptr: *const u8,
len: u64,
}
#[cfg(target_os = "solana")]
unsafe extern "C" {
fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64;
}
pub fn hash_a_string() {
let message = "Hello, Solana!"; // 你的字符串
// 步骤 1 - 将输入描述为系统调用能理解的切片
// 我们不复制字符串,只是告诉系统调用:
// “字节存在这个地址,长度是这么多”
let slices = [
SolBytes {
ptr: message.as_ptr(), // 指向 BPF 内存中字符串的指针
len: message.len() as u64, // 14 字节
}
];
// 步骤 2 - 分配输出缓冲区
// 你创建这个缓冲区,以便系统调用将结果写入其中
let mut hash_result = [0u8; 32];
// 步骤 3 - 执行系统调用
let status = unsafe {
sol_sha256(
slices.as_ptr() as *const u8, // 指向 SolBytes 数组的指针
slices.len() as u64, // 有多少个切片(这里只有 1 个)
hash_result.as_mut_ptr(), // 写入 32 字节结果的位置
)
};
// 步骤 4 - 检查是否成功
assert_eq!(status, 0, "syscall failed");
// hash_result 现在填充了 32 字节的哈希值
msg!("hash bytes: {:?}", hash_result);
}
当你在 Solana 上编写程序时,它并不会直接在验证节点 CPU 的裸机上运行。相反,它运行在 Solana 虚拟机 (SVM) 内部,使用一种称为 sBPF (Solana Berkeley Packet Filter) 的格式。这种沙盒机制对于安全性至关重要,它可以防止你的代码破坏验证节点。
你可以将系统调用(syscall)看作是一个“作弊码”或是一个通往受限沙盒环境之外的传送门。在 SIMD-0512 中,Dean Little 提议包含一个新的系统调用 sol_sha512,它产生一个 512 位的哈希值 [u8; 64],其接口与 sol_sha256 完全一致。
SHA-512 是 Ed25519 签名验证的核心原语,并且已经作为内部依赖存在于 Agave 和 Firedancer 验证节点客户端中。然而,目前它尚未作为系统调用开放给链上程序。
当你下次在 Anchor、Pinocchio 或 Quasar 中执行 Rent::get()? 时,你本质上是在执行一个系统调用,可能是 sol_get_rent_sysvar 或 sol_get_sysvar。
这里有一个关键问题:如果 SVM 是用 Rust 编写的,为什么我们需要使用 "C"(系统调用 ABI)来进行系统调用?
原因在于,系统调用 ABI(应用程序二进制接口) 定义为 C 是经过深思熟虑的:
因此,extern "C" 并不意味着验证节点是用 C 编写的,它意味着你的程序正在跨越 ABI 边界,需要使用约定的标准来执行此操作。

如果我的数据存在于堆栈(stack)上,并且虚拟机接收到了指向它的指针,例如:
let slices = [
SolBytes {
ptr: message.as_ptr(), // 指向 BPF 内存中数据的指针
len: message.len() as u64,
}
];
那么这个指针指向的是谁的堆栈?
这是否意味着虚拟机只能看到该指令执行时内存中的内容,从而使其在本质上是无状态的?
- 原文链接: x.com/inspiration_gx/sta...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!