一份 GCS 热副本、两份 Filecoin 冷归档,加上每次读取都必须对上的链上指纹。已在 Calibration 测试网验证,尚未进入生产。
采购训练数据的团队,会在交易完成后很久还在使用这些文件。那时,原来的下载地址可能已经失效,后台服务也可能经历过重启。团队需要完整取回文件,并确认内容没有被替换或损坏。如果没有任何校验,错误的文件可能被交付出去,而且无人察觉。
我们为这种情况构建并测试了一条存储路径。它会把每个交付的数据版本保存在三个地方:一份 Google Cloud Storage(GCS)热副本,以及两个相互独立的 Filecoin 存储提供者(Provider)上的冷归档。链上内容指纹 PieceCID 会在返回之前核对每一份副本。
当前状态: 本文结果来自 Filecoin Calibration 测试网中的测试数据和受控故障注入。基于 XnY 构建的首个应用 Humanbased 目前只使用 GCS 存储生产文件;Filecoin 冷归档和按授权交付解密密钥尚未作为生产环境的自助能力开放,因此客户数据尚未自动获得这套保护。本文任何结果都不是生产环境的吞吐、延迟或容量数据。
三层存储,各司其职
- GCS Origin:热副本,每次读取都先访问这里。²
- 两个 Filecoin Provider:冷归档。CID 按内容而不是位置引用数据。³ 每份副本都与一条链上记录绑定,因此能否恢复不只依赖某一家存储服务商的说法。
- 链上内容指纹:链上只保存标识和哈希,不保存文件本身。¹⁵ 任何一个字节变化,SHA-256 或 PieceCID 校验都会失败,这份副本会被拒绝。
指纹能发现篡改,却不能阻止篡改。无论从哪个地址取回,副本都必须与最初的哈希一致。⁴ 指纹也不能恢复已经丢失的字节,所以需要两份归档。
把文件整理成可恢复的数据包
恢复一份文件不只需要字节本身,系统还要知道这些字节属于哪个数据版本,以及应该如何重新组合。每份归档都是一个数据包,包含外层索引、版本快照、记录分块顺序、大小和摘要的加密清单,以及数量随文件大小变化的加密数据分块。测试使用的数据包共有 6 个组件,其中包括 3 个加密数据分块。
每个组件都写入两个 Provider,因此完整归档的链上存储记录数是组件数的两倍,测试数据包对应 12 条。一笔交易可以批量登记其中多条。
重启时不重复上传
Provider 可能已经接收上传,服务却在保存结果之前退出。只看本地数据库的重启进程会再上传一次,为重复副本付费。实际做法是:每次重启都先核对本地进度和链上记录,再继续处理。只有每个组件都在两个 Provider 上各有一份副本后,系统才写下完成凭证,因此缺少副本的数据包永远不会被标记为已归档。
无法验证就明确拒绝
读取依次尝试 Origin、首选 Provider 和备用 Provider。Provider 返回的字节在通过三项校验之前,不会交给用户:
- 长度:发现文件被截断,或者末尾多出了不属于原文件的内容。
- SHA-256:确认取回的每一个字节都与归档时一致。
- PieceCID:确认这些字节对应链上登记的存储对象。
任何一项校验失败,系统都会拒绝这份副本并尝试下一个来源;所有来源都失败时返回 502。服务只有在找到并验证一份副本之后,才会交付文件。
实验参考
测试环境
我们在 Filecoin 的 Calibration 测试网络上连接两个 Provider,完整走一遍流程:写入两份、分别读回,再核对两份内容。文件分为四档:1 MiB、16 MiB、64 MiB 和约 1 GiB(实际为 1,016 MiB)。样本由测试场景预先选择,用于覆盖不同文件大小和并发情况,不代表生产流量分布。
A1. 双副本写入和读回
结果:完成 97 次运行。每次完成的运行都通过了两份副本的长度和 SHA-256 校验,1,016 MiB 档的 6 次运行全部完成。
A2. 并发归档
结果:8/8 和 16/16,共 24 个文件。两轮都使用 16 MiB 文件,所以这一档的运行次数最多。一轮同时归档 8 个不同文件,另一轮同时归档 16 个。每个文件都写入两个 Provider,并从两份副本读回校验,说明在这些条件下,同时到来的归档任务仍能按文件分别完成双副本和内容校验。
A3. 1,016 MiB 文件的内存和时间开销
结果:全程 16 分 57.1 秒,Go heap 峰值 258.1 MiB。
客户端一边读取文件,一边上传并计算内容指纹。在一笔由本地客户端发起、连接 Calibration 网络的单文件测试中,Go heap 峰值为 258.1 MiB,说明整份文件从未同时放进 Go heap。这个数字不包括进程的其他内存占用;单次运行也不能外推到更大文件或 TB 级容量。
第二份副本在 482.0 秒时存好,两条链上记录在 762.8 秒(12 分 42.8 秒)时确认。随后从两个 Provider 顺序读回并校验,又用了 4 分 14.3 秒。
A4. 最小尾部数据块
结果:127 字节的最后一个数据块完成归档,12 个 PieceCID 全部一致。
文件按固定大小分块时,最后可能剩下一个很小的尾部数据块;加密后不足 127 字节的对象,Filecoin 无法计算 PieceCID。一个无法存储的尾部,就可能让整份归档无法完成。我们测试了最小可独立存储的尾部:99 字节明文,加上 28 字节固定加密开销。6 个归档组件都能从选定副本恢复。
更短的尾部会并入前一个数据分块,这部分逻辑目前只在受控测试中验证。
A5. 主动重启
结果:两个 Provider 各新增 6 个 Piece,重复为 0。
我们使用计划用于生产的同一套恢复逻辑,在 5 个位置主动退出:刚认领任务、上传调用返回后、第一份副本记录后、一个组件完成后,以及 12 份副本完成但尚未写下完成凭证时。
在“Provider 已接收上传、本地数据库还没有记录”的窗口,对账流程从链上找回两条存储记录并沿用原来的结果,没有再次上传。
A6. 读取切换与故障注入
结果:备用 Provider 以 200 交付了文件;损坏或无法验证的副本都被拒绝。
我们依次断开 Origin、首选 Provider 和全部三个来源。Origin 不可用、首选 Provider 被阻断时,备用 Provider 的副本被取回、校验,并以 200 返回。我们还让 Provider 返回错误、截断和超长内容,每份错误副本都被拒绝,只有所有来源都失败时才返回 502。真实 Provider 在 Calibration 上没有自行返回过损坏内容。
第一次断开 Origin 时,37 项检查中有 18 项返回 502,但文件本身完好。查询链状态的 RPC 节点交替返回两个相差约 52 个区块的已确认视图,较旧的视图还看不到新的存储记录。两个视图都更新之后,同一组检查全部通过。这说明系统会安全失败,但没有测量切换速度或生产可用率。
参考资料
- XnY Protocol Docs — Architecture:链上与链下数据的职责边界。docs.xny.ai/concepts/core-systems/architecture
- Google Cloud Documentation — Cloud Storage overview:GCS 的对象存储模型与常见使用场景。docs.cloud.google.com/storage/docs/introduction
- Filecoin Docs — Glossary:CID、存储提供者和存储证明等术语的官方定义。docs.filecoin.io/reference/general/glossary
- XnY Protocol Docs — Storage & Serving:存储后端、内容哈希与取回地址的关系,以及 GCS 与 Filecoin 的定位。docs.xny.ai/concepts/core-systems/storage-compute-serving
- XnY Protocol Docs — Data Assembly:数据版本在链上记录的承诺,以及实际内容留在链下的设计。docs.xny.ai/concepts/core-systems/data-assembly