如果一直有关注SKALE,您应该见识到我们的高吞吐量弹性侧链,它大大解放了以太坊网络的开发者。SKALE的运算速度和吞吐量都非常棒,今天我们非常兴奋地向社区开放SKALE存储!在本文中,我们将具体介绍SKALE文件存储(FileStorage)及其工作原理。

存储的现状

以太坊的数据存储数据非常昂贵——事实上,在以太坊上存储1GB的数据要消耗大约1,250个ETH(假设gas价格是2GWei)和大约8000个满区块!考虑到这个局限,大多数开发者和团队都尽量避免将数据存储在以太坊,转而使用去中心化存储解决方案(基本都是IPFS)来满足存储需求。这类存储的工作方式如下:

  1. 将一些富类存储到IPFS上。
  2. 获取某对象的IPFS哈希值。
  3. 将IPFS哈希值存储到以太坊。

这样就能成功绕过以太坊昂贵的存储成本了!但事实并非如此——因为IPFS还没有整合存储数据的激励机制(Filecoin),您的数据可能会随时被删除。因此,企业通常运行他们自己的IPFS节点,然后将数据“Pin”到IPFS,确保它们不会消失或受限于第三方的“Pin服务”——但这样并不是真正的去中心化,是吧?

因此IPFS并不是唯一的解决方案——企业/开发者正在集成许多其他的解决方案来满足他们的存储需求。但是它们为什么需要一个单独的存储系统呢?正是基于这种思想,SKALE开始着手在以太坊创建一个成本低廉的存储层,它能够处理高达100MB的文件。

存储的难处在哪?

首先,让我们回顾一下SKALE的弹性侧链。弹性侧链由在网络中运行“节点”的“虚拟化子节点”组成。虚拟子节点来自网络中的节点(这些节点充当验证者),并作为弹性侧链参与到网络中。通过这种方式,每个验证节点都能够联合其他节点同时运行多个弹性侧链。将节点资源划分为虚拟子节点,并将这些虚拟子节点随机分配到弹性侧链中,可以大大降低整个网络中节点间相互串通的可能性。

既然如此,去中心化存储到底难在哪里?实际上,对于第二层扩展解决方案,最常见的线程是外包计算和链下存储;对于Plasma、状态通道以及弹性侧链来说,这个说法是正确的。它们每个解决方案都有其独特的处理方式以及相对应的优劣——但都受制于“数据可用性问题”。这是去中心化存储面临的核心问题。

简单介绍一下数据可用性,它是存储中最棘手的问题,并且在各方之间都会出现一定的冗余(备份、擦除编码等)。而对于这些解决方案的模块化,它们通常不提供自己的冗余层,而是让用户自己进行选择。当然,用户应该谨慎使用中心化系统(因为不符合去中心化式存储的精神)。

弹性侧链的一大优势是他们有一个内置的数据可用性协议(用户仍然可以整合自己的协议),它确保了数据至少存储在每个弹性侧链⅔的虚拟化子节点上。我们在这篇文章中有更详细的讨论和解决方案。有了这个协议,我们就能够创建和发布一个运行在SKALE上的存储系统,同时有一个经过调整的EVM为现有的以太坊开发者提供这个特性。

本文余下章节将能让你更好地理解它们是如何协作的——如果您想直接研究代码,请查看FileStorage.jsFileStorage演示的代码库。

SKALE存储的工作原理

SKALE FileStorage由几个层组成:

图片

FileStorage.js是一个简单的npm包,用户只需几行代码即可将SKALE FileStorage集成到他们去中心化应用中。这个包是Filestorage.sol的接口,它能调用预编译的智能合约(Precompiled .cpp),这些智能合约可以访问节点的原生系统。这四个层都是通过EVM上传、下载和删除文件过程中不可分割的一部分。

上传

我们通过调用uploadFile方法来上传文件,各参数如下:

  • {string} address -- 您的以太坊地址。
  • {string} fileName --上传文件的名称。
  • {number} fileSize --上传文件的大小。
  • {ArrayBuffer} fileBuffer -- 将文件中的数据转换为十六进制。
  • {boolean} [logs=false] -- 日志标记。
  • {string} [privateKey] -- 您的私钥(用于签名交易)。

