本文深入探讨了Solana智能合约开发中可能遇到的各种资源限制,包括计算单元(CU)限制、交易大小限制和堆栈大小限制。文章详细解释了这些限制的原因、影响以及如何通过优化代码来规避这些限制,以确保智能合约在Solana区块链上高效、安全地运行。
GM GM 大家好 😁,
在本博客中,我们将探讨你在开发智能合约时可能遇到的各种 Solana 资源限制。你可能会遇到包含“limit”或“exceed”等词语的错误。这些错误代表 Solana 程序预定义的边界,旨在维护区块链上的公平性和性能。如果你想获得关于这些主题的更多信息,那么你来对地方了!
听听你可以从这篇博客中期待什么:-
Solana 是一种高性能的公共区块链,它与比特币和以太坊等传统区块链的不同之处在于其独特的交易处理方法,它将逻辑和状态分离到不同的账户中,这使得 Solana 可以同时处理许多交易。
然而,在 Solana 上运行的程序会受到多种资源限制。这些限制确保程序公平地使用系统资源,同时保持高性能。
了解这些边界非常有帮助,作为程序设计者,你必须注意这些限制,才能创建经济、快速、安全和实用的程序。
CU(计算单元)顾名思义,CU 是 Solana 上交易或指令执行的计算工作(CPU 周期和内存使用)的基本度量单位。它类似于以太坊中的 "Gas" 费用,但更可预测且延迟更低。
你的智能合约在链上执行的每个指令,例如读取或写入账户、执行密码学操作(如 zk-ElGamal)或验证签名以及序列化和反序列化,都会消耗一定数量的计算单元(CU)。这与节点完成的工作量大致成正比。
如果你执行简单的交易,那么节点可以有效地处理这些智能合约,从而降低 CU 消耗。但是,如果你执行复杂的数学运算或大量的循环,节点会消耗大量的内存和 CPU,并且运行程序(智能合约)需要更多的时间,从而导致更高的 CU 消耗。
例如,对于一个简单的交易,如将 SOL 从钱包 A 发送到钱包 B,大约需要 3000 个计算单元
let transfer_amount_accounts = Transfer {
from: ctx.accounts.signer.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
};
let ctx = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
transfer_amount_accounts,
);
transfer(ctx, amount * LAMPORTS_PER_SOL)?;
// Takes around 3000 CU
上面的代码是用 Anchor ⚓️ 编写的,它执行了一个简单的 SOL 转移,为此需要 3000 个 CU。
我们知道,繁重的数学运算或循环会消耗大量的计算单元。但是,每个交易或指令都有 200,000 个 CU 的默认预算。
如果交易或指令耗尽了 200,000 CU 的限制,则该交易或指令将被简单地回滚,所有状态更改都会被撤销,并且费用不会退还给调用该交易的签名者。(这种机制可以防止攻击者在节点上运行永无止境或计算密集型程序,这可能会减慢或停止链的运行)。
Error: exceeded maximum number of instructions allowed (200000) compute units
就我个人而言,我在构建我的 Chaubet 项目时遇到了这个错误。当一个投注者购买股票时,这个函数 执行一些繁重的数学计算和检查。因此,该指令超过了 200,000 CU 的限制,并且整个指令都被回滚了。
我们可以使用 SetComputeUnitLimit 增加我们的计算限制,我们可以通过向我们的交易添加一个指令来请求一个特定的计算单元限制。但是 🍑 我们只能将 CUs 预算增加到 140 万个单位。
const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 500000, // Increased from 200k to 500k CUs.
});
简而言之,Solana 的 CU 限制是为了确保公平的资源分配。但这是什么意思?
Solana 验证器是处理区块链交易并维护网络状态的独立计算机(或节点)(这些是你必须知道的基本原理 😒)。每个验证器都有有限的 CPU 功率和内存,就像任何普通计算机一样。当程序在这些节点内运行时,它们会分配一些内存来处理所有指令。
现在,如果一个恶意用户发送一个包含无限循环的交易,它可能会使用大量的内存,并且可能会减慢甚至崩溃系统。由于区块链是一个共享计算机/节点的网络,如果一个用户执行大量的 CPU 任务并使用大量的 CPU/内存,它会占用系统,使其他用户无法使用。
由于 CU 预算的这种限制,它有助于 Solana 网络防止拒绝服务(DoS)攻击和验证器上的资源耗尽。
在深入了解交易大小限制之前,让我们先了解一下什么是交易以及它们包含什么。 Solana 上的交易是用户发送的与网络交互的请求,通常是为了改变数据,例如在账户之间转移 lamports(本地Token单位)。
每个交易包含一个或多个指令,这些指令指定要在区块链上执行的操作。此执行逻辑以原始字节形式存储在程序状态中。

