# FTO 中立索引器接入（INTEROP / P2）

> **状态：准备材料（draft），未对外提交或联络。** 本文 + 两组金标向量 + `wire_vectors_check.mjs`
> 构成「运营」层的自证包：**任何第三方仅凭公开规范就能实现一个独立索引器，并证明它与参考实现
> 逐字节同 digest / state root。** 这是 FTO「用 / 发 / 运营 三层开放」里 **运营** 层的验收物。
>
> 相关：[`SPEC.md`](./SPEC.md)（规范）· [`spec/vectors.json`](./spec/vectors.json)（逻辑事件金标）·
> [`spec/wire-vectors.json`](./spec/wire-vectors.json)（原始 wire 金标）· [`MAINNET-PUBLIC-READINESS.md`](../MAINNET-PUBLIC-READINESS.md)（G2/G3/G10）·
> [`feat`](../FEATURE-MATRIX.md)。

---

## 1. 为什么需要「中立」

FTO 没有 VM：**真相是一份事件日志**，所有人按同一规范、同一顺序重放同一批事件，得到同一状态。
只要「谁的事件、什么顺序、怎么算」是公开且可复算的，索引器就**不是**单点：

- **用**：任何人可持有 / 转移 / 流片 / 交易。
- **发**：任何人可建 factory / 铸币 / 出题。
- **运营**：**任何人可跑索引器（含 keeper / 承诺）并得到同一 digest。**

> 缺了运营层，"开放给所有人"就退化为"一个公司运营的服务"。FTO 已经实现三份独立实现
> （JS / Python / C#）同 digest；本文把这条事实变成**第三方可自证的契约**。

---

## 2. 一条流水线，四个确定性步骤

独立索引器从**链上原始数据**到**状态承诺**只有四步，全部是纯函数：

```
1. 发现   一个 tx 的 OP_RETURN 载荷（第二字节 = 0x31）→ wire 解码
2. 排序   (block height asc, intra-block pos asc, txid asc)   ← 未确认 = 最后（fail-closed）
3. 规范化 owner 8 字节 id → owner:<hex8>（含 #16 迁移别名表）
4. 重放   factory / mint / transfer / tapeout 规则 → canonical rows → digest / state root
```

细节：

| 步骤 | 规范 | 参考实现 |
| --- | --- | --- |
| wire 布局 | `SPEC.md` §2 | `fto/src/wire.js` |
| 排序（`pos` 优先，否则因果 + rank + txid） | `SPEC.md` §5 | `fto/src/logical.js` `rawSort` |
| owner 规范化 + #16 别名 | `SPEC.md` §3.2 | `fto/src/owner.js` |
| 资产规则（含 G4/G9 分叉） | `SPEC.md` §4/§6/§7 | `fto/src/indexer.js` |
| 状态行 / digest / state root | `SPEC.md` §8 | `fto/src/stateroot.js` |
| 链上承诺校验 | `SPEC.md` §8.3 | `fto/tools/state_root.mjs` |

**排序的唯一歧义点**已被冻结：块内位置 `pos` 是链的真实顺序，**只要一个高度里所有事件都带 `pos`
就用它**（`pos` 天然尊重花费顺序）；否则退回「因果（花费）→ 适用性 rank → txid」。参考实现
`fto/tools/order_residual.mjs` 度量两种规则在真实链上是否分歧。

---

## 3. 自证包（第三方跑这三条命令）

```sh
# 1) 逻辑事件 -> digest/root：实现资产规则后必须命中
node fto/tools/spec_vectors_check.mjs

# 2) 原始 wire -> digest/root：从链上 payload 起步（解码 + 排序 + 规范化 + 规则）
node fto/tools/wire_vectors_check.mjs

# 3) 三实现同 digest：JS == Python == C#（含 G4/G9 分叉与无效事件拒绝）
node fto/tools/consistency_check.mjs
```

你自己的实现只要对 `spec/vectors.json` 与 `spec/wire-vectors.json` 的**每个** `expect.digest` /
`expect.root` 逐字节命中，就是一个合规的中立索引器。

### 两组向量的分工

| 文件 | 输入 | 覆盖 | 跨语言 |
| --- | --- | --- | --- |
| `spec/vectors.json` | **已解码**逻辑事件 | 资产规则、G4/G9 分叉、无效拒绝 | JS + Python + C# |
| `spec/wire-vectors.json` | **原始 wire** `{txid,height,pos,payload,signers}` | wire 解码 + 排序（`pos` 权威）+ owner 规范化（含 #16 别名）+ 规则 | JS + Python + C#（`wire-tapeout` 因需 F0 网表度量，仅 JS） |

`wire-vectors.json` 的 `signers` 是**该 tx 输入所有者的 8 字节 id**（`SPEC.md` §6：esplora
`vin[].prevout.scriptpubkey_address` → `addressOwnerId`）。索引器从地址推导它；向量把它作为链上
事实显式给出，从而让「解码/排序/规则」与「取地址」解耦测试。

### 契约（第三方实现必须满足）

