Solana 开发基础简介
Solana 开发基础简介
最近沉迷Solana生态,并且基于已有的链上合约写了个Dapp(Anyswap),感觉得深入一下solana链基础才能配合后续更好的开发,所以本文想用大白话来总结一下solana开发需要的知识。
本文基于solana官方文档而来
Solana 简介
区块链发展了这么多年,有两个链是人们在构建过程中绕不过去的坎儿,其中之一是BTC,另外就是ETH。
BTC自然不用多说,我想聊的更多的是ETH,ETH自2014年横空出世以来解决了人们在链上的很多问题,特别是智能合约的出现,让DEFI、NFT等概念大火。
ETH发展了这么多年,可以说基于ETH发展出的生态已经让ETH基于区块链开发的垄断地位了,但是也有很多人对ETH的高Gas不满,是的一个链用的人多了Gas自然就贵了,这也是Solana崛起的原因之一——Gas非常便宜,正常情况下不到一美分。但如果后续Solana生态发展成ETH的样子,Gas是什么样子也说不准,但目前来说,Solana生态还在蓬勃发展。
另外Solana的优点就是的链上程序是二进制应用,C、C++、Rust都可以编写(最近出了个Python的解释器有待观望),非常方便已有这些编程语言基础的程序员入门。之后就是确认时间非常短400ms,就可以在区块中确认,几乎没有等待时间。
Solana 开发入门
简而言之Solana的开发分为两部分:
- 链上程序开发
- 链下Dapp开发
链上程序是公开的大家都可以使用的合约程序,链下Dapp是可以给用户使用的界面(普通用户自然不可能与链上程序直接交互)。
账户模型
账户是Solana的核心要素,可以理解为unix世界里的文件。
关键点:
- 账户最多存储10MB包含可执行程序、程序状态。
- 账户需要SOL为单位的租金(rent)与存储的数据量成正比,租金可以在账户关闭时全额退还。(与BTC的UTXO模式不同)
- 账户都有一个程序“所有者”,只有账户所有者程序才能修改余额,任何人都可以增加余额。
- 程序账户是存储可执行代码的无状态帐户。
- 数据帐户由程序创建,用于存储和管理程序状态。
- 本机程序是Solana运行时附带的内置程序。
- Sysvar帐户是存储网络群集状态的特殊帐户。
账户结构:
data: 存储帐户状态的字节数组。如果帐户是一个程序(智能合约),则存储可执行程序代码。此字段通常称为“帐户数据”。
executable :指示帐户是否为程序的布尔标志
lamports :账户余额的数字表示,单位为lamports,即 SOL 的最小单位(1SOL=10e9 lamports)(为何是使用lamports,原因是solana为了避免链上程序处理浮点数设计的,所有链上程序交互都是以整数计算)。
owner: 指定拥有帐户程序的公钥(程序 ID)。
程序账户的owner的所有者才可以修改账户上的存储数据或转账。
本机程序
本机程序是solana链的原生程序,为网络提供核心功能。在Solana上开发自定义程序时,通常会与两个本机程序进行交互,即系统程序和BPF加载程序。
默认情况下所有新账户都归系统程序所有,系统程序执行几个关键任务,例如:
- 创建新帐户:只有系统程序可以创建新帐户。
- 空间分配:设置每个账户的数据字段的字节容量。
- 分配程序所有权:系统程序创建帐户后,可以将指定的程序所有者重新分配给其他程序帐户。这就是自定义程序获取系统程序创建的新帐户的所有权的方式。 在Solana上,“钱包”只是系统程序拥有的帐户。钱包的lamport余额是账户拥有的SOL金额。
BPFLoader程序
BPF加载程序是被指定为网络上所有其他程序(不包括本机程序)的“所有者”的程序。它负责部署、升级和执行自定义程序。
Sysvar账户
Sysvar帐户是位于预定义地址的特殊帐户,用于提供对集群状态数据的访问。这些帐户会使用有关网络群集的数据动态更新。您可以在此处找到Sysvar帐户的完整列表。
自定义程序
在 Solana 上,“智能合约”被称为程序。程序是包含可执行代码的帐户,并由设置为 true 的“可执行”标志指示。
程序部署时理论上会产生三个账户:
- 程序账户: 链上程序主账户,存储可执行账户的地址(即存储已编译的程序代码的账户)和程序的更新权限(有权对程序账户进行更改的地址)。
- 程序可执行数据帐户: 第一点提到的包含程序的可执行字节码的帐户。
- 缓冲区帐户:在主动部署或升级程序时存储字节码的临时帐户。该过程完成后,数据将传输到程序可执行数据帐户,并关闭缓冲区帐户。 例如,以下是指向令牌扩展程序帐户及其对应程序可执行数据帐户的 Solana Explorer 的链接。
数据帐户
Solana程序是“无状态的”,这意味着程序帐户仅包含程序的可执行字节码。若要存储和修改其他数据,必须创建新帐户。这些帐户通常称为“数据帐户”。
数据帐户可以存储所有者程序代码中定义的任何任意数据。
交易与指令
在Solana上我们发送交易与网络进行交互,一个交易可以包含多个指令,每个指令代表一个要处理的具体操作。上述提到的程序账户就可以处理Solana网络的交互逻辑。
关键点:
- 执行顺序:如果交易包含多条指令,则按照指令添加到交易中的顺序进行处理。
- 原子性:交易是原子性的,这意味着它要么完全完成,所有指令都成功处理,要么完全失败。如果交易中的任何指令失败,则不会执行任何指令。
简单说,一条交易可以视为处理一条或多条指令的请求。比如一个指令是发送交易给A,另一个指令是与B程序进行交互...
每次交易最大为1232个字节
Transcation由以下组成:
- 签名(sign):交易中包含的签名数组。
- 消息(message):要原子性处理的指令列表。
消息包含
- 消息头:指定签名者和只读账户的数量。
- 账户地址:交易指令所需的账户地址数组。
- 最近区块哈希:充当交易的时间戳。
- 指令:要执行的指令数组。
Solana网络坚持 1280 字节的最大传输单元 (MTU) 大小。符合Ipv6大小限制,确保UDP可以快速可靠传递集群消息。在考虑必要的标头(IPv6 为 40 字节,分段为8 字节)后,仍有 1232 个字节可用于数据包的数据,例如序列化交易。
其中签名需要64字节。签名的数量可能会有所不同,具体取决于交易的要求。
消息:消息包括说明、帐户和其他元数据,每个帐户需要 32 个字节。账户和元数据的组合大小可能会有所不同,具体取决于交易中包含的指令。
消息
消息头指定交易的帐户地址数组中包含的帐户的权限。它由三个字节组成,每个字节包含一个 u8 整数,它们共同指定:
- 交易所需的签名数量。
- 需要签名的只读账户地址数量。
- 不需要签名的只读账户地址数量。
交易消息包括一个数组,其中包含交易内指令所需的所有账户地址。
最近区块哈希:交易区块哈希的最长期限为 150 个区块(假设区块时间为 400 毫秒,则为大约1分钟)。如果交易的区块哈希比最新的区块哈希早 150 个区块,则认为该交易已过期。这意味着未在特定时间范围内处理的交易将永远不会被执行。
指令数组:与帐户地址数组非常相似,这个紧凑的数组以指令数的紧凑型u16编码开始,然后是指令数组。数组中的每条指令都指定以下信息:
- 程序 ID:标识将处理指令的链上程序。这表示为指向帐户地址数组中的帐户地址的 u8 索引。
- 账户地址索引的紧凑数组:指向指令所需的每个账户的账户地址数组的 u8 索引数组。
- 不透明u8数据的紧凑数组:特定于所调用程序的 u8 字节数组。此数据指定要在程序上调用的指令以及指令所需的任何其他数据(例如函数参数)。
交易费用
Solana区块链有几种不同类型的费用和成本开销,这些费用是使用该无准入要求的网络所产生的。它们可以分为几种特定类型:
- 交易费用 - 给验证者处理交易/指令的费用
- 优先级费用 - 一种可选费用,用于提高交易被处理的顺序
- 租金 - 为了在链上存储数据而预留的余额
交易费用:Solana上的验证节点需要被激励,所以每笔交易都需要给构建Solana的去中心化节点费用。
目前,基础Solana交易费设定为每个签名5k lamports的静态值。在这个基础费用之上,可以添加任何额外的优先级费用。
为什么要交易费?
- 为提供节点资源的验证者补偿
- 防止spam垃圾交易横行
- 通过协议设定的每笔交易的最低费用金额,为网络提供长期的经济稳定性
基础经济模型设计:
- 每笔交易费用的固定比例(最初是50%)被烧毁(销毁),剩余的部分归当前处理交易的领导者所有。
- 预先设置好的全局通货膨胀率为分配给Solana 验证者的奖励提供了来源
费用收取
关键点
交易必须至少有一个账户已签署交易并是可写入的。这些可写入的签名者帐户首先在帐户列表中序列化,其中第一个始终用作“费用支付者”。
- 交易被处理之前,费用支付者账户的余额扣除做为交易费,如果余额不足则交易终止并导致交易失败
- 如果交易指令中有任何指令失败,则依然扣除交易费
计算预算
为了防止计算资源的滥用,每个交易都被分配了一个“计算预算”。这个预算指定了有关计算单元的详细信息,包括:
- 交易可能执行的不同类型操作所关联的计算成本(每个操作消耗的计算单元)
- 交易可以消耗的最大计算单元数量(计算单元限制)
- 交易必须遵守的操作边界(如账户数据大小限制)
账户数据大小限制
交易可以通过包含一个SetLoadedAccountsDataSizeLimit指令(不得超过运行时的绝对最大值)来指定它允许加载的账户数据的最大字节数。如果没有提供SetLoadedAccountsDataSizeLimit,则交易默认使用运行时的MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES 值。
计算单元
链上每笔交易执行的所有操作都需要验证者在处理时消耗不同数量的计算资源(计算成本)。这些资源消耗的最小度量单位称为“计算单元”。
在处理交易时,计算单元会被链上执行的每条指令(消耗预算)逐步消耗。由于每条指令执行不同的逻辑(写入帐户、CPI、执行系统调用等),因此每条指令可能消耗不同数量的计算单元。
以下是一些产生计算成本的常见操作:
- 执行指令
- 在程序之间传递数据
- 执行系统调用
- 使用系统变量
- 使用 msg! 宏记录日志
- 记录公钥
- 创建程序地址(PDAs)
- 跨程序调用(CPI)
- 加密操作
计算单元限制
每笔交易都有一个可以使用的最大计算单元数 (CU),称为“计算单元限制”。每个交易,Solana 运行时的绝对最大计算单元限制为140万CU,并将默认请求的最大限制设置为每条指令每条指令20万CU。
交易可以通过包含单个 SetComputeUnitLimit 指令来请求更具体和最佳的计算单元限制。可以是上限或下限。但它可能永远不会要求高于每笔交易的绝对最大限制。
虽然交易的默认计算单位限制在大多数情况下适用于简单交易,但它们通常不是最佳的(对于运行时和用户)。对于更复杂的交易,例如调用执行多个 CPI 的程序,您可能需要为交易请求更高的计算单位限制。
请求交易的最佳计算单元限制,对于帮助您降低交易费用并帮助在网络上更好地安排交易至关重要。钱包、dApp 和其他服务应确保其计算单元请求是最佳的,以便为其用户提供最佳体验。
优先级费用
作为计算预算的一部分,运行时支持交易支付一个可选的费用,称为“优先级费用”。支付这笔额外费用有助于提高交易在处理时优先于其他交易的方式,从而缩短执行时间。
租金
存入每个Solana 账户以使其相关数据在链上可用的费用称为“租金”。这笔费用在每个账户的正常 lamport 余额中扣留,并在账户关闭时收回。
垃圾回收
未保持 lamport 余额大于零的帐户将在称为垃圾回收的过程中从网络中删除。此过程有助于减少不再使用/维护的数据的网络范围存储。
在交易成功将帐户余额减少到精确 0 后,运行时会自动进行垃圾回收。任何试图将账户余额降低到其租金豁免最低余额(不完全为零)的交易都将失败。
程序
在 Solana 生态系统中,“智能合约”被称为程序。每个程序都是一个链上账户,用于存储可执行逻辑,并组织成特定的功能,称为指令。
关键点:
- 程序是包含可执行代码的链上账户。此代码被组织成不同的函数,称为指令
- 程序是无状态的,但可以包括创建新账户的指令,这些账户用于存储和管理程序状态。
- 程序可以通过升级权限设定的账户来更新。当升级权限设置为null时,程序变为不可变。
- 可验证的构建使用户能够验证链上程序是否与公开可用的源代码匹配。
编写Solana程序: Rust、Anchor框架。
升级Solana程序
链上程序可以由被指定为“升级权限”的账户直接修改,该账户通常是最初部署程序的账户。
如果升级权限被撤销并设置为 None,则程序变为不可变,无法再更新。
可验证程序
Solana 开发者社区引入了支持可验证构建的工具,使开发者和用户能够验证链上程序是否准确反映了他们公开共享的源代码。
搜索已验证的程序: 要快速检查已验证的程序,用户可以在 SolanaFM浏览器上搜索程序地址并转到到“验证”网页标签。在这里查看经过验证的程序示例。
验证工具: Ellipsis Labs的Solana可验证构建CLI使用户能够独立地验证链上程序与发布的源代码。
Anchor中对可验证构建的支持: Anchor提供了内置的可验证构建支持。详细信息可以在Anchor文档 中找到。
伯克利包过滤器 (BPF)
Solana利用 LLVM编译器基础设施 将程序编译成可执行和可链接格式 (ELF)文件。这些文件包括Solana程序的伯克利包过滤器 (eBPF)字节码的修改版本,称为“Solana字节码格式” (sBPF)。
LLVM 的使用使 Solana 能够潜在地支持任何可以编译到 LLVM 的 BPF 后端的编程语言。这大大增强了 Solana 作为开发平台的灵活性。
程序派生地址
这个后续用到了再说
etc
CPI 跨程序调用
跨程序调用 (CPI) 是指一个程序调用另一个程序的指令。此机制允许 Solana 程序具有可组合性。
您可以将指令视为程序向网络公开的 API 端点,将 CPI 视为一个 API 在内部调用另一个 API。
当一个程序向另一个程序发起跨程序调用(CPI)时:
- 初始交易中调用程序A的签署者权限会被延申给程序B。
- 被调用的程序B也可以进一步对其他程序进行CPI,深度最多为4(例如: B->C,C->D)。
- 这些程序可以代表源自其程序ID的程序PDAs进行“签名”
Solana链上代币
代币是代表不同类别资产所有权的数字资产。 代币化使产权的数字化成为可能,是管理可替代和不可替代资产的基本组成部分。
- 同质化代币代表相同类型和价值的可互换和可分割资产(例如 USDC)。
- 非同质化代币(NFT)代表不可分割资产(例如艺术品)的所有权。
本节将介绍代币在 Solana 上如何表示的基础知识。这些被称为 SPL (Solana 程序库) 代币
- 代币程序包含与网络上的代币(同质化和非同质化)交互的所有指令逻辑。
- 铸币账户代表一种特定类型的代币,并存储有关代币的全局元数据, 例如总供应量和铸币权限(授权创建代币新单位的地址)。
- 代币账户跟踪特定地址拥有特定类型代币(铸币账户)的单位数量的个人所有权。
代币程序
代币程序 包含与网络上的代币(同质化和非同质化)交互的所有指令逻辑。 Solana上的所有代币实际上是代币计划拥有的 数据账户。
一些常用的说明包括:
- InitializeMint:创建一个新的铸币账户来代表一种新型的代币。
- InitializeAccount:创建一个新的代币账户来持有特定类型代币(铸币厂)的单位。
- MintTo:创建特定类型代币的新单位,并将其添加到代币帐户。这增加了代币的供应,并且只能由铸币账户的铸币厂机构完成。
- Transfer:将特定类型代币的单位从一个代币账户转移到另一个代币账户。
Mint账户
Solana 上的代币由代币计划拥有的铸币账户 的地址唯一标识。 此帐户实际上是特定代币的全局计数器,并存储以下数据:
供应量:代币的总供应量
代币精度:代币小数精度位数
铸币权限:被授权创建代币新单位的账户,从而增加供应量
冻结权限:有权冻代币从“代币账户”转移的账户
代币账户
要跟踪特定代币的每个单位的个人所有权,必须创建代币计划拥有的另一种类型的数据帐户。此帐户称为 代币帐户 。
账户上最常引用的数据包括:
- 铸造: 代币账户持有的代币类型
- 所有者:被授权将代币转出代币账户的账户
- 金额:代币账户当前持有的代币单位数量
为了让钱包拥有某个代币的单位数量,它需要为特定类型的代币(铸币)创建一个代币账户,将钱包指定为代币账户的所有者。一个钱包可以为同一类型的代币创建多个代币账户,但每个代币账户只能由一个钱包拥有,并持有一种代币的数量。
关联代币账户
为了简化为特定铸币和所有者查找代币账户地址的过程,我们经常使用关联代币账户。
关联代币账户是一种代币账户,其地址是使用所有者的地址和铸币账户的地址确定派生的。 您可以将关联代币账户视为特定铸币厂和所有者的“默认”代币账户。
重要的是要了解关联的代币账户不是不同类型的代币账户。它只是一个具有特定地址代币帐户。
群集和公共 RPC 端点
Solana区块链有几个不同的验证节点组,称为集群。每个集群在整体生态系统中承担不同的任务,并包含专用的API节点来处理其各自集群的JSON-RPC请求。
集群内的各个节点由第三方拥有和操作,每个节点都有一个公共端点。
版本化交易
版本化交易是新的交易形式,允许在 Solana 运行时中使用附加功能,包括地址查找表。
虽然不需要对链上程序进行更改来支持版本化交易的新功能(或向后兼容),但开发人员将需要更新其客户端代码以防止由于不同交易版本而导致的错误。
Solana 运行时支持两种交易版本:
- legacy - 旧的交易版本,没有额外的功能
- v0 - 添加了对地址查找表的支持
地址查找表
作用: 由于 Solana 区块链上的每笔交易都需要列出作为交易一部分进行交互的每个地址,这种列表实际上限制每笔交易 32 个地址。使用地址查找表的话,可以将限制提高到每笔交易 256 个地址。
交易确认及到期
对于许多新开发者来说,在构建应用程序时,与交易确认相关的问题很常见。本文旨在加深对 Solana 区块链上确认机制的整体理解,同时提供一些推荐的最佳实践。
etc...