English

上传完成前,URI 为什么已经不能变了?

协议 · · Kay (工程师) · 6 分钟阅读

贡献者点下提交,三秒后拿到一个永久地址。日后正是凭这个地址,他才能证明这份贡献出自他手、据此主张所有权,并在数据产生收益时拿到所有权应得的那一份。而此刻,地址指向的文件还没有到 Arweave;它却已经写进了链上交易,再也改不了。

这个地址指向的是一张收据:谁做的、做了什么、什么时候、凭什么可信。收据是一份 metadata 文档,存在链下;链上只留这个地址和一份哈希,用来证明这份收据此后没有被改过。

XnY 把数据的来源、组装关系和收益分配锚定在链上¹,但完整的 metadata 文档不进合约。链上只存两样东西:

bytes32 metadataHash;   // keccak256(metadata JSON 原始字节)
string  metadataUri;    // "arweave://<dataItemId>"

arweave://<id> 是不绑定具体 gateway 的地址,读取时映射为某个 Arweave gateway 的 HTTP URL,例如 https://arweave.net/<id>。任何人以后都能按它取回文档、重算 hash、和链上比对,不需要信任任何一个 Arweave gateway。

五类链下 metadata 都用这一对字段锚定,紧凑序列化后实测 0.5–1.2 KB²:

kind装什么
frontier-metadata一个数据领域的定义:名称、默认策略、提交规则
campaign-metadata一次征集活动:报酬模型、预算、资格门槛、分成
task-metadata一个具体任务:类型、执行方式、验收规则、依赖
cf-metadata一次贡献:贡献者 DID、所属任务、载荷位置、字节数
dataset-metadata一个数据集版本:CF 清单、贡献者 Merkle root、加密套件

五种共享同一个 envelope。

URI 必须在创建链上对象的交易里写进 calldata。交易提交后不能再补,也不能换。写错了没有第二次机会:链上那条记录会永远指向一个取不到的地址,这份贡献的来源就无法验证,贡献者对它的收益主张也就证明不了。

Arweave 这边却是异步的:DataItem 交给 Turbo(接收已签名 DataItem、打包后提交到 Arweave 的上传服务),再等网络确认,要几分钟。而且 ANS-104 DataItem 的 ID 由签名算出³,RSA-PSS 签名带随机 salt⁴,同一份内容重新签一次就是另一个 ID。

(下文「网关服务」指 XnY 自己这个服务,「Arweave gateway」指 Arweave 网络的公共读取入口,两者不是一回事。)

网关服务在请求内做的事,于是只剩创建身份:本地构造并签名 DataItem,算出 ID,把已签名的完整字节写进 PostgreSQL 和独立的 BackupStore(对象存储),返回 URI。上传和确认交给后台。

URI 能提前定下来,靠的是把身份和交付拆开。下面先说这个身份有多硬——201 证明了什么、ID 为什么由签名决定;再说交付怎么保证送到——队列和失败路径;最后说为什么这份文档不进合约。

请求内三秒内后台几分钟到几小时签名 DataItem算出 id写 BackupStore写 PostgreSQL 行两步都成功才返回 201此线之后 URI 不再变化Turbo上传服务uploadedconfirmed
图 1 · 同步段创建身份,异步段完成交付。分隔线左侧的一切,都发生在调用方拿到响应之前。

201 证明了什么

状态已经成立尚未成立
accepted身份已创建,URI 已固定,已签名 DataItem 已写入 PostgreSQL 和独立的 BackupStoreAPI 没有等待 Turbo 接收
uploadedTurbo 已接收,进入打包流程还没观察到 Arweave 网络确认
confirmed已观察到 DataItem 或其所在 bundle 进入 Arweave 区块,按当前确认策略视为已落链公共 Arweave gateway 可能还要几小时才能取到

201 返回时状态是 accepted。它证明一个有稳定身份、由网关服务负责继续交付的资源已经创建,不证明内容已经永久保存。对外的说法要跟着分:在 uploaded 时告诉用户「已永久保存」,是替 Turbo 承诺一件它还没做完的事。调用方用 GET /v1/arweave/items/{dataItemId} 查当前状态。

confirmed 之后,写进去的内容撤不掉。schema 里每个自由文本字段的注释都带同一句警告²:

Permanently written to Arweave — do not put personal data in it.