签名数组:- 交易中包含的一系列签名。
消息:- 这是交易的核心部分。它包含描述交易意图所需的一切。
正如你已经在上面的图像中看到的,消息是交易的核心部分,它包含:-
标头:- 此标头指示签名者和只读账户的数量 (3 字节)
账户数组:- 它包含执行交易所需的所有账户,由客户端提供(每个公钥 32 字节)
最近的区块哈希:- 附加到此交易的最新区块哈希 (32 字节)
指令数组:- 包含签名者调用的所有指令(大小取决于指令代码的复杂性)。
| 组件 | 大小 | 描述 |
|---|---|---|
| 签名 | 64 字节 | 交易中包含的每个签名 |
| 消息头 | 3 字节 | 指示签名者和只读账户的数量 |
| 账户公钥 | 32 字节(每个账户) | 参与交易的每个账户的公钥 |
| 最近的区块哈希 | 32 字节 | 附加到交易的最近的区块哈希 |
| 指令 | 各不相同 | 取决于交易中指令的数量和复杂性 |
每个 Solana 交易的大小限制为 1,232 字节。 交易总大小是所有签名、涉及的账户、区块哈希和指令(包括它们的账户和数据)的字节之和,并且必须小于 1,232 字节。 由于此限制,开发人员需要将所有签名者、账户地址和所需数据放入 1,232 字节的约束中
当你尝试从客户端发送太多账户或在单个交易中发送太多指令时,你可能会遇到 transaction size limit exceeded 错误。
你可能想知道为什么仅限于 1,232 字节? <br/> 简而言之:- Solana 中 1,232 字节的交易大小限制与使用 IPv6(互联网协议版本 6)通过互联网传输数据的方式密切相关。但让我们更详细地分解它。
原因如下:
那么,什么是计算机网络?
简单来说,它是一组连接在一起以共享数据📦的计算机或节点。 这可能涉及两个本地节点/计算机(LAN)之间的通信,或者跨越全球的计算机(互联网)之间的通信。
为了有效地与节点通信,而不会将数据📦或信息发送到错误的目标节点,我们需要遵循一套规则或协议。 IP(互联网协议)是网络上节点的通用寻址系统。 IP 为每个节点或计算机提供的地址,并使用这些地址来路由和将数据包📦传递到正确的目标位置。 通过网络发送的数据不会一次性发送。 它被分成称为数据包的小块。
但是,solana 还在 IP 之上使用 UDP(用户数据报协议)来更快地发送数据包/数据📦(不像 etheareum 使用 TCP,如果数据包太大,它会进行大量检查并阻碍速度)。 UDP 只是尽可能快地发送数据包📦,不保证它们会到达。 但是 🍑 如果数据包很大(如果大于 MTU),那么可能会被分片(拆分),我们可能会丢失一些数据,这会降低可靠性。

