Solana之旅6:Solana存储费与交易剖析
Posted DongAoTony
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Solana之旅6:Solana存储费与交易剖析相关的知识,希望对你有一定的参考价值。
存储成本花销
请参考:https://blog.csdn.net/chhaozeng/article/details/116810006下面一段话
按这个信息,按SOL价格$100算,1KB的花费在:(100 x 364 x 0.01 )/ 2 = $182/年。
参考:https://blog.csdn.net/mutourend/article/details/119776339,它的计算更细一些
按这个信息,按SOL价格$100算,两年15KB的花费,应在$11。后面我们可以看到,这种算法是不对的:上面的2年租金是可豁免的rent费。
之前1500字节消费的信息找不到了。但上述两个值,可以参考一下。后者是solana的预估,我们把该工具安装,也运行一次,如下:
既然这个Solana提供了工具来预估,当存储的数据量小时,是会被豁免rent费用的,但到1.5GB时,真正应付出的费用为:1500000000 x 0.00000348 = 52200 - 10440 = 41760 SOL。继续计算可以得到:1.5MB 大概一年要花费$4176(按1SOL = $100),那1.5K约$4.176,差不多一年¥30。
在网络不通畅时,会报错:
从以上信息里,可以看到以下信息:
- solana rent 去访问的mainnet-beta,也就是Solana主网;
- 该访问应是一个ReadOnly的请求:
- 该请求是没有fee的,因为测试我没有SOL。
交易
关键的概念或名词
账户(Accounts)
Solana内部帐户用于存储状态。它们是开发Solana的重要组成部分。
事实(Facts)
- 账户被用来存储数据
- 每个账户都有唯一的地址
- 帐户的最大大小为10mB
- 程序派生地址账户(PDA accounts)的最大大小为 10kb
- PDA accounts 可被用来基于程序的利益签名
- 账户的尺寸是静态的
- 账户的数据存储需要支付存储费
- 默认帐户所有者是系统程序
进一步理解
账户模型
在Solana系统上,存在3种账户:
- 数据账户,用于存储数据;
- 程序账户,用于存储可执行程序;
- 原生账户(Native accounts),它们代表着Solana系统中的诸多原生程序(用来维护系统运转、质押和投票等)
在数据账户中, 又分两份种类型:
- 系统拥有的账户
- PDA (Program Derived Address) 账户,也就是程序派生账户
每个帐户都有一个地址(通常是公钥)和一个所有者(程序帐户的地址)。帐户存储的完整字段列表如下所示。
字段 | 描述 |
---|---|
lamports | 该账户所拥有的SOL原生代币数额(以lamports为单位) |
owner | 该账户的程序owner |
executable | 表明该账户是否可处理instructions |
data | 该账户存储的原始(raw)字节数组 |
rent_epoch | 存储费将要缴纳的epoch |
下面是几条关于所有权的重要规则:
- 只有数据帐户的所有者owner才能修改其数据和借记的lamports
- 任何人都可以将lamports存入数据账户
- 如果帐户的数据为零,帐户的所有者可以为其分配一个新的所有者
程序帐户不存储状态。
例如,如果您有一个计数器程序,允许您递增计数器,则必须创建两个帐户,一个帐户用于存储程序代码,另一个帐户用于存储计数器。
为了防止账户被删除,你必须支付租金(存储费)。
存储费(Rent)
在帐户上存储数据需要花费SOL维护,这笔费用由存储数据数据的账户支付存储费来获得。如果您的帐户中有相当于2年租金的最低余额,您的帐户将免交租金。您可以通过关闭帐户和发送lamports回您的钱包来收回这笔账户上的存款。
存储费在以下两个不同的情况下,进行结算或缴纳:
- 当该账户被一个交易引用了
- 一个epoch到了
由系统账户收取的存储费的一部分会被销毁,而剩下的部分会在每个槽的末尾分配给投票账户。
如果账户没有足够的钱支付存储费,账户将被释放,数据将被删除。
程序(Programs)
任何开发人员都可以在Solana区块链上编写和部署程序。程序(在其他协议上被称为智能合约)是链上活动的基础,支持诸如:DeFi、NFTs、社交媒体、游戏的任何东西。
事实(Facts)
-
程序处理来自最终用户和其他程序的指令(instructions)
-
所有程序都是“无状态”的: 它们交互的任何数据都存储在独立的帐户中,这些账户将通过指令(instructions)传给程序
-
程序本身存储在帐户中标记为:
executable
-
所有程序都属于BPF Loader,并由Solana Runtime执行
-
backend开发人员通常使用Rust或c++编写程序,但也可以选择任何语言,只要符合BPF的LLVM规范就好
-
所有的程序都有唯一的入口点,从这里将进行指令(instruction )的处理 (例如,
process_instruction
); 指令里的参数总是包括:
program_id
:pubkey
accounts
:array
,instruction_data
:byte array
进一步理解
与大多数其他区块链不同,Solana完全将代码与数据分离。所有与程序交互的数据都存储在独立的帐户中,并通过指令作为引用传入。这个模型允许一个通用程序跨不同的帐户操作,而不需要额外的部署。这种模式的常见示例可以在原生程序和SPL程序中看到。
原生程序 & Solana程序库
Solana配备了许多程序,作为链上互动的核心构件。这些程序被分为原生程序和solana程序库(SPL)程序(简称SPL程序)。
原生程序提供了操作验证器所需的基本功能。在这些程序中,最著名的是System Program(系统程序),它负责管理新帐户和在账户之间转移SOL。
SPL程序支持许多链上活动,包括创建、交换和出借代币,以及生成股权质押池和维护链上命名服务。SPL代币Program可以直接通过CLI调用,而其他像关联代币帐户Program通常是由自定义程序组成的。
编写链上程序
程序通常使用Rust或c++开发,但也可以使用任何支持LLVM BPF后端的语言开发。Neon Labs和Solang的最新举措,使得程序可与EVM兼容,并允许开发者用solidity编写程序。
大多数基于rust的程序遵循以下架构:
文件 | 描述 |
---|---|
lib.rs | Registering modules |
entrypoint.rs | Entrypoint to the program |
instruction.rs | Program API, (de)serializing instruction data |
processor.rs | Program logic |
state.rs | Program objects, (de)serializing state |
error.rs | Program-specific errors |
最近,Anchor已经成为开发程序的流行框架。Anchor是一个固执的框架,类似于Ruby on Rails,它减少了样板文件,简化了基于rust的开发的(反)序列化过程。
在将程序部署到测试网或主网之前,通常在Localhost和Devnet环境中开发和测试程序。Solana支持以下环境:
集群环境 | RPC连接URL |
---|---|
Mainnet-beta | https://api.mainnet-beta.solana.com |
Testnet | https://api.testnet.solana.com |
Devnet | https://api.devnet.solana.com |
Localhost | Default port: 8899 (e.g. http://localhost:8899, http://192.168.1.88:8899) |
一旦程序部署到一个环境,客户端可以通过RPC连接到各自的集群,然后与该链上程序交互。
部署程序
开发人员可以通过CLI部署他们的程序:
solana program deploy <PROGRAM_FILEPATH>
当一个程序被部署时,它被编译成ELF共享对象(包含BPF字节码)并上传到Solana集群。程序是基于账户来运行(就像Solana上的其他所有东西一样),只是这些账户被标记为“可执行”并分配给BPF Loader。此帐户的地址称为’ program_id ',该地址将用于在所有未来的交易中引用程序。
Solana支持多个BPF加载器,最新的是可升级BPF加载器。BPF Loader负责管理程序的帐户,并通过’ program_id ‘让客户端可访问它。所有的程序都有唯一的入口点,在那里客户端的指令,将被处理(例如’ process_instruction ')。指令里总是包含以下的参数:
program_id
:pubkey
accounts
:array
,instruction_data
:byte array
一旦被调用请求访问,程序将由Solana Runtime执行。
交易
客户端可以通过向集群提交事务来调用programs。一个事务可以包含多个指令,每个指令都针对自己的程序。当一个事务被提交时,Solana Runtime将按照顺序和原子的方式处理它的指令。如果指令的任何一部分失败,整个事务就会失败。
事实(Facts)
- 指令是Solana里的最基本的操作单元
- 每个指令包含:
program_id
对应着程序accounts
数组,里面存放着读出和写入的账户地址instruction_data
字节数组,它具体与关联的程序定义并解析
- 多个指令可以打包到一笔交易中
- 每个交易包含:
accounts
数组 ,里面存放着读出和写入的账户地址- 不少于一条的指令
- 最近的
blockhash
- 不少于一条的签名
- 按照顺序和原子的方式处理交易中的指令
- 如果指令的任何一部分失败,则整个交易失败。
- 交易被限制为不超过1232字节
进一步理解
Solana运行时需要指令和交易来指定他们打算读取或写入的所有帐户的列表。通过提前要求这些帐户,运行时能够整合所有交易,让它们得以高效地并行执行。
当一个交易被提交给一个集群时,运行时将按顺序和原子方式处理它的指令。对于每条指令,接收程序将解释其数据数组并对其指定的帐户进行操作。程序要么成功返回,要么返回错误代码。如果返回错误,整个事务将立即失败。
任何旨在从帐户借出钱或修改其数据的交易都需要帐户持有人(是holder,不是Onwer)的签名。任何将被修改的帐户都被标记为“可写”。只要交易费用支付人支付了必要的存储费和交易费用,帐户就可以在未经持有人许可的情况下存入(代币)。
在提交之前,所有的事务必须参考一下最近的blockhash。blockhash用于防止重复和消除陈旧的交易。一个交易的blockhash的最大使用时间是150个块,截止目前,这个时间大约是1分19秒。
交易费
Solana网络要收2种类型的费用:
- 交易费,用于传播交易 (aka “gas fees”)
- 存储费,用于在链上存储数据
在Solana中,交易费用是确定的: 它没有收费市场(在这个市场中,用户可以支付更高的费用来增加他们被纳入下一个区块的机会)的概念。在撰写本文时,交易费用仅由所需签名的数量决定(即:’ lamports_per_signature '),而不是根据所使用的资源数量。这是因为目前所有事务的硬上限为1232字节。
所有交易都需要至少一个“可写”帐户来签署交易。一旦提交,首先被序列化的可写签名者帐户将是交易费用支付人。无论交易成功或失败,该账户都将支付交易的费用。如果手续费支付人没有足够的余额支付交易费用,交易将被取消。
在撰写本文时,所有交易费用的50%由生成块的验证器获得,而剩余的50%则被销毁。这个结构可以激励验证者在leader时间表的时间段内处理尽可能多的交易。
程序派生地址 (PDAs)
程序派生地址(pda)是被设计为由特定程序控制的帐户的所在地。使用pda,程序可以通过编程方式对某些地址签名,而不需要私钥。pda作为跨程序调用的基础,它允许Solana应用程序相互组合。
事实(Facts)
- pda是看起来像公钥的32字节字符串,但没有相应的私钥
findProgramAddress
将确定性地从programId和种子(字节集合)派生出一个PDA- 一个凹凸(一个字节)是用来基于ed25519椭圆曲线推算出PDA
- 程序可以通过提供种子和凹凸调用invoke_signed,来为其pda签名
- PDA只能由生成它的程序来签名
- 除了允许程序对不同的指令进行签名外,pda还为索引帐户提供类似hashmap的接口。
进一步理解
pda是开发Solana项目的重要组成部分。使用pda,程序可以为帐户签名,同时保证没有外部用户也可以为同一帐户生成有效的签名。除了为帐户签名外,程序还可以修改其pda的持有帐户。
Image courtesy of Pencilflip
生成PDAs
要理解pda背后的概念,把pda当作不是技术上创建的,而是找到的,可能理解起来更容易。pda基于种子(例如字符串 " vote_account ")和程序id的组合而生成。然后,种子和程序id的组合通过sha256哈希函数运行,以查看它们是否生成了位于ed25519椭圆曲线上的公钥。
在通过哈希函数运行我们的程序id和种子时,我们有大约50%的机会最终得到一个位于椭圆曲线上的有效公钥。在这种情况下,我们只是简单地添加一些东西来欺骗我们的输入,然后再次尝试。这种**蒙混因素( fudge factor)**的技术术语是“颠簸(bump )”。在Solana中,我们从bump = 255开始,然后简单地通过bump = 254、bump = 253等进行迭代,直到我们得到一个不在椭圆曲线上的地址。这似乎是最基础的,但一旦发现,它就给我们提供了一种确定性的方法,可以一遍又一遍地推导出相同的PDA。
与PDAs交互
当生成一个PDA时,’ findProgramAddress ‘将返回地址和用于将地址踢出椭圆曲线的凸起(bump)。有了这个bump,程序就可以对任何需要它的PDA的指令进行签名。为了进行签名,程序应该传递指令、帐户列表以及用于派生PDA的 bump,以便得到’ invoke_signed ‘。除了对指令进行签名之外,pda还必须通过’ invoke_signed '对自己的创建进行签名。
当使用pda构建时,它通常是将bump seed存储在帐户数据本身。这使得开发人员可以轻松地验证PDA,而不必将bump作为指令参数传入。
剖析交易
程序执行从一个transaction被提交到集群开始。Solana运行时将启动一个链上程序,以顺序和原子的方式处理交易中包含的每个指令。
本节介绍交易的二进制格式。
交易格式
交易包含着一个compact-array的签名,后跟一个message。签名数组中的每一项都是给定消息的数字签名。Solana运行时验证签名的数量是否与message header前8位的数字相匹配。它还验证每个签名是否由与消息的帐户地址数组中相同索引处的公钥对应的私钥签名。
整体的交易格式如下图:
)]
签名格式
每个数字签名都是ed25519二进制格式,会占用64字节。
消息格式
消息体包含一个头,后跟一个[帐户地址]的紧凑数组(compact-array),后面是一个最近的blockhash,后面是一个[instructions]的紧凑数组。
消息头格式
消息头包含三个无符号的8位值。第一个值是包含交易的所需签名的数量。第二个值是对应的只读帐户地址的数量。消息头中的第三个值是不需要签名的只读帐户地址的数量。
账户地址格式
需要签名的地址出现在帐户地址数组的开头,首先是请求读写访问的地址,然后是只读帐户。不需要签名的地址跟在需要签名的地址之后,同样是先有读写帐户,然后是只读帐户。
Blockhash格式
一个blockhash包含一个32字节的SHA-256哈希。它用来指示客户端最后一次查看分类账的时间。当blockhash太旧时,验证器将拒绝交易。
指令格式
一个指令包含一个程序id索引,后跟一个帐户地址索引的紧凑数组,再后跟一个不透明的8位数据的紧凑数组。程序id索引用于识别链上能够解释不透明数据的程序。账户数组中的索引是无符号8-bit的值,用来索引消息体中帐户地址数组中帐户地址的。帐户地址索引每一个都是无符号8-bit数值。
紧凑数组(Compact-Array)格式
紧凑数组序列化为数组长度,后面跟着每个数组项。数组长度是一种特殊的多字节编码,称为compact-u16。
Compact-u16格式
compact-u16是16位的多字节编码。第一个字节包含该值的低7位中的低7位。如果该值高于0x7f,则将该字节的最高位置1,并将该值的接下来的7位放入第二个字节的低7位中。如果该值高于0x3fff,则则将第二字节的最高位置1,并将该值的其余2位放入第三个字节的低2位。
账户地址格式
帐户地址是32字节的任意数据。当地址需要数字签名时,运行时将其解释为ed25519密钥对的公钥。
以上是关于Solana之旅6:Solana存储费与交易剖析的主要内容,如果未能解决你的问题,请参考以下文章
Solana 链中的打字稿错误。 (测试网中的 Solana 交易)