在Solana上,状态压缩是一种创建链下数据指纹(fingerprint)(或哈希值)
并将此指纹存储在链上以进行安全验证的方法。
有效地利用Solana分类账的安全性来安全验证链下数据,确保其未被篡改。
这种压缩方法允许Solana程序和dApps使用廉价的区块链分类账空间, 而不是更昂贵的账户空间来安全存储数据。
这是通过使用一种特殊的二叉树结构(称为并发默克尔树)来实现的,
该结构为每个数据片段(称为叶子)创建一个哈希值, 将它们一起哈希,并仅将最终哈希值存储在链上。
什么是状态压缩?
简单来说,状态压缩使用“树”结构 以确定性方式将链下数据加密哈希在一起, 计算出一个单一的最终哈希值并将其存储在链上。
这些树是在这种“确定性”过程中创建的:
- 取任意数据片段
- 创建该数据的哈希值
- 将此哈希值作为树底部的叶子(
leaf)存储 - 然后将每对叶子(
leaf)哈希在一起,创建一个分支(branch) - 然后将每个分支(
branch)哈希在一起 - 不断向上攀登树并将相邻的分支哈希在一起
- 一旦到达树顶,生成最终的根哈希(
root hash)
这个根哈希(root hash)然后存储在链上,作为所有叶子数据的可验证证明。
允许任何人加密验证树内的所有链下数据,同时实际上只在链上存储最少量的数据。
因此,由于这种状态压缩,显著降低了存储/证明大量数据的成本。
默克尔树和并发默克尔树
Solana的状态压缩使用了一种特殊类型的默克尔树,
允许对任何给定树进行多次更改,
同时仍保持树的完整性和有效性。
这种特殊的树称为并发默克尔树,它有效地在链上保留了树的“变更日志”。 允许对同一棵树进行多次快速更改(即,在同一个区块中),在证明无效之前。
什么是默克尔树?
默克尔树,有时称为哈希树,是一种基于哈希的二叉树结构,
其中每个叶节点表示其内部数据的加密哈希值。
而每个非叶节点(称为分支)表示其子叶哈希的哈希值。
然后每个分支也被哈希在一起,攀登树,直到最终只剩下一个单一的哈希值。
这个最终的哈希值称为根哈希或“根”, 可以与证明路径结合使用来验证存储在叶节点中的任何数据片段。
一旦计算出最终的根哈希(root hash),
存储在叶节点中的任何数据片段都可以通过重新哈希特定叶的数据和攀登树的每个相邻分支的
哈希标签(称为证明或证明路径)来验证。
将这个重新哈希与根哈希进行比较就是对基础叶数据的验证。
如果它们匹配,则数据被验证为准确。
如果不匹配,则叶数据已更改。
只要需要, 原始叶数据可以通过简单地对新叶数据进行散列并以与原始根相同的方式重新计算根散列来更改。
然后,使用此新根哈希来验证任何数据,并有效地使先前的根哈希和先前的证明失效。
因此,对这些传统 Merkle 树进行的每次更改都必须按顺序执行。
当使用默克尔树时,更改叶数据并计算新的根哈希的过程可能是非常常见的事情!
虽然这是树的设计重点之一,但也可能导致其中一个最显著的缺点:快速变化。
什么是并发默克尔树?
在高吞吐量应用中,例如在Solana运行时环境中, 验证者可能会相对快速地接收到更改链上传统默克尔树的请求(例如,在同一个插槽中)。
每次叶数据更改仍需按顺序执行。
导致每个后续更改请求失败,因为根哈希和证明已被插槽中的先前更改请求无效化。
引入并发默克尔树。
并发默克尔树在链上的每棵树特定账户 中存储了最近更改、其根哈希及其派生证明的安全变更日志。 这种变更日志缓冲区具有最大数量的变更日志“记录”(即最大缓冲区大小)。
当验证者在同一插槽中接收到多个叶数据更改请求时, 链上的并发默克尔树可以使用这个“变更日志缓冲区”作为更可接受证明的来源。
有效地允许同一棵树在同一插槽中进行最多最大缓冲区大小次更改。显著提高吞吐量。
并发默克尔树大小调整
创建这些链上树时, 有3个值将决定您的树的大小、创建成本和对您的树的并发更改数量:
- 最大深度
- 最大缓冲区大小
- 树冠深度
最大深度
树的最大深度是从任何数据叶到树根的最大跳数。
由于默克尔树是二叉树,每个叶子仅连接到另一个叶子;作为一个叶子对存在。
因此,最大深度用于确定使用简单计算存储在树中的最大节点数(即数据片段或叶子):
nodes_count = 2 ^ maxDepth
由于必须在创建树时设置深度,因此您必须决定希望您的树存储多少数据片段。
然后使用上述简单计算,您可以确定存储数据所需的最低最大深度(maxDepth)。
示例1:铸造100个NFT
如果您希望创建一棵树来存储100个压缩NFT,我们需要至少100个叶子或100个节点。
// maxDepth=6 -> 64 nodes
2^6 = 64
// maxDepth=7 -> 128 nodes
2^7 = 128
我们必须使用最大深度(maxDepth)7以确保我们可以存储所有数据。
示例2:铸造15000个NFT
如果您希望创建一棵树来存储15000个压缩NFT,我们需要至少15000个叶子或15000个节点。
// maxDepth=13 -> 8192 nodes
2^13 = 8192
// maxDepth=14 -> 16384 nodes
2^14 = 16384
我们必须使用最大深度14以确保我们可以存储所有数据。
更高的最大深度意味着更高的成本
最大深度将是创建树时成本的主要驱动因素之一,因为您将在创建时支付此成本。
最大深度越高,可以存储的数据指纹(即哈希)越多,成本越高。
最大缓冲区大小
最大缓冲区大小实际上是指在根哈希仍然有效的情况下,树上可以发生的最大更改次数。
由于根哈希实际上是所有叶数据的单一哈希, 更改任何单个叶会使常规树所有后续尝试更改任何叶所需的证明无效。
但对于并发树,有效地存在这些证明更新的变更日志。
此变更日志缓冲区在创建时通过此maxBufferSize值设置和调整大小。
树冠深度
树冠深度,有时称为树冠大小,是为任何给定证明路径缓存/存储在链上的证明节点数量。
在对叶子(leaf)执行更新操作时,如转移所有权(例如销售压缩NFT),
必须使用完整证明路径来验证叶子的原始所有权,从而允许更新操作。
(或通过链上的并发缓冲区缓存的任何根哈希)来执行。
树的最大深度越大,执行此验证所需的证明节点越多。
例如,如果您的最大深度为14,则需要14个总证明节点来进行验证。
随着树变大,完整证明路径变长。
通常,每个这些证明节点都需要包含在每次更新交易中。
由于每个证明节点值占用32字节交易空间(类似于提供公钥),
较大的树很快就会超过最大交易大小限制。
引入了树冠。
树冠允许在链上存储一定数量的证明节点(对于任何给定证明路径)。
从而减少每次更新交易中需要包含的证明节点数量,因此保持整体交易大小低于限制。
例如,最大深度为14的树需要14个总证明节点。
具有10个树冠,仅需4个证明节点提交每次更新交易。