好的,现在我们为什么要学习所有这些东西? 它们与交易大小限制有什么关系? 请耐心等待,我们即将到达那里,让我们连接一些点😌。
Solana 节点,像其他联网计算机一样,使用互联网协议(IP),通常是 IPv6(版本)进行通信。 它们必须遵循 IP 的寻址和路由数据规则。 Solana 节点使用 UDP 传输协议相互发送序列化的交易,该协议快速高效。
但是,如果交易(UDP 数据包)大于网络的最大传输单元(MTU),则它会被 IP 层分片(拆分),而不是被 UDP 本身分片。 UDP 只是发送数据包,它不处理分片。 如果任何分片在传输过程中丢失,则由于 UDP 不提供错误纠正或重新传输,因此整个交易都会丢失。
因此,此分片由 Solana 处理,为了避免分片,数据包/交易大小应小于 MTU(最大传输单元),通常为 1280 字节。 删除标头后(IP 和 UDP 标头 = 48 字节),剩下的 1,232 字节分配给交易大小。
随着 Solana 采用现代传输协议(如 QUIC)(它不像 UDP 那样具有严格的 MTU 约束,QUIC 更像是 TCP(处理分片)和 UDP(速度极快)的混合体),更大的消息可以更可靠地传递。
在旧版本中,交易大小限制为 1,232 字节,因此很难为 ZK 和 DeFi 应用程序创建复杂的交易。 在客户端增加账户数量(每个账户的公钥为 32 字节)并列出所有这些公钥可能会很快达到交易大小限制。
因此,开发人员应编写优化的代码,将所有账户、用户签名压缩到微小的 1,232 字节空间中。

通过这个新的 SIMD-296 提案,交易大小限制增加到 4k 字节。 这种更改允许更多的指令、更大的数据有效负载以及更顺畅地执行复杂的 dApps,尤其是在涉及多次交互或需要丰富的元数据的 dApps。
在 Solana 程序(智能合约)中,栈大小存在限制 - 为局部变量和函数调用分配的内存量。 当前的栈帧大小限制为 每个帧 4KB。
栈内存用于:
// 这个函数/栈帧包含超过 4KB 的大小
pub fn handle(_ctx: Context<SendAmount>) -> Result<()> {
// 在栈上分配一个 1MB 的缓冲区
let mut buffer = [0u8; 50000]; // 50,000 字节(每个 u8 是 1 字节,因此 50,000 * 1 = 50,000 字节)
// 用一些模式填充缓冲区
for i in 0..buffer.len() {
buffer[i] = (i % 256) as u8; // 用重复的 0..255 填充
}
// 查找所有字节的总和(只是为了好玩)
let sum: u64 = buffer.iter().map(|&b| b as u64).sum();
println!("Sum of all bytes: {}", sum);
Ok(())
}
⚠️ 警告示例:
当你在栈上分配一个大的缓冲区或数组时,你可能会遇到一个错误,例如:
Error: Function _ZN16cu_optimizations16cu_optimizations6handle17h60f6d64a7e5552edE Stack offset of 1048576 exceeded max offset of 4096 by 1044480 bytes, please minimize large stack variables. Estimated function frame size: 1048576 bytes. Exceeding the maximum stack offset may cause undefined behavior during execution.这个警告表明你的函数的栈帧超过了 Solana 的 4KB 栈限制。 为了解决这个问题,避免在栈上分配大的数组或缓冲区 - 使用堆分配或重构你的代码以减少栈的使用。
error Access violation in program section at address 0x1fff02ff8 of size 8
| 区域 | 起始地址 | 目的 | 访问规则 |
|---|---|---|---|
| 程序代码 | 0x100000000 | 可执行字节码 (SBF 指令) | 只读,只执行 |
| 栈数据 | 0x200000000 | 函数调用帧和局部变量 | 读/写 (每个栈帧 4KB) |
| 堆数据 | 0x300000000 | 动态内存分配(默认 32KB) | Bump 分配器(无释放) |
| 输入参数 | 0x400000000 | 序列化的指令数据和账户元数据 | 执行期间只读 |
为了解决栈的限制:
solana_optimization_github <br/> rare_skill_blog_post <br/> solana github discussion SIMD-0296<br/> frank_castle tweet on solana transaction size limit<br/> a great video to undertand about memory mangment in programming
- 原文链接: github.com/baindlapranay...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!