确保fileBuffer的格式正确之后,一个事务就会被发送到FileStorage.sol,来保证指定帐户里面没有该名称的文件。如果该检查通过,则调用Precompiled.cpp来验证该文件是否小于100MB,同时也检验节点的文件系统是否存在足够的空间来释放连续的内存块,以存储该文件。

在可用空间不足的情况下,调用者会受到提示,建议升级其SKALE区块链或者删除当前节点上的某些文件;在存储空间足够的情况下,一部分空间将会释放出来,文件状态更新为上载中(UPLOADING)FileStorage.sol将会创建一个FileInfo结构,其中包含名称、大小以及指示文件的哪些块已被上传的布尔数组。

所有这些操作完成后,用户需要支付存储费用(大约0.01 ETH/MB),付款后文件就开始上传。上传之前,文件会被分割成1MB或更小的块,作为独立的事务上传到Filestorager.sol。因为任何人都可以调用uploadChunk函数,所以我们有必要检查文件UPLOADING的状态,文件是否属于事务发送方,以及每个块是否小于1MB并且在继续之前尚未上传。如果所有这些检查都通过了,这些块将通过Precompiled.cpp上传到节点的文件系统。如果块上传失败,事务会重新启动,然后通知用户他们的数据可能已损坏。数据块上传成功后,布尔数组中该数据块的索引将被设置为true。

注意:SKALE EVM的修改(包括blockSize)来支持上传1MB的数据块。

最后finishUpload函数将会被调用,该函数将验证文件是否处于UPLOADING状态,以及在文件状态更新为UPLOADED之前所有的块是否已经上传。FileStorage.js会将上传文件[ACCOUNT]/[FILENAME]的路径字符串返回给用户。

下载

当下载文件时,我们可以通过调用downloadFileIntoBrowser将文件下载到浏览器中,或者通过调用downloadFileIntoBuffer将文件下载到缓冲区。这两个函数的唯一区别是,前者创建一个writeStream并将文件写入本地计算机,而后者返回一个缓冲区。它们二者都包含以下参数:

  • {string} storagePath -- 文件在Filestorage中的路径。
  • {boolean} [logs=false] -- 日志标记。

下载文件时,首先要调用getFileSize函数,它根据文件路径查询文件的大小。读取文件大小后,该文件会通过readChunk函数迭代1 MB的块,该函数会验证相应的文件以及当前块在阅读之前已经成功地存储到每个节点,然后加载到缓冲区,最终返回给用户(在下载到缓冲区的情况)或写入到一个流(在下载到浏览器的情况)。

注意:除非文件大小刚好是1MB的整数倍,否则最后一个块将小于1MB。

删除

我们调用deleteFile方法来删除文件,各参数如下:

  • {string} address -- 您的以太坊地址。
  • {string} fileName -- 已上传文件的名称。
  • {boolean} [logs=false] -- 日志标记。
  • {string} [privateKey] -- 您的私钥(用于签名交易)。

这一步会执行FileStorage.sol中的deleteFile方法。它将验证文件是否存在,是否处于UPLOADINGUPLOADED状态。然后使用Precompiled.cpp中的C++ remove函数从节点的存储中删除该文件。

如果该文件已成功从每个节点的文件系统中删除,则该文件的状态将更新为EMPTY,其相应的FileInfo结构将从智能合约中删除。

好消息!

既然您已经了解了它的工作原理,接下来只需要在您的项目中添加几行代码即可集成SKALE存储!首先查看一下FileStorage.js,以FileStorage demo代码库为例,了解将它集成项目的流程!另外,请随意查看SKALE的技术概述共识概述,以便更深入地了解SKALE,以及它是如何实现20,000 TPS的。

SKALE的愿景是让创建运行全状态智能合约的低成本、高性能侧链更简单快捷。我们旨在不牺牲安全性或去中心化的前提下,为开发者提供快速与功能的高效体验。您可以通过TelegramTwitterDiscord关注我们,注册这个表格获取最新的SKALE讯息。