本文介绍了Solana比较重要的一些概念,包括共识机制、账户模型、程序、交易、指令等等
参考文档:
HackQuest社区: https://www.hackquest.io/zh
Solana中文文档: https://www.solana-cn.com/SolanaDocumention/home.html
Solana VS ETH
一、共识机制
Solana
- Proof of History (PoH): PoH 是一种创新的时间记录机制,通过加密散列函数生成一个可验证的时间顺序,使得区块链中的事件可以按时间线性顺序排列。这减少了节点之间的时间同步需求,使得节点能够迅速达成共识,而无需等待整个网络确认。
- Proof of Stake (PoS): 验证者需要抵押一定数量的 SOL 代币,持有更多代币的验证者有更高的概率被选中生成区块。PoS 提供了经济激励,确保验证者行为诚实。
- PoH 确保区块的时间戳和顺序,PoS 则确保网络的安全性和抗攻击性,两者结合使得网络能够达到每秒数千笔交易处理速度
以太坊
- 从 PoW 到 PoS 的转变: 以太坊 2.0 的升级(The Merge)将共识机制从能源密集的 PoW 转为 PoS,大幅降低了能耗。验证者通过锁定 ETH 参与共识,取代了矿工的角色,提升了网络的安全性和可持续性。
二、交易处理能力
Solana
- 并行处理: 通过将交易分解为多个子集,利用不同的验证节点并行处理,提高了吞吐量。Solana 的架构支持高达每秒数千笔交易,平均出块时间为 400 毫秒,平均每秒 2000+ 笔交易,在高负载时保持较低的交易费用。
- 低延迟和低费用: Solana 的架构设计使其能够快速确认交易,通常在一秒以内完成。即使在高负载下,交易费用仍保持低廉,适合需要高频交易的应用。
以太坊
- 串行执行: 以太坊的智能合约在虚拟机(EVM)中按顺序执行,每笔交易必须等待前一笔完成才能开始。虽然这种方式确保了状态的一致性,但也限制了以太坊吞吐量长期维持在每秒15笔~30笔,以牺牲性能换来安全性和一致性。
- Layer 2 和 Rollup: Layer 2 解决方案通过将大量交易在链下处理,然后将结果提交到主链,显著提高了交易速度和降低了成本。Rollup 是其中一种常见的技术,能够将数百笔交易合并为一个批次,能够将燃料费减少多达100倍。
三、交易费用(Gas费)
Solana它的交易费用是根据交易的复杂度和大小动态计算的,这意味着,交易费用会根据交易的执行成本而变化,而不是根据网络上的交易量变化。Solana 的平均交易费用通常低于 0.01 美元,平均为 0.00025 美元,这使得进行小额交易更具成本效益。
以太坊 的交易费用因网络拥堵而波动,这是一种纯粹的市场机制,网络中交易拥堵情况下你的交易要想被确认,就需要支付高昂的手续费。目前一笔转账交易的手续费大概在 1 ~ 10 美元左右。
四、智能合约
Solana中一切皆账户,它的智能合约(Solana 中称之为程序program,后续也统一使用“程序”这一术语)也是账户,但细分为可执行账户和数据账户,前者存储程序的代码,用来执行特定的逻辑,后者存储状态,即程序运行时的数据。这种分离的模式,使得程序的升级更加简单,因为程序本身无状态,可以直接升级为新的代码逻辑。
以太坊 的智能合约本身就包含了合约的逻辑代码,以及状态数据。因此合约部署之后,就不支持直接的升级,只能通过代理的方式间接升级,即重新部署一套合约代码,生成新的合约地址,代理再指向这个新的合约地址。
五、账户
Solana中一切皆账户,它的账户就像一个容器(或者电脑中的文件夹),可以包含程序代码、状态数据以及账户元数据。按照功能可划分为可执行账户和数据账户,前者为存储程序代码的账户,也称为程序账户。后者包括普通用户账户和其他非程序账户,这些账户存储了用户的余额、交易历史和其他相关数据,但它们本身不包含程序代码。对于熟悉以太坊账户的同学来说,这种划分方式也许有点奇怪,不过随着了解的深入,就会认识到 Solana 的单一账户模型,使得多个交易能够并行处理,这正是 Solana 高性能的基础。
以太坊 分为EOA账户和智能合约,前者是普通用户在以太坊网络中的账户,用于存储以太币(ETH)和进行交易。后者是包含智能合约代码和状态的账户,这些账户由合约创建并部署在以太坊区块链上。通过这两种账户类型的结合,以太坊提供了一个灵活且功能强大的去中心化应用开发平台。
共识机制
PoH 工作流程
Proof of History (PoH) 是 Solana 区块链的核心创新之一,它为区块链提供了一个去中心化的时间源。PoH 本质上是一个高频可验证延迟函数 (VDF),它能够创建历史记录,证明某个事件发生在特定时刻之前或之后。
我们看一个简化版的 PoH 工作流程,从一个随机值开始,运行 hash 函数,并将输出(output)作为输入(input)再次运行该函数。记录函数执行的次数(index)以及每次调用的结果(output)。次数,提供了顺序和时间两个维度的支持;将输出作为输入,依次头尾相连,形成了一条完整的证据链。
随机值的选择,可以选择纽约时报当天的标题,或者其他的事实。
hashN 代表实际的 Hash 输出,Solana 中每个交易都与前一笔交易的哈希相连接,形成一个由交易构成的链,当一定数量的交易都被执行并构成了PoH 链后,它们被打包到一个区块(slot)中。
只要选择的哈希函数是抗碰撞的,这个哈希集和就只能被单线程顺序计算出来。这满足了在 index 为 300 时,如果不通过算法实际运行 300 次,无法获得这样的结果的设定。
因此,我们可以从数据结构中推测出从 index 0 到 index 300 真实的过去的时间。这样以来,在网络中尽管每个节点的时间戳可能会有不一致,但是我们通过 hash 的次数(比如300),就变相的替代了时间,于是网络中有了全局一致的时间钟,并保持了交易的顺序。
PoH 的特性和优势
- 去中心化时间戳: PoH 提供了一个不依赖中心化时间服务器的时间戳机制。
- 顺序保证: 由于每个事件都嵌入到连续的哈希链中,事件的顺序得到了密码学保证。
- 高效验证: 验证者可以并行验证 PoH 链,大大提高了验证速度。
- 减少网络开销: 节点间不需要频繁同步时间,减少了网络通信。
- 提高交易处理速度: PoH 允许 Solana 在不牺牲安全性的情况下实现高吞吐量。
PoH 的广播和验证
- 区块生成: Leader 节点生成包含 PoH 序列和交易的区块。
- 并行验证: 其他节点接收区块后,可以将 PoH 序列分割成多个部分,在 GPU 上并行验证。
- 快速共识: 由于 PoH 提供了时间和顺序保证,节点可以快速就区块的有效性达成共识。
- 动态调整: Solana 可以根据网络条件动态调整 PoH 的哈希频率,以适应不同的硬件能力。
PoH 应用
假设我们正在观察 Solana 网络中的一个时间段,我们将看到 PoH 是如何与其他组件协同工作的。
1. PoH 生成器启动
- 假设当前的 PoH 生成器(通常是当前的 leader 节点)开始工作。
- 初始哈希值:0x1234...(这可能来自之前的状态或预定义的起始点)
2. PoH 序列生成
PoH 生成器开始连续进行哈希操作:
Index 0: Hash(0x1234...) = 0xabcd...
Index 1: Hash(0xabcd...) = 0xef01...
Index 2: Hash(0xef01...) = 0x2345...
...
3. 交易到达
- 在 Index 50 时,一个交易 T1 到达:
"Alice 发送 5 SOL 给 Bob"
- T1 的哈希值为 0x7890...
4. 交易嵌入 PoH
PoH 生成器将 T1 嵌入到 PoH 序列中:
Index 50: Hash(0x...|| 0x7890...) = 0xdead...
Index 51: Hash(0xdead...) = 0xbeef...
...
5. 继续生成 PoH 和处理交易
- 更多的交易陆续到达并被嵌入 PoH 序列
- 例如,在 Index 100 嵌入 T2,Index 150 嵌入 T3,等等
6. 形成区块
- 假设每 200 个 PoH 条目形成一个区块
- 当达到 Index 200 时,当前的 PoH 序列和所有嵌入的交易被打包成一个区块
7. 区块广播和验证
- Leader 节点将这个区块广播给其他验证者节点
验证者接收到区块后,可以并行验证 PoH 序列:
- 验证者 1 检查 Index 0-66
- 验证者 2 检查 Index 67-133
- 验证者 3 检查 Index 134-200
8. 共识和确认
- 验证者快速确认 PoH 序列的正确性
- 他们也验证嵌入的交易(T1, T2, T3 等)是否有效
- 一旦大多数验证者确认,区块被添加到链上
9. Leader 轮换
- 基于 PoH 序列,网络预先知道下一个 leader 是谁
- 例如,如果每 1000 个 PoH 条目轮换一次 leader,那么在 Index 1000 时,新的 leader 接管 PoH 生成
10. 时间估算
- 假设每次哈希操作平均需要 0.1 毫秒
- 那么 200 个 PoH 条目(一个区块)大约代表 20 毫秒的真实时间
- 网络可以据此估算交易的确切时间戳
实际应用中的优势
- 高吞吐量:由于 PoH 提供了时间和顺序保证,Solana 可以并行处理大量交易。
- 低延迟:交易几乎立即被嵌入 PoH,提供近乎实时的确认。
- 可预测的 leader 选择:网络总是知道谁是下一个 leader,减少了切换开销。
- 快速终结性:由于快速的验证过程,交易可以在几秒内达到最终确认。
- 精确的时间戳:每个交易都有一个与 PoH 序列关联的精确时间戳。
POS 工作流程
先来了解下在 Solana的系统架构中最重要的两种角色:Leader(出块者)和 Validator(验证者)。两者实际上都是质押了 SOL 代币的全节点,只是在不同的出块周期内,Leader 会由不同的全节点来充当,而没有当选 Leader 的全节点会成为 Validator。 所以在选择验证者方面, Solana 采用的是 PoS(权益证明)机制,验证者是通过抵押一定数量的代币来参与网络交易的验证的,持有更多代币的验证者有更大的机会被选中生成新的区块。
1.用户发起交易后,会被客户端直接转发给 Leader 节点,或者先被普通节点接收,再立刻转发给 Leader;
2.出块者 Leader 接收网络内全部的待处理交易,一边执行,一边给交易指令排序,制成交易序列(类似区块)。每隔一段时间,Leader 会把排好的交易序列发送给 Validator 验证节点;
3.Validator 按照交易序列(区块)给定的顺序执行交易,产生相应的状态信息State(执行交易会改变节点的状态,比如改变某些账户的余额);
4.每发送 N 个交易序列,Leader 会定期公开本地的状态 State,Validator会将其与自己的 State 作对比,给出 肯定/否定 的投票。这一步就类似于以太坊2.0或其他POS公链里的“检查点”。
5.如果在规定时间内,Leader收集到占全网 2/3 质押权重的节点们给出的肯定票,则此前发布的交易序列和状态 State 就被敲定,“检查点”通过,相当于区块完成最终确认 Finality;
6.一般而言,给出肯定票的 Validator 节点与出块者 Leader 所执行的交易、执行后的状态都是相同的,数据会同步。
Leader 选举
在 Solana 的共识协议中,有 Epoch(纪元)和Slot(间隔)两大时间单位。每个 Slot 约为0.4\~0.8秒,相当于一个区块的时间间隔。而每个Epoch周期包含43.2万个Slot(区块),长达2\~4天。每过4个 Slot(出块周期),Leader节点就会进行一次变更。
Solana 使用一种称为 Tower BFT(一种改进的实用拜占庭容错算法)的共识机制,结合 Proof of Stake (PoS) 和 Proof of History (PoH) 来选择和轮换 leader。
Leader 调度:
- Solana 网络预先确定一个 leader 调度表,通常覆盖未来几天的时间。
- 这个调度是基于验证者的质押量(stake)按比例分配的。
Leader 轮换:
- Leader 角色在验证者之间频繁轮换,通常每 1.6秒~3.2秒就会切换。
- 在一个 "epoch"(大约 2-3 天)内,每个验证者都有机会成为 leader。
单一 Leader:
- 在任何给定的时刻,只有一个节点作为 leader。
- Leader 负责生成 PoH 序列和打包交易到区块。
Leader 的职责:
- 生成 PoH 序列
- 接收交易并将其打包到区块
- 广播区块给其他验证者
在每个新的Epoch周期开始时,Solana网络会按照各节点的质押权重进行抽选,组成一个出块者Leader轮换名单,“钦定”了未来不同时刻的出块者。也就是说出块者会提前获知他们成为出块者。 具体而言,究竟如何指定 Leader 会考虑诸多因素,比如:
1.质押的代币数量: 在 PoS 中,质押的代币数量是一个关键的考虑因素。Validator 通常倾向于选择质押数量较大的节点,因为这增加了节点被选中为区块生产者的机会。这也有助于确保网络由具有足够利益参与的节点维护。
2.节点的性能: Validator 的节点性能是另一个关键因素。高性能的节点能够更快速地验证和打包交易,有助于维持网络的高吞吐量。Validator 可能会选择性能较好的节点以提高整个网络的效率。
3.网络延迟: Validator 可能会考虑节点之间的网络延迟。选择网络延迟较低的节点有助于减少区块的传播时间,从而提高网络的实时性。
4.节点的可用性: Validator 会关注节点的可用性,确保它们能够稳定运行而不容易出现故障。可靠的节点能够为网络提供更稳定的服务。
去中心化考虑
虽然 Solana 在任何时刻只有一个 leader,但这并不意味着系统是中心化的。以下几点说明了 Solana 如何维护去中心化:
频繁轮换:
- Leader 角色快速轮换大大降低了单一实体控制网络的风险。
基于质押的概率选择:
- 成为 leader 的机会与验证者的质押量成正比,鼓励更多参与者加入网络。
大量验证者:
- Solana 网络有数百个活跃验证者,确保了足够的去中心化程度。
验证者的持续参与:
- 即使不是 leader,其他验证者也在持续验证交易和区块,维护网络安全。
惩罚机制:
- 对于行为不当的验证者(包括 leader),存在削减质押的惩罚机制。
开放参与:
- 任何人只要满足硬件要求并质押足够的 SOL,都可以成为验证者。
治理决策:
- 网络参数和升级通过去中心化的治理过程决定,而不是由单一实体控制。
Solana 要实现的目标是选择一组合适的 Leader,确保网络的安全性、效率和公平性。通过综合考虑这些因素,Solana 能够做出最佳的区块生产决策,从而推动网络的正常运行。
小结
现在在了解了基础的细节后,让我们来看看 Solana 的网络整体是如何通过 PoH 和 PoS 运作的:
1.生成交易: 用户创建并广播交易,包含交易的详细信息和数字签名。
2.PoH 链上的排序: 交易的哈希通过数字签名连接到 PoH 链上。由于 PoH 链是有序的,交易也就被排序了。
3.Validator 验证: Validator 负责验证交易的有效性,并选择哪些交易将包含在下一个区块中。Validator 的选择可能基于质押的代币数量、验证者的性能等因素。
4.交易打包成区块: Validator 选择的交易被打包成一个区块,其中包括一个特殊的块生产交易,它包含了当前 PoH 链的哈希以及其他信息。
5.区块传播和确认: 区块广播到整个网络,其他节点验证并确认区块的有效性。确认后,区块和其中包含的交易就被添加到整个区块链中。
通过这个过程,Solana 通过 PoH 链的时间有序性和 PoS 的节点验证机制,实现了交易的流转和整个区块链的更新。这样的设计使得 Solana 平均出块时间被压缩至 400 毫秒,且无需 Layer2 即拥有较高速度,手续费亦可忽略不计。
账户模型
每个帐户都可以通过其唯一地址进行识别,地址以Ed25519算法生成32个字节的公钥
来表示。
在以太坊中,我们将代码与数据、状态直接存储在智能合约中。而 Solana 账户最大的不同就是将两者分开存放在不同的账户上。所以 Solana 账户又分为程序账户和数据账户。
- 程序账户(可执行账户):存储不可变的数据,主要用于存储程序的代码(BPF 字节码)。
- 数据账户(不可执行账户):存储可变的数据,主要用于存储程序的状态。
(说明:Solana中的智能合约并不叫“智能合约”,而是“程序program”,尽管它们代表的是相似的概念。为了避免混淆,后续我们将统一使用“程序”这一术语。)
账户分类
基本账户
- 数据账户:用于存储各种数据,如用户的 SOL 余额、代币余额、NFT 元数据或游戏状态信息。
- 程序账户:存储可执行代码,类似于智能合约。一旦部署,这些程序就变成不可变的,可以被其他账户或程序调用执行。
- 原生账户:这是 Solana 内置的特殊账户,用于执行系统级的功能,如创建新账户或转账 SOL。
特殊账户类型
- PDA(Program Derived Addresses):程序派生地址。PDA 是由程序确定性生成的地址,不与任何私钥相关联。该类账户存储程序的状态,即程序执行过程中存储的数据,跟以太坊的状态是一个概念。
- ATA(Associated Token Account)账户:关联账户。它是用户与特定的 SPL(Solana Program Library)Token 代币关联的账户,地址是用户主钱包和代币铸造地址的派生,主要作用是允许用户方便管理他们持有的代币。
Solana 链上程序是只读或无状态的,即程序的账户(可执行账户)只存储代码,不存储任何状态,程序会把状态存储在其他独立的账户(不可执行账户)中。如果一个程序账户是一个数据账户的所有者,那么它就可以改变数据账户中的状态。
要点
- 帐户最多可以存储10MB的数据,这些数据主要包含由可执行程序代码或程序状态。
- 帐户需要以SOL为单位的租赁押金,与存储的数据量成正比,该押金在帐户关闭时可全额退还。
- 每个帐户都有一个程序“所有者”。只有帐户所有者的程序才能修改其数据或扣除其lamport余额。但是,任何人都可以增加余额。
- 程序(智能合约)是存储可执行代码的无状态帐户。
- 数据帐户由程序创建,用于存储和管理程序状态。
- 本机程序是Solana运行时附带的内置程序。
- Sysvar帐户是存储网络群集状态的特殊帐户。
账户定义
帐户的最大大小为10MB(10兆字节),存储在 Solana 上每个帐户上的数据具有以下结构,称为AccountInfo。
对于每个帐户AccountInfo
都包含以下字段:
- lamports: 表示账户余额,lamport 是 Solana 中的基本货币单位,类似于以太坊的wei。
- data:表示存储的内容,这是一个字节数组,可以包含任意类型的数据,如程序的状态、用户资产信息,以及存储程序的字节码等。
- owner:表示拥有或管理该账户的程序的公钥。这表示了哪个程序有权对该账户进行操作。如果账户包含的数据是可执行的,那么owner表示加载该账户的程序。
- executable:表示是否可执行,如果为true,表示该账户中的数据可以被执行,是程序账户。如果为false,表示该账户用于存储普通的数据,而不是可执行代码。
- rent_epoch:表示下一次该账户将被扣除租金的时期。Solana使用租金机制来防止账户被无限期占用而不使用,避免状态膨胀。
作为Solana账户模型的关键部分,Solana 上的每个账户都有一个指定的“所有者”,特别是一个程序。只有被指定为账户所有者的程序才能修改账户上存储的数据或扣除lamport余额。需要注意的是,虽然只有所有者可以扣除余额,但任何人都可以增加余额。
租金机制
Solana 引入了租金概念来管理账户存储和网络资源使用。租金与交易费用不同。用户支付租金以将数据存储在 Solana 区块链上。而交易费用是为了处理网络上的指令而支付的。
租金的目的:
- 防止网络存储无限增长
- 激励用户清理不再需要的账户
- 补偿验证者存储和处理数据的成本
租金计算:
- 基于账户大小(以字节为单位)
- 当前租金率:每字节每年 0.00000348 SOL(可能会随时间调整)
租金豁免:
- 账户余额超过两年租金的账户可以免租金
- 例如,如果账户大小为 1KB,约需 0.0035 SOL 来获得租金豁免
租金收取:
- 每个 epoch(约 2-3 天)收取一次
- 从账户余额中扣除
账户关闭:
- 如果账户余额不足以支付租金,账户可能被关闭
- 关闭的账户数据将被删除,剩余余额返回到一个指定账户
PDA
在Solana区块链中,PDA指的是“程序派生地址”(Program Derived Address)。这是一种特殊类型的地址,由 Solana 的程序生成,而不是由用户的私钥直接派生。PDA的主要目的是允许程序拥有和控制某些数据或资产,而不需要传统的私钥签名。
为什么需要 PDA?
在区块链中,你需要一个私钥来证明你拥有一个公钥的所有权,同时你才能签字同意这个账户的转账请求。但如果这个账户的所有者不是一个人而是一个去中心化程序,那么把私钥放在这个程序上就不是一个好主意,因为所有程序代码都在链上都是公开的,如果所有人都能看到你的私钥,那么人们就能进行一些恶意操作,比如偷走你的代币。这时我们就需要一个没有私钥的 PDA。 这样程序不需要私钥就能对一个地址进行签名操作。
私钥、公钥与助记词
Solana 和以太坊一样,有私钥、公钥、助记词三种东西。私钥是私自保管不可示人的,当我们授权某一笔交易时,我们需要通过私钥签名并“授权”该交易。私钥是一串乱码,不好记,与之对应有一串助记词。助记词可以通过算法推出私钥,所以实际上我们在使用钱包时,只要记住助记词。而我们可以通过加密算法从私钥推算出公钥。公钥是可以展示给别人看的,别人通过你的公钥给你转账,同时公钥也是程序的地址,也叫 program_id。
要注意的是我们只能从助记词通过加密算法推算出私钥,从私钥推算出公钥,而无法反向从公钥推算出私钥,从私钥推算出助记词。否则我们所有加密地址中的代币都可以被任何人控制了。这其中的过程通过ECDSA算法进行计算,如果希望了解具体的原理可以先从了解 ECDSA算法开始。但是理解 PDA 账户可以不必这么深入,让我们先把这个过程进行一个简化。
如上图所示(这是一个简化的图示),每一个 X 轴私钥会在曲线上对应一个 Y轴公钥,但是 Q 点的 公钥没有对应任何一个 X 轴上的私钥。那么这就意味着这个公钥没有对应的私钥!这意味着这个公钥不是从私钥派生/衍生(Secret Key Derived)出来的。
这就是 PDA 的原理,“程序派生地址”(Program Derived Address)是没有对应的私钥的,它是由一个程序的program_id和seed派生/衍生出来的,这也是为什么它被称之为“程序派生地址”(Program Derived Address)。有时候我们通过 program_id 和 seed 获得的公钥正好有对应的私钥,那么这种情况下我们就需要重新生成一个公钥,通常是在我们的 program_id 和 seed 之外再加上一个数字(这个数字有个专有名词叫 bump),这个数字从 255 开始,依次往下,直到生成的公钥没有私钥为止。
Solana 程序
Solana 程序,在其他链上叫做智能合约,是所有链上活动的基础。任何开发者都可以在 Solana 链上编写以及部署程序。每个程序都是一个链上账户,用于存储可执行逻辑,并组织成特定的功能,称为指令。链上的一切活动,从去中心化金融(DeFi),到非同质化代币(NFT),再到社交媒体,链上游戏,都由Solana程序所驱动。
Solana 程序模型的显着特征之一是代码和数据的分离。程序存储在程序账户中,它是无状态的,这意味着它们不会在内部存储任何状态,但它是可执行的executable,会执行相应的逻辑。相反,它们需要操作的所有数据都存储在单独的数据帐户中,这些帐户在 Transaction 交易中通过引用传递给程序账户,因为它本身是不可执行的。
Solana中将程序和状态分离的设计,使得程序可以独立于状态进行开发、测试、部署和升级,提高了程序的可重用性和可扩展性。相反在以太坊中,智能合约和状态是绑定到一起的,合约的升级是一件非常麻烦的事情,必须通过代理模式间接实现逻辑和状态的分离,才可以进行逻辑的升级,并且在新的智能合约中,新增变量的处理要非常小心,避免存储布局 Layout 冲突,覆盖掉旧变量。
程序分类
Solana程序通常可以分为以下两种:
On-chain Programs:这些是部署在 Solana 上的用户编写的程序,由开发者在 Solana 网络上根据具体业务场景开发的程序。它们可以通过升级权限进行升级,该权限通常是部署程序的帐户或者指定的其他账户。
Native programs:这些是集成到 Solana 核心模块中的程序。它们提供了验证节点(validator)运行所需的基本功能。native programs 只能通过网络范围内的软件更新进行升级。常见的原生程序有:
System Program:这是Solana最基础的原生程序之一。它负责管理新账户的创建和SOL代币在账户之间的转移。System Program的功能包括:
- 创建新账户
- 分配账户存储空间
- 转移SOL代币
- 管理账户所有权
BPF Loader Program 这个程序负责加载和执行其他程序。它将编译后的程序代码加载到Solana运行时环境中,使其可以被执行。BPF Loader的主要功能包括:
- 部署新程序
- 加载程序指令
- 管理程序升级
Vote program:这个程序在Solana的共识机制中扮演重要角色。它管理验证者节点的投票过程,包括:
- 记录验证者投票
- 管理投票账户
计算验证者的权益和奖励
Solana Program Libraries - SPL:虽然SPL不是单一的程序,而是一系列标准化程序的集合,但它们也被视为原生程序的一部分。SPL定义了许多重要的链上活动标准,包括:
- 代币创建和管理(如SPL Token程序)
- 代币交换(如SPL Token Swap程序)
- 借贷协议
- 质押池管理
- 链上域名解析服务(如SPL Name Service)
其中 System Program 这个程序负责管理建立新账户以及在两个账户之间转账SOL。Solana SPL 程序定义了一系列的链上活动,其中包括针对代币的创建,交换,借贷,以及创建质押池,维护链上域名解析服务等。
系统程序
默认情况下,所有新帐户都归系统程序所有。系统程序执行几个关键任务,例如:
New Account Creation: Only the System Program can create new accounts.
- 创建新帐户:只有系统程序可以创建新帐户。
- 空间分配:设置每个账户的数据字段的字节容量。
- 分配程序所有权:系统程序创建帐户后,可以将指定的程序所有者重新分配给其他程序帐户。这就是自定义程序获取系统程序创建的新帐户的所有权的方式。 在Solana上,“钱包”只是系统程序拥有的帐户。钱包的lamport余额是账户拥有的SOL金额。
只有系统程序拥有的帐户才能用作交易费用支付方。
数据帐户
Solana程序是“无状态的”,这意味着程序帐户仅包含程序的可执行字节码。若要存储和修改其他数据,必须创建新帐户。这些帐户通常称为“数据帐户”。
数据帐户可以存储所有者程序代码中定义的任何任意数据。
请注意,只有系统程序可以创建新帐户。一旦系统程序创建了一个帐户,它就可以将新帐户的所有权转移到另一个程序。
换句话说,为自定义程序创建数据帐户需要两个步骤:
- 调用系统程序创建一个帐户,然后将所有权转移给自定义程序
- 调用现在拥有该帐户的自定义程序,然后初始化程序代码中定义的帐户数据
此数据帐户创建过程通常抽象为单个步骤,但了解基础过程很有帮助。
如何编写程序
这里我们看一个简单的 solana 程序,这是 Rust 编写的 hello world 程序,实现了简单的日志打印。通常我们将程序写在lib.rs文件中:
// 引入 Solana 程序的相关依赖
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
msg
};
// 程序入口点
entrypoint!(process_instruction);
// 指令处理逻辑
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8]
) -> ProgramResult{
msg!("Hello, world!");
Ok(())
}
所有的程序都有一个单独的入口点,类似于 Rust 中的main函数,指令的执行就是从这里开始的(即process_instruction),参数须包括:
- program_id: pubkey (程序ID,即程序地址)
- accounts: AccountInfo数组,指令所涉及的账户集合。
- instruction_data: byte array字节数组,即指令所需的参数,该例子中并没有用到。
在实际的项目中,通常不会把所有逻辑都写在lib.rs文件中,为了更清晰的划分功能模块,大部分程序遵循以下架构:
交易与指令
在Solana上,我们发送交易与网络进行交互。交易包括一个或多个 指令,每个指令代表一个要处理的具体操作。指令的执行逻辑存储在部署到Solana网络的程序上,每个程序都存储自己的指令集。
- 交易:是一组原子性的操作,代表对区块链状态的一系列更改,包括转账代币、调用程序、更新账户状态等。每个交易都具有唯一的签名,并由一个或多个指令组成。交易费用的支付通常使用 Solana 的原生代币 SOL。
- 签名:每个交易都必须由一个或多个账户的私钥进行签名,以确保交易的身份和完整性。
- 指令:是交易中的一条具体指令,包含执行指令所需的具体数据,可以包括执行指令的程序唯一标识 program_id、账户列表、指令参数、配置信息等,用于执行一个特定的操作。
以下是有关如何执行交易的关键细节:
- 执行顺序:如果交易包含多条指令,则按照指令添加到交易中的顺序进行处理。
- 原子性:交易是原子性的,这意味着它要么完全完成,所有指令都成功处理,要么完全失败。如果交易中的任何指令失败,则不会执行任何指令。
简单来说,可以将交易视为处理一条或多条指令的请求。
交易
Solana交易由以下组成:
交易消息的结构包括:
在进行一笔转账交易后我们可以在区块链浏览器查看相关操作,就可以看见一笔转账交易包含了三个指令:
- Set Compute Unit Price: 设置单个CU的价格
- Set Compute Unit Limit:设置最多能消耗的CU的数量
- Transfer: 进行一次转账
交易大小
Solana 网络坚持 1280 字节的最大传输单元 (MTU) 大小,符合IPv6 MTU大小限制,以确保通过 UDP 快速可靠地传输集群信息。在考虑必要的标头(IPv6 为 40 字节,分段为8 字节)后,仍有 1232 个字节可用于数据包的数据,例如序列化交易。
这意味着 Solana 交易的总大小限制为 1232 字节。签名和消息的组合不能超过此限制。
- 签名:每个签名需要 64 字节。签名的数量可能会有所不同,具体取决于交易的要求。
- 消息:消息包括说明、帐户和其他元数据,每个帐户需要 32 个字节。账户和元数据的组合大小可能会有所不同,具体取决于交易中包含的指令。
交易费用
执行一个交易就需要 Compute unit。 如果你熟悉 EVM,CU(Compute unit)就像是gas fee
当然如果你不熟悉也没关系,Solana 就像个由多个节点连接组成的公共巨型计算机,节点运行者往往需要投入大量的物理资源(如CPU, GPU)来维持巨型计算机的稳定运行,为了奖励节点运行者处理链上大量的交易维持网络的稳定,gas费将做为他们贡献的补偿。
当然 CU 的存在还有一些别的目的,比如:
1.通过对交易引入实际成本,减少网络垃圾
2.设定每笔交易的最低费用金额,为网络提供长期的经济稳定性
因此,当用户在链上发送一笔交易时,往往需要支付一笔手续费用于处理交易中所包含的指令。
CU最大限制
由于每笔交易中所包含的指令调用数量和数据量的不同,每笔交易都设定了最大的CU限制——”compute budget”以确保单笔交易的数据量不会过大从而造成网络的拥堵。
每条指令的执行都会消耗不同数量的CU,在消耗了大量的CU后(即消耗的CU已经超出了”compute budget”所限定的最大CU),指令运行将停止并返回错误,从而导致交易失败。
交易费
在一笔转账交易中,我们可以看到其中包含了对于CU limit和CU price的设置。
指令Set Compute Unit Price中,可以看到compute budget 程序将每CU的价格设定为 50000 lamports (1 SOL = 1000,000,000 lamports)
指令Set Compute Unit Limit中,compute budget程序将该笔交易的CU消耗上限设置为200,000. 当一笔交易所有的指令CU消耗超过了200,000时,交易将会失败。
手续费的计算公式为: CU数量 * CU价格 = 手续费用
交易的确认
一笔交易在根据在solana网络上的确认程度可以分为以下几类主要状态:
'processed': 查询已通过连接节点获得1次确认的最新区块
'confirmed': 查询已通过集群获得1次确认的最新区块
'finalized': 查询已由集群完成的最新区块
指令
指令是链上处理特定操作的请求,是 程序中最小的连续执行逻辑单元。
在构建要添加到交易中的指令时,每个指令必须包括以下信息:
- 程序地址:指定被调用的程序。
- 账户:列出指令读取或写入的每个账户,包括使用
AccountMeta
结构体的其他程序。 - 指令数据:一个字节数组,用于指定要调用程序上的指令处理程序,以及指令处理程序所需的任何其他数据(函数参数)。
账户元
对于指令所需的每个账户,必须指定以下信息:
pubkey
:账户的链上地址is_signer
:指定是否需要该帐户作为交易的签署者is_writable
:指定是否修改帐户数据
这些信息被称为AccountMeta账户元。
通过指定指令所需的所有账户,以及每个账户是否可写,交易可以并行处理。
例如,两个不包含写入相同状态的任何账户的交易可以同时执行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。