1. **载荷识别**：第二字节 != `0x31` 或类型未知 → **忽略**，绝不使重放失败。
2. **状态锚**：`stateRoot`（`0x52`）**不是**资产事件，不改变 digest / root，只用于承诺校验。
3. **排序**：一个高度内若全部有 `pos`，按 `pos` 再 `txid`；否则按因果/rank/txid。未确认最后。
4. **owner**：事件只带 8 字节 id；先套 #16 冻结别名表，再写成 `owner:<hex8>`。
5. **无效事件**：跳过并记入 `errors`（带 index / type / reason），**不中断**重放——digest 必须是
   有序日志的纯函数。
6. **分叉**：`AUTH_HEIGHT`（G4）与 `ECON_HEIGHT`（G9）是**网络参数**；低于它的历史保持旧解释，
   **历史永不被重写**。

---

## 4. 与 FIP-101（Fractal Standard Indexing Service）的关系

> FIP-101 是 Fractal 的「标准化数据索引服务」提案：开放、无需许可、可质押、以 **Index Mining**
> 奖励合格索引器。**FTO 是它上面的一个 metaprotocol**——不改变 FIP-101，只是把 FTO 的索引结果
> 做成一个可被标准索引服务承载的「数据域」。**以下为对接设想，非承诺、未提交。**

| FIP-101 概念 | FTO 对应物 | 现状 |
| --- | --- | --- |
| 索引数据域 | FTO 资产状态（`F/A/M/B` 行）+ 电路 / 任务 / PoD | ✅ 规范 + 三方实现 |
| 可验证输出 | `digest` + VCP `stateRoot` + 链上 `stateRoot` 承诺（含 `setHash`） | ✅ G2/G3 线上真跑 |
| 索引器实现 | 任何实现，凭 `SPEC.md` + 两组向量自证 | ✅ 本文自证包 |
| 许可 / 质押 | FIP-101 层的事，FTO 不引入自有门槛 | 交由 FIP-101 |
| 奖励（Index Mining） | FIP-101 的 FB 奖励；FTO 不重复发奖（FTU 若启用另属 G8/G9） | 设计边界 |

**可向标准索引服务暴露的最小接口（设想）**：

- `GET /api/digest` → `{ spec:"FTO/v1", params:{AUTH_HEIGHT,ECON_HEIGHT}, digest, events, errors }`
- `GET /api/root` → `{ root, height, events, setHash, commitment:{ok, reasons} }`
- `GET /api/events` → 规范有序的已验证事件集（供独立方重放，而非只信摘要）
- 注册元数据（见 §5）

对齐方式：**只要两个实现命中同一 `digest`，它们对 FTO 数据域就是一致的**——这正是 FIP-101
「多索引器」所需的同构性。

---

## 5. 索引器注册元数据（提案）

一个 FTO 索引器对外声明的最小自述：

```json
{
  "protocol": "FTO/v1",
  "impl": "fto-reference|third-party",
  "version": "1.x.y",
  "wireVersion": "0x31",
  "params": { "AUTH_HEIGHT": 1577499, "ECON_HEIGHT": 1577517 },
  "vectors": {
    "logical": { "file": "fto/spec/vectors.json", "digest": "<sha256 of file>" },
    "wire":    { "file": "fto/spec/wire-vectors.json", "digest": "<sha256 of file>" }
  },
  "endpoints": { "digest": "/api/digest", "root": "/api/root", "events": "/api/events" },
  "network": "fractal-testnet|fractal-mainnet"
}
```

> 用**向量文件的 sha256** 而非版本号做锚，避免"同名不同规则"。

---

## 6. 参考实现与版本

| 实现 | 路径 | 语言 | 入口 |
| --- | --- | --- | --- |
| JS（canonical） | `fto/src/{wire,logical,indexer,econ,auth,stateroot}.js` | JS | 边缘 / 本地服务 / CLI/SDK |
| Python | `fto/verify_indexer.py` | Python | `python fto/verify_indexer.py [--wire] <file.json>` |
| C# / .NET | `fto/third-indexer/` | C# | `dotnet run --project fto/third-indexer -- [--wire] <file.json>` |

三方对两组金标逐字节同 digest / root；`consistency_check` 另有 G4/G9 分叉与无效事件的合成日志。

---

## 7. 残余 / 开放项

- **F0 网表不在此自证包内**：`tape-out` 的 burn（nand/latches）需要 F0 网表度量。参考实现
  `assembleLogical` 附带 burn；`wire-tapeout` 向量仅由 JS 校验。第三方若只做**资产**索引，可忽略
  tape-out 的 burn（与 Python/C# 的资产重放一致）；若要完整，需要 F0 codec（`f0/`）。
- **签名绑定地址**：`signers` 的**推导**（bech32 → script → `addressOwnerId`）不在向量内，另行由
  `auth.js` 测试与线上跨语言对账覆盖。
- **FIP-101 对接未落地**：本节为设想，**未提交 FIP、未联络任何方**；是否接入由你侧决定。
- **主网未开放写入**：fail-closed 守卫不变（见 `MAINNET-PUBLIC-READINESS.md` §G6）。

---

*本文件是准备材料。机器可核的事实是 `spec/*.json` + `node tools/verify_all.mjs`。*
