跳到主要内容

Epoch 承诺

状态:2026-09-18 起在生产环境运行。 Rocky 上的每一个订单事件和每一笔成交——无论客户还是自营——都会按 epoch 承诺到 Canton 账本。

链上结算 讲的是价值层:涉及客户账户的成交以两腿 DvP 在 Canton 上交割资产。本页讲的是数据层。Rocky 维护一份 append-only 的日志,记录交易所发生的一切——下单、订单终态、成交——并在每个 epoch 把对这份日志的承诺写入一份 Canton 合约。任何人都可以拿一条日志记录,向 Rocky 索取其包含证明,再对照账本上记录的根进行验证。在 DvP 层只记账的自营账户成交(settlementStatus: "skipped"),在这里是完整收录的。

承诺了什么​

日志事件写入时机承诺内容
spot_order / perp_order订单被接受进入订单簿仅根(可用包含证明证明)
spot_order_done / perp_order_done订单被撤销或完全成交仅根
spot_fill / perp_fill成交发生根 以及 合约内的完整规范记录

日志由交易所数据库在与业务事件同一个事务里写入,因此不可能存在没有日志行的事件,反之亦然。每行有一个全局序号 seq、一段规范文本记录和叶子哈希 sha256(canonical)。

规范记录格式​

字段用 | 按固定顺序分隔。小数按存储值打印(18 位小数);时间为 UTC,精确到微秒。

spot_order|<orderId>|<userId>|<symbol>|<side>|<orderType>|<price>|<qty>|<createdAt>
spot_order_done|<orderId>|<FILLED|CANCELED>|<executedQty>|<finalizedAt>
spot_fill|<tradeId>|<symbol>|<takerSide>|<price>|<qty>|<takerUserId>|<makerUserId>|<ts>
perp_order|<orderId>|<userId>|<symbol>|<side>|<price>|<qty>|<createdAt>
perp_order_done|<orderId>|<FILLED|CANCELED>|<executedQty>|<ts>
perp_fill|<tradeId>|<symbol>|<takerSide>|<price>|<qty>|<takerUserId>|<makerUserId>|<ts>

用户 id 是交易所内部的账户 UUID,它把记录与账户关联而不暴露 Canton party;客户可以从 GET /api/v3/myTrades 和 GET /api/v3/allOrders 认出自己的 id。

承诺合约​

每个 epoch(正常运行为 60 秒;当交易所的 Canton 流量预算吃紧时节奏放宽到 300 秒 / 900 秒,但绝不跳过 epoch),Rocky 的 operator party 创建一份 RockyEpochCommitment 合约:

字段含义
epoch_id单调递增的 epoch 编号
seq_from、seq_to本 epoch 承诺的日志行范围(含两端)
root[seq_from, seq_to] 内叶子按 seq 顺序构成的 Merkle 根(十六进制 SHA-256)
prev_root上一个 epoch 的根,把承诺串成链
n_orders、n_cancels、n_fills各类事件的数量
fills本 epoch 全部成交的规范记录,按行拼接
created_at承诺的账本时间

树是普通的二叉 Merkle 树:叶子 = sha256(canonical),内部节点 = sha256(0x01 ‖ left ‖ right),奇数层复制末节点,单叶即为根。0x01 前缀保证叶子永远不会与内部节点碰撞。

由于 fills 就在合约里,成交量可以只凭账本重建,无需信任 Rocky 的 API。订单与撤单只通过根承诺:它们的数量不影响链上成本,而每一条仍可单独证明。

接口​

均为 https://api.rocky.exchange 上的公开 GET 请求,无需 API key。

接口返回
/v1/chain/status锚定服务是否 live、epoch 节奏、签名 party、模板 id、当前日志积压与最新 epoch
/v1/chain/epochs?limit=50最近的 epoch(epoch_id、seq_from、seq_to、计数、root、prev_root、status、update_id、contract_id)
/v1/chain/epochs/{id}单个 epoch
/v1/chain/proof?seq=<n> 或 ?ref_id=<orderId 或 tradeId>[&kind=spot_fill]一条日志记录的包含证明

证明响应示例:

{
"seq": 52314,
"kind": "spot_fill",
"ref_id": "01a0b2e8-c07a-74a3-bd67-5a9ab2484f0b",
"canonical": "spot_fill|01a0b2e8-c07a-74a3-bd67-5a9ab2484f0b|CETH-CBTC|BUY|0.031990000000000000|0.038000000000000000|e41f4b95-…|0cb01d99-…|2026-09-18T05:02:11.804211Z",
"leaf": "9f1c…",
"index": 1287,
"path": [ { "hash": "3ab0…", "side": "right" }, { "hash": "77e2…", "side": "left" } ],
"epoch": {
"epoch_id": 20, "seq_from": 51001, "seq_to": 53644,
"root": "23077cf0be097cdf2e65c3334fca01e7177cecd439ad0e9c4094cc13eb549411",
"status": "confirmed",
"update_id": "12202f0ad6a8c90d10a8a3b7f2b89217c80e1d0d4b87547beb71c07935a72b4a12a0",
"contract_id": "00c4…"
},
"root_recomputed_matches": true
}

所属 epoch 尚未创建的记录返回 HTTP 202,带记录与叶子但没有 epoch;等下一个 epoch 后再查。

公开锚定(ccview)​

RockyEpochCommitment 是私有合约:只有 Rocky 的 operator party 是利益相关方,DSO、Scan 以及 ccview 这类公开浏览器都看不到它。为了让只信任公开 Canton 数据的人也有抓手,Rocky 每 10 分钟把新确认的 epoch 根的摘要写进 operator party 一笔极小额 Canton Coin 自转账的备注里。Canton Coin 转账以 DSO 为利益相关方,公开浏览器会索引。

备注格式为 rocky:epochs:<from>-<to>:sha256:<digest>,其中 digest = sha256(root_from ‖ root_from+1 ‖ … ‖ root_to),按 epoch 顺序对 32 字节原始根拼接后哈希。GET /v1/chain/anchors 列出各锚及其 Canton update id 与 ccview_url(ccview 的 update 页 https://ccview.io/updates/<updateId>/,备注在 TransferFactory_Transfer 的 choice 参数里,字段名 splice.lfdecentralizedtrust.org/reason);每个 epoch 与证明响应都带有覆盖它的 public_anchor。

仅凭 ccview 验证一笔成交:取其证明 → epoch 根(并按下文校验包含路径)→ 覆盖该 epoch 的锚 → 从 /v1/chain/epochs 取 from..to 的根重算摘要 → 与 ccview 上那笔 Canton Coin 转账的备注比对。

自行验证证明​

不要信 root_recomputed_matches,自己重算。十二行 Python,无依赖:

import hashlib, json, urllib.request

p = json.load(urllib.request.urlopen(
"https://api.rocky.exchange/v1/chain/proof?ref_id=<tradeId>&kind=spot_fill"))

leaf = hashlib.sha256(p["canonical"].encode()).digest()
assert leaf.hex() == p["leaf"]
acc = leaf
for step in p["path"]:
sib = bytes.fromhex(step["hash"])
acc = hashlib.sha256(b"\x01" + (sib + acc if step["side"] == "left" else acc + sib)).digest()
assert acc.hex() == p["epoch"]["root"]
print("included in epoch", p["epoch"]["epoch_id"], "update", p["epoch"]["update_id"])

然后到账本上核对根:通过任何托管了该合约观察方的参与者按 update_id 取 update,或在 Canton 区块浏览器里按 contract_id 查询,比较其 root 字段。两者一致,即说明该记录在那个 epoch 属于交易所日志,而 epoch 本身锚定在 Canton 的排序之中。

保证了什么,没保证什么​

  • 记录的完整性与防篡改。 epoch 一旦确认,其中任何订单事件或成交都无法在不改变账本上已有根的情况下被修改或删除;prev_root 链让悄悄改写历史变得可检测。
  • 及时性。 每 60 秒开一个 epoch,绝不跳过;卡住的提交用同一个 command id 重试,并阻塞更新的 epoch 以保持链的连续。/v1/chain/status 暴露积压情况。
  • 不是结算。 承诺不移动资产。客户成交的交割是链上结算描述的 DvP;自营账户之间的成交是 Rocky 托管内部的转移,在这里只作为数据被承诺。
  • 不是遗漏证明。 与储备金证明一样,这只能证明账本上的内容确实在日志里;「日志没有漏掉任何事件」由数据库同事务规则保证,并可通过把每个 epoch 的 n_fills 与公开成交流对比来审计。

成本透明​

承诺由 Rocky 自己的 Canton 流量支付。交易所在 GET /v1/traffic(公开)公布各 lane 的流量预算与消耗,包括这些合约所在的 L2-epoch lane;GET /v1/traffic/mm 是 Rocky 自营做市舰队据以留在预算内的单字状态。2026-09-18 实测,一份承诺约消耗 3–4 KB sequencer 流量。