第 18 章 链上索引与可验证数据工作流
Walrus 应用通常有三条数据线:
- Walrus 数据面:
blob_id -> bytes。 - Sui 状态面:对象、交易、事件、epoch。
- 应用索引面:数据库、搜索、缓存、权限视图。
可靠系统不是只读一个 aggregator,也不是只扫链上对象。它会把三条线对齐:链上状态说明“应该有什么”,Walrus 返回“实际能读到什么”,索引器提供“产品需要怎样查询”。
推荐索引表
一个通用表结构:
| 字段 | 说明 |
|---|---|
network | mainnet、testnet、devnet |
asset_id | 业务对象 ID |
blob_object_id | Walrus Blob 对象 ID |
blob_id | HTTP 读取 ID |
owner | 业务 owner |
size | 字节数 |
end_epoch | 存储到期 epoch |
tx_digest | 最近一次写入交易 |
object_version | Sui 对象版本 |
status | active、expired、replaced、missing |
updated_at | 索引器更新时间 |
network + asset_id 应该唯一。不要让 mainnet 和 testnet 数据共用同一个主键空间。
事件驱动流程
推荐用事件作为主入口:
- 合约在创建或更新 Walrus 引用时发事件。
- 索引器按 checkpoint 拉取事件。
- 索引器再读取相关对象,确认对象当前版本。
- 后台任务调用 aggregator 做抽样读取或完整校验。
- 前端只查应用 API,不直接扫链。
事件驱动的好处是成本稳定、延迟可控,而且容易恢复。索引器重启后从最后处理的 checkpoint 继续即可。
可验证读取
读取路径不要只做“HTTP 200 就成功”。至少校验:
- 返回的内容长度是否等于记录的
size。 - SDK 或协议层是否能验证 Blob 身份。
- 如果业务层保存了
sha256或 BLAKE2/3 摘要,下载后重新计算。 - 读取的
blob_id是否来自当前网络的索引记录。
后端示意流程:
GET /assets/:id
-> 查 DB 得到 blob_id、size、hash、end_epoch
-> 若 end_epoch 已过期,返回需要续期
-> 向 aggregator 读取
-> 校验大小和业务哈希
-> 返回内容或签名 URL / 代理流
如果是大文件,不要在 API 层一次性读入内存。用流式代理、Range 请求或让前端直接读 aggregator,再把校验任务放在前端 worker 或后台任务中。
重组、延迟和幂等
Sui checkpoint 提供稳定的处理边界,但你的应用仍要处理重复投递和延迟:
- 事件处理必须幂等,以
tx_digest + event_seq或对象版本去重。 - 同一对象的旧事件晚到时,不应覆盖新版本。
- 索引器落库前先比较
object_version。 - 任何“已上传但未上链”的临时记录都要有超时清理。
一个安全的更新规则:
if incoming.object_version > stored.object_version:
update row
else:
ignore stale event
如果只按时间戳更新,节点时钟、重试和队列延迟都会制造脏数据。
链上索引对象还是链下数据库
共享对象注册表适合保存协议级目录,例如某个 collection 的根索引、治理控制的官方数据集列表。它不适合承担所有查询需求。
链上索引适合:
- 少量、必须被合约读取的状态。
- 需要权限和资产语义的记录。
- 需要不可抵赖的审计锚点。
链下索引适合:
- 全文搜索、标签、排序、分页。
- 大量派生字段。
- 内容可用性探测结果。
- 多 aggregator 健康评分。
实践上,两者要组合:链上保存最小可信事实,链下保存产品视图。
续期和过期处理
过期不是偶发错误,而是生命周期的一部分。索引器应定期扫描:
end_epoch - current_epoch <= renew_threshold
然后把记录标记为 renew_required,通知 owner 或触发 sponsor 流程。不要等用户读取失败后才发现文件过期。
如果应用允许自动续期,必须设置预算边界:
- 单用户每日续期上限。
- 单 Blob 最大保存期。
- 项目级 WAL/SUI 余额告警。
- 续期失败的降级展示。
审计日志
每次引用变化都应保留审计记录:
| 字段 | 用途 |
|---|---|
old_blob_object_id | 回滚和争议处理 |
new_blob_object_id | 当前内容定位 |
actor | 谁发起变更 |
tx_digest | 链上证据 |
reason | 可选,业务原因 |
不要为了“节省表”只保留最新值。Walrus 存的是内容,业务系统还要解释内容为什么变了。