程序

在 Solana 上,“智能合约”被称为程序。 程序 部署在链上的账户中,这些账户包含程序编译后的可执行二进制文件。用户通过发送包含 指令 的交易与程序交互,这些指令告诉程序需要执行的操作。

关键点

  • 程序是包含 可执行代码 的账户,代码组织为称为 指令 的函数。
  • 尽管程序是 无状态的,但它们可以包含创建和更新其他账户以存储数据的指令。
  • 升级权限 可以更新程序。一旦移除该权限,程序将变为不可更改。
  • 用户可以通过可验证的构建,验证链上程序账户的数据是否与其公开的源代码匹配。

编写 Solana 程序

Solana 程序主要使用 Rust 编程语言编写,开发通常有两种常见方法:

  • Anchor:一个为 Solana 程序开发设计的框架。它通过使用 Rust 宏减少样板代码,提供了一种更快速、更简单的编写程序方式。对于初学者,建议从 Anchor 框架开始。

  • 原生 Rust:这种方法是在不使用任何框架的情况下,直接用 Rust 编写 Solana 程序。它提供了更大的灵活性,但也增加了复杂性。

更新 Solana 程序

要了解更多关于程序部署和升级的信息,请参阅 部署程序 页面。

程序可以通过被指定为“升级权限”的账户 直接修改,通常是最初部署程序的账户。如果 升级权限 被撤销并设置为 None,程序将变为不可更改,无法再进行更新。

可验证程序

可验证构建允许任何人检查链上程序代码是否与其公开的源代码相匹配,从而能够检测源代码与部署版本之间的差异。

Solana 开发者社区引入了支持可验证构建的工具,使开发者和用户都能够验证链上程序是否准确反映其公开共享的源代码。

  • 搜索已验证的程序:用户可以在 Solana Explorer 上搜索程序地址,以快速检查已验证的程序。查看一个已验证程序的示例 这里

  • 验证工具Solana Verifiable Build CLI 是由 Ellipsis Labs 提供的工具,用户可以使用它独立验证链上程序与已发布的源代码是否一致。

  • Anchor 对可验证构建的支持:Anchor 提供了对可验证构建的内置支持。详细信息可以在 Anchor 文档 中找到。

伯克利数据包过滤器 (BPF)

Solana 使用 LLVM(低级虚拟机)将程序编译为 ELF(可执行和可链接格式)文件。这些文件包含 Solana 定制版本的 eBPF 字节码,称为“Solana 字节码格式”(sBPF)。ELF 文件包含程序的二进制文件,并在程序部署时存储在链上的可执行账户中。

内置程序

加载器程序

每个程序本身都由另一个程序拥有,这个程序就是它的加载器。目前存在五个加载器程序:

加载器程序 ID备注指令链接
nativeNativeLoader1111111111111111111111111111111拥有其他四个加载器
v1BPFLoader1111111111111111111111111111111111管理指令已禁用,但程序仍然执行
v2BPFLoader2111111111111111111111111111111111管理指令已禁用,但程序仍然执行指令
v3BPFLoaderUpgradeab1e11111111111111111111111正在逐步淘汰指令
v4LoaderV411111111111111111111111111111111111v4 预计将成为标准加载器指令

这些加载器对于创建和管理自定义程序是必要的:

  • 部署一个新的程序或缓冲区
  • 关闭一个程序或缓冲区
  • 重新部署/升级现有程序
  • 转移程序的权限
  • 完成程序的最终化

Loader-v3 和 Loader-v4 支持在程序初次部署后对其进行修改。修改权限由程序的权限管理,因为每个程序的账户所有权归加载器所有。

预编译程序

Ed25519 程序

程序程序 ID描述指令
Ed25519 程序Ed25519SigVerify111111111111111111111111111验证 ed25519 签名。如果任何签名验证失败,将返回错误。指令

ed25519 程序处理一个指令。第一个 u8 是需要检查的签名数量,接着是一个单字节填充。之后,以下结构体会被序列化,每个需要检查的签名对应一个。

Ed25519SignatureOffsets
struct Ed25519SignatureOffsets {
signature_offset: u16, // offset to ed25519 signature of 64 bytes
signature_instruction_index: u16, // instruction index to find signature
public_key_offset: u16, // offset to public key of 32 bytes
public_key_instruction_index: u16, // instruction index to find public key
message_data_offset: u16, // offset to start of message data
message_data_size: u16, // size of message data
message_instruction_index: u16, // index of instruction data to get message data
}

签名验证的伪代码:

process_instruction() {
for i in 0..count {
// i'th index values referenced:
instructions = &transaction.message().instructions
instruction_index = ed25519_signature_instruction_index != u16::MAX ? ed25519_signature_instruction_index : current_instruction;
signature = instructions[instruction_index].data[ed25519_signature_offset..ed25519_signature_offset + 64]
instruction_index = ed25519_pubkey_instruction_index != u16::MAX ? ed25519_pubkey_instruction_index : current_instruction;
pubkey = instructions[instruction_index].data[ed25519_pubkey_offset..ed25519_pubkey_offset + 32]
instruction_index = ed25519_message_instruction_index != u16::MAX ? ed25519_message_instruction_index : current_instruction;
message = instructions[instruction_index].data[ed25519_message_data_offset..ed25519_message_data_offset + ed25519_message_data_size]
if pubkey.verify(signature, message) != Success {
return Error
}
}
return Success
}