签名决定 ID,所以要保存签名结果

dataItemId 是对签名值做 SHA-256 再 base64url 编码³。输入是签名那一小段字节,不是整个 DataItem。签名在网关服务本地完成,ID 也就在本地算出,全程不用碰 Arweave 网络。这就是 201 能当场返回终态 URI 的原因。

代价是网关服务只能签一次。RSA-PSS 的 salt 是随机的,重签就换 ID,而链上可能已经锚定了旧的那个。所以返回 201 之前持久化的是完整的已签名 DataItem,不只是 metadata JSON。丢了它,重新签名得不到同一个 ID。

持久化是双写:先写 BackupStore,再写 PostgreSQL 行,两步都成功才返回 201,任一步失败返回 500。worker 取字节时会校验能否重新推导出同一个 ID,PostgreSQL 那份缺失或损坏就从 BackupStore 恢复。副本在 confirmed 之后也保留,因为 Turbo 的 bundle 解包和索引要滞后几小时,这期间公共 Arweave gateway 取不到。

keccak256 的输入和 DataItem 里的 payload 是同一份收到的原始 JSON 字节,网关服务不重新序列化²。字段顺序、空格、Unicode 转义任何一处变化都会产生另一个 hash。

调用方重发同一个请求也不会产生第二个身份。POST 带 Idempotency-Key 请求头,服务端按调用方和 key 去重,命中就返回第一次的 dataItemId 和 URI。

队列在 PostgreSQL 里

记录 commit 之后,这一行开始走状态机:

accepted → pushing(被 worker claim)→ uploaded → confirmed

任务不在内存 channel 里,进程重启后状态还在表里,worker 起来接着上传。API 提交事务后、写 HTTP 响应之前,向一个 channel 发一个空信号,告诉 worker 库里可能有新任务。worker 不需要等响应完成。信号不携带内容,十次唤醒和一次唤醒没有区别,所以 channel 的 buffer 容量是 1。轮询保留为兜底扫描,负责 wake 丢失时把漏掉的任务扫出来。

多个 worker 各 claim 一件,用 FOR UPDATE SKIP LOCKED:别的 worker 正占着的行直接跳过,不在锁上排队⁵。它只挡住两个 worker 同时拿到同一行。整个系统是 at-least-once,允许重复执行,每次重发的都是同一份已签名 DataItem。

① 写 BackupStore② 写 PostgreSQL 行这一行就是队列③ commit④ 唤醒空信号 · buffer = 1worker Aworker B认领 · FOR UPDATE SKIP LOCKED只防同一行被两个 worker 取走
图 2 · 行即队列:先写备份、再写行、commit 之后才唤醒——顺序本身就是论点。轮询作为兜底,负责把丢失的唤醒扫出来。

失败路径

下面几条路径的共同点是:交付中断了,但身份没有丢。每一条要回答的都是同一个问题——这份已签名的 DataItem 接下来由谁负责送出去。

acceptedpushinguploadedconfirmed认领Turbo收录超时回收402 · 429 重试(有上限)failedretired其他 4xx重试用尽放弃管理工具退回(虚线 = 需人工)
图 3 · 每条失败路径都有归处,且都复用同一份已签名 DataItem。虚线是全图唯一需要人介入的转移;failed 是唯一不会自动排空的状态。

进程崩溃后,行会卡在 pushing,超时后被回收、退回队列。回收和 worker 的写操作共用一个时间戳守卫:claim 时拿到的时间戳出现在后续每条 UPDATE 的 WHERE 里,被回收过的行匹配不到,过期的 worker 写不进任何数据。

Turbo 收了但确认迟迟不来的情况,由 uploaded 之后的独立确认器处理:先查 Arweave 索引有没有收录证据,再以 Turbo 的 settled 状态兜底,已收录但未 mined 的给一段宽限期。超过 deadline 仍没有可验证的收录证据,才进入有次数上限的重传,次数用完转 failed。

4xx 里只有 402 和 429 留在重试路径上。402 是钱包免费额度耗尽,充值买入 Turbo Credits 后转入付费路径;429 是限流。这两种下一次尝试可能成功,其他 4xx 原样重试也不会变,直接转 failed,不消耗重试预算。

failed 是状态机里唯一不会自己排空的状态。原因可恢复的(预算耗尽、回执落库失败),用管理工具退回 accepted 重新交付;放弃的关闭为 retired。