Secp256k1 程序

程序程序 ID描述指令
Secp256k1 程序KeccakSecp256k11111111111111111111111111111验证 secp256k1 公钥恢复操作(ecrecover)。指令

secp256k1 程序处理一个指令,其第一个字节是指令数据中序列化的以下结构体的数量:

Secp256k1SignatureOffsets
struct Secp256k1SignatureOffsets {
secp_signature_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes
secp_signature_instruction_index: u8, // instruction index to find signature
secp_pubkey_offset: u16, // offset to ethereum_address pubkey of 20 bytes
secp_pubkey_instruction_index: u8, // instruction index to find pubkey
secp_message_data_offset: u16, // offset to start of message data
secp_message_data_size: u16, // size of message data
secp_message_instruction_index: u8, // instruction index to find message data
}

恢复验证的伪代码:

process_instruction() {
for i in 0..count {
// i'th index values referenced:
instructions = &transaction.message().instructions
signature = instructions[secp_signature_instruction_index].data[secp_signature_offset..secp_signature_offset + 64]
recovery_id = instructions[secp_signature_instruction_index].data[secp_signature_offset + 64]
ref_eth_pubkey = instructions[secp_pubkey_instruction_index].data[secp_pubkey_offset..secp_pubkey_offset + 20]
message_hash = keccak256(instructions[secp_message_instruction_index].data[secp_message_data_offset..secp_message_data_offset + secp_message_data_size])
pubkey = ecrecover(signature, recovery_id, message_hash)
eth_pubkey = keccak256(pubkey[1..])[12..]
if eth_pubkey != ref_eth_pubkey {
return Error
}
}
return Success
}

这允许用户在交易中指定任何指令数据用于签名和消息数据。通过指定一个特殊的指令 sysvar,还可以从交易本身接收数据。

交易的成本将计算需要验证的签名数量,并乘以签名验证成本的倍数。

Secp256r1 程序

程序程序 ID描述指令
Secp256r1 程序Secp256r1SigVerify1111111111111111111111111验证最多 8 个 secp256r1 签名。接收签名、公钥和消息。如果任何一个失败,则返回错误。指令

Secp256r1 程序处理一个指令。第一个 u8 是要检查的签名数量,接着是一个单字节的填充。之后,每个需要检查的签名都会序列化以下结构:

Secp256r1SignatureOffsets
struct Secp256r1SignatureOffsets {
signature_offset: u16, // offset to compact secp256r1 signature of 64 bytes
signature_instruction_index: u16, // instruction index to find signature
public_key_offset: u16, // offset to compressed public key of 33 bytes
public_key_instruction_index: u16, // instruction index to find public key
message_data_offset: u16, // offset to start of message data
message_data_size: u16, // size of message data
message_instruction_index: u16, // index of instruction data to get message data
}

签名验证的伪代码:

process_instruction() {
if data.len() < SIGNATURE_OFFSETS_START {
return Error
}
num_signatures = data[0] as usize
if num_signatures == 0 || num_signatures > 8 {
return Error
}
expected_data_size = num_signatures * SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START
if data.len() < expected_data_size {
return Error
}
for i in 0..num_signatures {
offsets = parse_signature_offsets(data, i)
signature = get_data_slice(data, instruction_datas, offsets.signature_instruction_index, offsets.signature_offset, SIGNATURE_SERIALIZED_SIZE)
if s > half_curve_order {
return Error
}
pubkey = get_data_slice(data, instruction_datas, offsets.public_key_instruction_index, offsets.public_key_offset, COMPRESSED_PUBKEY_SERIALIZED_SIZE)
message = get_data_slice(data, instruction_datas, offsets.message_instruction_index, offsets.message_data_offset, offsets.message_data_size)
if !verify_signature(signature, pubkey, message) {
return Error
}
}
return Success
}

注意:所有签名都强制使用低 S 值,以避免意外的签名可变性。

核心程序

Solana 集群的创世块包含了一系列特殊程序,这些程序为网络提供核心功能。历史上,这些程序被称为“原生”程序,并且曾经与验证器代码一起分发。

程序程序 ID描述指令
系统程序11111111111111111111111111111111创建新账户、分配账户数据、将账户分配给拥有程序、从系统程序拥有的账户中转移 lamports,并支付交易费用。SystemInstruction
投票程序Vote111111111111111111111111111111111111111创建和管理跟踪验证器投票状态和奖励的账户。VoteInstruction
质押程序Stake11111111111111111111111111111111111111创建和管理代表委托给验证器的质押和奖励的账户。StakeInstruction
配置程序Config1111111111111111111111111111111111111将配置数据添加到链中,后面是允许修改它的公钥列表。与其他程序不同,配置程序没有定义任何单独的指令。它只有一个隐式指令:“存储”。其指令数据是一组密钥,用于控制对账户的访问以及存储在其中的数据。ConfigInstruction
计算预算程序ComputeBudget111111111111111111111111111111设置交易的计算单元限制和价格,允许用户控制计算资源和优先级费用。ComputeBudgetInstruction
地址查找表程序AddressLookupTab1e1111111111111111111111111管理地址查找表,允许交易引用比交易账户列表中通常能容纳的更多账户。ProgramInstruction
ZK ElGamal 证明程序ZkE1Gama1Proof11111111111111111111111111111提供用于 ElGamal 加密数据的零知识证明验证。

Is this page helpful?

Table of Contents

Edit Page