免费额度只减不增,402 迟早出现。网关服务独立轮询钱包剩余额度并输出为监控指标,好提前发现。

为什么不直接写进合约

1 KB 在 Base 上约 64 万 gas⁶,按 2026-08-28 的 gas 价(0.006 gwei)和 ETH 价($2,497)折算约 1 美分。拦住这个方案的不是成本。

event log 和 calldata 比 storage 便宜得多,但属于历史数据。普通节点可以裁剪历史区块和 receipts(以太坊正沿 EIP-4444 推进⁷⁸),以后要读它们得靠还保留完整历史的节点。只有 storage 能按当前状态读取,而它按字节线性计费。读这份 metadata 的下游是 HuggingFace、训练框架和 agent⁹,它们通过 HTTP 取文件,不会调合约再 ABI-decode。

storage 还会锁死字段。五种 metadata 各自按 major.minor 独立加字段、升版本²,合约的 storage layout 上线后冻结成 append-only,改一次要走完整的 UUPS 升级流程¹⁰。

链上那对字段是定长的,bytes32 加一个 53 字节的 URI,约 8 万 gas,跟文档多大无关。锚定成本是常量,多条锚定才能合并进同一笔交易。变长内容进合约就做不到这一点。

Arweave 这一侧¹¹的条件恰好匹配:metadata 是 KB 级小文件,Turbo 对这个尺寸的 DataItem 目前免费¹²;Arweave 一次写入、长期持久¹³,并能通过 gateway 用 HTTP 取到¹⁴。免费上传是 Turbo 当前的策略,不是存储模型的一部分。


URI 能提前返回,是因为系统先保存了那个无法确定性重建的身份,上传本身并没有变快。后面的队列、重试和确认,都只是在把这份身份送到 Arweave。

参考

  1. XnY Docs — Architecture. 协议总览:Contribution → Assetification → Usage;去中心化存储为 source-of-truth。docs.xny.ai/docs/concepts/core-systems/architecture
  2. XnY Docs — Metadata documents. 五种 metadata schema;上传到 metadataUri 的字节与算入 metadataHash 的字节完全相同,中间没有规范化步骤。docs.xny.ai/docs/developer/schemas
  3. ANS-104: Bundled Data v2.0. §2「DataItem signature and id」:id 是签名的 SHA-256 摘要。github.com/ArweaveTeam/arweave-standards
  4. RFC 8017, PKCS #1 v2.2. §8.1 / §9.1.1 RSASSA-PSS 编码带随机 salt。rfc-editor.org/rfc/rfc8017
  5. PostgreSQL Documentation — SELECT.「The Locking Clause」:FOR UPDATE … SKIP LOCKED。postgresql.org/docs/current/sql-select.html
  6. evm.codes — SSTORE (0x55). 零值槽位首次写入 20,000 gas。evm.codes/#55
  7. EIP-4444: Bound Historical Data in Execution Clients. 历史区块与 receipts 的裁剪。eips.ethereum.org/EIPS/eip-4444
  8. Ethereum Foundation Blog — Partial History Expiry. 主网历史裁剪的实际推进(2025-07-08)。blog.ethereum.org/2025/07/08/partial-history-exp
  9. XnY Docs — Key Concepts. Storage / Compute / Serving 与主流 AI 工具链的集成。docs.xny.ai/docs/concepts/core-systems/key-concepts
  10. OpenZeppelin Contracts — UUPSUpgradeable. UUPS 代理升级模式(EIP-1822)。docs.openzeppelin.com/contracts/5.x/api/proxy
  11. XnY Docs — Storage, Compute & Serving. 内容寻址存储为 source-of-truth;云缓存不替代去中心化记录。docs.xny.ai/docs/concepts/core-systems/storage-compute-serving
  12. ar.io Docs — Upload to Arweave (Turbo).「Free uploads under 105 KiB」。docs.ar.io/build/upload
  13. Arweave Yellow Paper. 一次付费、长期持久的存储捐赠模型。arweave.org/yellow-paper.pdf
  14. ar.io Docs — Gateways. 永久性来自 Arweave 网络;gateway 负责 HTTP 访问、索引与缓存。docs.ar.io/learn/gateways

链接访问日期:2026-09-02。

相关文章