# OMNX Chain（omnx-1）· 更新日志

## 2026-09-25 · T3 补丁：落盘顺序修复 —— 断电不再产生资金通道赤字

> **本次仍为测试网**，主网闸门保持关闭；对外口径不变（HTTP Gossip + 单验证者确定性轮转，不具备拜占庭容错）。

### 一、缺陷（真实存在，路径已复现）

`_finalize_block` 里的旧顺序是「先 `privacy.flush()` 追加隐私日志 → 再由 `_push_block()`
调 `_save()` 写透明账本」。进程若恰在这两步之间被 SIGKILL（断电 / `kill -9`），重启后会读到
「note 已在日志里、托管余额还没入账」——未花费 note 之和大于托管余额，即**赤字**，
`backed=false`，账实不符。这是资金不变量被破坏的方向。

### 二、修复

| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/chain.py` | 落盘顺序改为「**先出块并落盘透明账本 → 再追加隐私日志**」，隐私日志成为唯一提交点 |
| 2 | `node/modules_privacy.py` | `flush(chain, height=None)` 支持显式传入本块高度（日志 `h` 字段口径不变，无 off-by-one）；`bridge_info()` 新增 `backed_exact` / `escrow_surplus` / `escrow_deficit` |
| 3 | `tests/test_privacy_durability_order.py` | **新增**，21 项：钉死落盘顺序、断电两个方向、`backed` 未被弱化成恒真 |
| 4 | `deploy/aws/run-local-gate.sh` | 自检脚本 10 → **11 个**，基线 732 → **753 项**（合计 858 项） |

### 三、修复后语义

- 断电只可能留下**盈余**（托管 > 未花费 note 之和，无人可认领），**不可能**留下赤字；
- `backed`：未花费 note 已被托管足额背书（无赤字）时为真 —— 正常链恒为真；
- `backed_exact`：严格对账口径（托管与累计口径分毫不差），用于排查盈余；
- 赤字方向仍会被如实报成 `backed=false`：回归用例反向钉死「旧顺序产物必须报赤字」，防止检查被放水。

### 四、全量回归

`bash deploy/aws/run-local-gate.sh` → **858/858 全绿**
（check-node 7 + 钱包签名 19 + EVM/DEX 42 + tests 753 + shell 语法 37）。

## 2026-09-25 · T3：透明账本 ↔ 隐私账本资金通道打通

> **本次仍为测试网**，主网闸门保持关闭；对外口径：HTTP Gossip + 单验证者确定性轮转，不具备拜占庭容错。
> ⚠️ 链上交易**仍是明文**（`from` / `to` / `value` 公开）。本次打通的是「透明账户余额 ↔ 隐私 note」的
> 资金通道，**不等于链上交易已加密**；发送方地址关联性（环签名 / zk-SNARK）仍未解决，属 T4。

### 一、交付内容

| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/modules_privacy.py` | **通道新增**：托管地址 `0x…0010` / `bridge_config` / `bridge_plan`·`bridge_apply`·`bridge_undo`·`bridge_commit` / `_pool_mark`·`_pool_undo` / 只读 `bridge_info()`；`route()` 改为「规划 → 纯校验 → 动账 → 落账」；`flush`·`compact`·`_load` 记通道口径（日志新增 `pub` 字段） |
| 2 | `node/chain.py` | **仅 1 行**：`load_genesis_config` 白名单加 `privacy`（否则创世里的 privacy 段被静默过滤） |
| 3 | `node/rpc.py` | 新增只读 `exx_privacyEscrow`；`exx_privacyInfo` 追加 `bridge` 小节；`exx_shield` 加即时余额预检；`exx_unshield` 地址格式校验 + 归一；提交回执加 `planned` / `applied_at_next_block` / `bridge` |
| 4 | `deploy/genesis.json` | 新增 `privacy` 段：`bridge_enabled=true`、`escrow_address=0x…0010`、`fee_sink=@fees_pool`（既有参数一个未改） |
| 5 | `node/explorer.html` + 离线版浏览器 HTML | 供应卡片追加「🔒 隐私池托管余额」只读一行（旧节点无该接口时静默跳过） |
| 6 | `tests/test_privacy_bridge.py` | **新增**，145 项专项自检 |
| 7 | `tests/test_privacy_chain.py` | **+2 行**：显式钉死 `bridge_enabled=false`，保住 T1/T2 语义 |
| 8 | `docs/隐私资金通道设计.md` | **新增**（口径 / 不变量 / 原子性 / 创世开关 / 回滚 / 已知边界） |
| 9 | `deploy/aws/run-local-gate.sh` | 自检脚本 9 → **10 个**，基线 587 → **732 项** |

### 二、记账口径

| 操作 | 发起方 | 托管 `0x…0010` | 手续费池 `fees_pool` |
|---|---|---|---|
| `shield` | `-(V + gas)` | `+(V - F)` | `+F` |
| `transfer` | `-gas` | `-F` | `+F` |
| `unshield` | `-gas` | `-(V + F)` | `+F`；目标地址 `+V` |

- 不变量：`balances[托管地址] ≡ 未花费 note 面值之和` ⇒ `total_supply()` 仍恒为 10 亿
- 托管地址是系统地址（无私钥），只能由链在交易执行中动账；余额可被 `eth_getBalance` 与浏览器直接查询
- 隐私手续费与 Gas 同口径进 `fees_pool`，随块结算给出块验证者
- ⚠️ 隐私层的 `fee_value` 与 0.1% 底座费的关系，留待 T5「隐私版手续费分账」统一重做；
  当前 shield/unshield 属模块内部划转，按链上既有规则**不重复收** 0.1% 底座费

### 三、安全性质

1. **原子性**：规划（只读）→ 纯校验 → 动账 → 落账；任何一步失败，金额按 `(地址, 增量)`
   逆序逐条冲回、Merkle 叶子 / nullifier / 根 / delta 按长度整体回退 —— **零半执行状态**。
   自检用「注入落账故障」真实验证：失败后状态指纹与失败前逐字段一致。
2. **防凭空造币闸门**：通道开启前产出的 note 没有透明侧背书，若放行等于无锚增发。
   因此「池内已有 note 且历史上从未发生通道动账」时，第一笔通道交易被**直接拒绝**
   （错误信息含「未背书 note」）。测试网处置：清空 `node/data/privacy.*` 后在空池上启用。
3. **地址归一**：`unshield` 的 `to` 统一归一为小写（`0x` + 40 位 hex），
   避免 EIP-55 校验和写法被记成第二个账户。

### 四、验证

- [x] `tests/` 10 个脚本 **732/732** 全绿（本地工程；包内按 `install.sh` 布局暂存后同样 732/732）
- [x] T1/T2 的 97 项一项未回退（关闭态对照：透明账本零动账）
- [x] 托管余额 == 累计口径 == 归档日志 `pub` 汇总，三处独立交叉对账一致
- [x] 攻击面：篡改 `public_value`、双花、非法 `to`、托管不足、负数、未知类型、未背书 note
      —— 全部拒绝且余额 / nullifier / Merkle 树零变化
- [x] 重启（日志重放路径）与压实（快照路径）后口径一致；老格式（无 `pub` / 无 `bridge` 段）可正常装载
- [x] 交付包重打：`deploy/omnx-aws-deploy-new.tar.gz`，sha256 随包更新

### 五、上链跑通后发现并修掉的缺陷

| # | 缺陷 | 不修的后果 | 修法 |
|---|---|---|---|
| D | 关闭态区块也写全零 `pub` 记账 | 重放时把「从未开通道」误判成「已开通道」，**防凭空造币闸门失效** | 只有真发生通道动账才写 `pub`；重放也只在非零时置 `history` |

### 六、回滚

| 级别 | 操作 | 效果 |
|---|---|---|
| 1（首选） | 创世 `privacy.bridge_enabled` 改回 `false`，重启节点 | **不改一行代码**退回 T1/T2 语义 |
| 2 | 还原 `node/modules_privacy.py` / `node/rpc.py` 的 `.bak-<时间戳>` 备份并回退创世 | 回到 T3 之前 |
| 3 | `python3 scripts/omnx-snapshot.py import <快照>` | 状态级回滚 |

⚠️ 已 shield 出的 note **无法撤销**；关闸后 note 仍在链上（可查、可私密转账），
但要再 `unshield` 必须重新开闸。

### 七、边界

- 本次**只动公链侧**（隐私账本 ↔ 透明账本），不涉及 DEX / 交易所 / 适配器 / 子链；
- 未导入任何真实业务数据；主网部署闸门保持关闭，未收到项目方明确文字不得执行主网部署；
- 节点需**重启**才会加载本次代码与创世开关。

## 2026-09-24 · 平滑升级能力：交易信封版本 / 状态版本闸门 / 快照导入导出 / 蓝绿部署方案

> 项目方拍板：**未知版本交易「广播阶段收下、落账阶段 closed 拒绝」**；部署包一并打包 `tests/`。
> **本次仍为测试网，主网闸门保持关闭。** 对外口径：HTTP Gossip + 单验证者确定性轮转，不具备拜占庭容错。

### 一、交付内容

| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/tx_envelope.py` | **新增**。信封版本 / 哈希口径 / 广播与落账裁决 / 状态文件版本闸门 |
| 2 | `node/chain.py` | **增量**：6 处插入 + 1 处替换（见下），净 +568 字符 |
| 3 | `scripts/omnx-snapshot.py` | **新增**。快照 `export` / `verify` / `import` / `backup`（增量）/ `info` |
| 4 | `docs/链升级兼容性规范.md` | **新增**。改动分类表（A 状态兼容 / B 条件兼容 / C 不兼容）+ 分叉预案模板 |
| 5 | `docs/蓝绿部署方案.md` + `deploy/aws/bluegreen-switch.sh` | **新增**。方案与切换器（**只交付，不执行**，AWS 仍搁置） |
| 6 | `tests/test_upgrade_compat.py` | **新增**，51 项；并挂进 `run-local-gate.sh`（8 → 9 个脚本） |
| 7 | `deploy/aws/bundle.sh` | `tests/` 随包分发（此前缺 `tests/`，上云后闸门第 ⑦ 步必然失败） |

### 二、`chain.py` 的 7 处改动（逐条列清）

| 位置 | 改动 | 性质 |
|---|---|---|
| 顶层 | `import tx_envelope` | 插入 |
| `add_tx` | 签名前 `tx_envelope.stamp(tx)` 盖版本戳 | 插入 |
| `add_tx` | 哈希改调 `tx_envelope.tx_hash(tx)`（写入方与校验方共用一份实现） | 替换 1 行 |
| `add_tx_external` | 广播阶段 `gossip_gate()` 宽松收下 + `verify_hash()` | 替换 5 行 |
| `_apply_tx` | 落账阶段 `apply_gate()`，`v > MAX_APPLY_V` 一律不执行 | 插入 |
| `snapshot()` | 写入 `state_v` / `min_reader_v` / `chain_id` / `chain_id_num` | 插入 |
| `_load()` | `reader_gate()` 状态版本闸门 | 插入 |

### 三、哈希口径：为什么不是「显式白名单」

显式白名单要求穷举历史上出现过的每一个交易字段（`module`/`op`/`token`/`pair`/`amount`/
`minOut`/`symbol`/`to`/`zone`/`tier`/`params`/`data`/`nonce`/`gas`/`gas_price`/`value`…），
分散在 dex / platform / rwadex / privacy / evm 各层。**漏掉任何一个，那一类交易的哈希就会变，
等于把正在跑的链判成非法。**

所以走加法：哈希口径保持「全部字段 − 运行时字段」不变，只额外挖掉保留命名空间 `x_`。
当前没有任何字段以 `x_` 开头，因此对现存交易新旧算法算出的哈希**逐字节相同** ——
`tests/test_upgrade_compat.py` 第 2 节用 8 种真实交易形态做了新旧对照。

`x_` 字段不参与哈希 ⇒ 它天然未签名，所以纪律是：只放不影响状态的提示数据；
影响状态的扩展必须**升 v 并提升为正式字段**。本节点自己**不生产**未申报的 `x_` 字段。

### 四、版本裁决口径（按项目方拍板实现）

| 阶段 | 行为 |
|---|---|
| 广播 `add_tx_external` | **宽松**：结构认得就收，不认识的版本也入池、转发 |
| 落账 `_apply_tx` | **closed**：`v > 1` 一律不执行，记 `ok=False` + `error`，发送方余额与 nonce 完全不动 |
| 状态文件 `_load` | `min_reader_v` 高于本节点 → **拒绝启动**（旧程序硬读新格式会静默解析坏） |

### 五、快照工具

```
python3 scripts/omnx-snapshot.py info      [--data DIR]
python3 scripts/omnx-snapshot.py export    [--data DIR] [--out DIR] [--label TXT]
python3 scripts/omnx-snapshot.py backup    [--data DIR] [--out DIR] [--full-every N] [--keep N]
python3 scripts/omnx-snapshot.py verify    <快照.tar.gz | 目录>
python3 scripts/omnx-snapshot.py import    <快照.tar.gz> [--data DIR] [--force]
```

- 产物带 `MANIFEST.json`：逐文件 sha256 + 链高 + 链头哈希 + 版本戳；**导入前强制先 verify**；
- 纳入快照的有 4 个文件，**`evm_state.json` 曾经最容易被漏** —— 漏了会导致 ETH 侧余额回退；
- 跨链快照（`chain_id_num` 不一致）拒绝导入；节点在跑时拒绝导入（除非 `--force`）；
- ⚠️ **增量区块包只用于归档与核对，不能单独恢复** —— 余额/nonce/合约存储是执行结果，
  链路没有「从创世重放」的实现。恢复一律用完整快照。

### 六、验证

- [x] `tests/` **9 个脚本 587/587** 全绿（88 + 101 + 36 + 39 + 97 + 59 + 68 + **51** + 48），基线 439 项一项不回退
- [x] 新增 51 项覆盖：哈希零漂移对照、广播宽松/落账 closed、`x_` 字段、状态版本闸门、
      老 `chain.json`（删掉全部新键）仍能装载、快照 export/verify/import/backup 端到端、
      篡改快照被 sha256 抓到、增量包拒绝导入、跨链拒绝导入
- [x] `node/chain.py` / `node/tx_envelope.py` / `scripts/omnx-snapshot.py` 语法与导入通过（Python 3.9.6）

### 七、诚实边界

- **节点升级仍需停机重启**。「平滑」指的是协议与状态兼容性，不是热升级；
- `x_` 字段**没有签名保护**（这是它的定义，不是 bug）；因此只允许放不影响状态的提示数据；
- 蓝绿方案**只交付方案与脚本，未执行任何云端操作**（AWS 仍搁置）；
- 链上交易仍是明文账户模型；隐私账本仍是独立账本，T3 前与透明账本无资金通道。

### 八、回滚

`node/chain.py.bak-20260924-213159`（升级前）。回滚 = 还原该文件 + 删除 `node/tx_envelope.py`。
`chain.json` **无需回滚**：多出来的 `state_v` / `min_reader_v` / `chain_id` / `chain_id_num`
是旧程序 `_load()` 用 `.get()` 忽略的键。`scripts/omnx-snapshot.py` 是纯新增文件，删掉即可。

---

## 2026-09-24 · 隐私账本 T1+T2 合并交付：note 引擎上链 + 隐私状态独立持久化

> AWS 部署保持搁置。锁定的数据闸门不变：**7 项任务全部完成并通过回归前，不得导入任何真实业务数据。**
> **本次仍为测试网，主网闸门保持关闭。** 对外口径：HTTP Gossip + 单验证者确定性轮转，不具备拜占庭容错。

### 一、这次做了什么

把第 1+3 期的**离屏** note 引擎真正接进了链，并把隐私状态从 `chain.json` 里**分离出来独立持久化**。

| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/modules_privacy.py` | **新增**。`PrivacyStore`（快照 + 追加日志，原子写）/ `PrivacyLayer` / `PrivacyRouter` / `build_shield` / `build_transfer` / `build_unshield` |
| 2 | `node/notes.py` | **增量**：`STATE_V`、`_delta` 增量、`take_delta()` / `snapshot_state()` / `load_state()` / `apply_delta()` / `integrity_check()` |
| 3 | `node/chain.py` | **3 处纯增量插入，0 删除，共 +14 行**：`__init__` 末尾 bootstrap；`_finalize_block` 里每块 flush（鸭子类型判断，不每块走 import）；`_apply_tx` 加 `elif mod == "privacy"` |
| 4 | `node/rpc.py` | **0 删除，+104 行**：`_privacy_submit` + 8 个 `exx_privacy*` 方法 |
| 5 | `tests/test_privacy_chain.py` | **新增**。T1+T2 专项自检 **97 项** |
| 6 | `deploy/aws/run-local-gate.sh` | 自检脚本 7 → 8 个，计数注释 439 → 536 |
| 7 | `deploy/aws/bundle.sh` | BUILD-INFO 的 md5 清单加入 `node/modules_privacy.py` |

### 二、关键设计：witness 不走链

花费 note 需要明文 witness（金额 / 盲因子 / 私钥）。如果让 witness 进交易，它就会进区块、进 `chain.json` —— 直接泄露。
所以把「构建」和「提交」拆开：**witness 只经 RPC 内存传递，用完即弃**；
进块的只有公开字段 `cm / cv / nf / Merkle 路径 / 所有权证明 / 范围证明`。
交易信封同时预留 `"v": 1` 版本字段，给 T4（环签名/zk）与 T6（证明压缩）留平滑升级位。

### 三、存储方案：快照 + 追加日志

| 文件 | 作用 | 写时机 |
|---|---|---|
| `node/data/privacy.json` | 全量快照（`tmp + os.replace` 原子替换） | 只在压实或关闭时写 |
| `node/data/privacy.log` | 追加日志，每含隐私交易的区块追加一条 | O(1) 追加；**无隐私交易时零写入** |

日志带 `seq` 与哈希链 `prev` / `hash`。装载时跳过 `seq ≤ 快照 seq` 的记录（压实后不重复回放），
日志起点由**快照 head** 决定。损坏（哈希链断裂 / 内容篡改）→ 隐私子系统降级，
**拒绝新的隐私交易但公链照常出块** —— 宁可少一个功能，也不能让节点起不来。

**实测体积基线**：单输出 shield 的 journal 记录 1176 字节；1 入 2 出 transfer 2078 字节；纯转账区块 0 字节。

### 四、上链跑通后发现并修掉的 3 个真实缺陷

第一版实现「纸面上是通的」，是专项自检把它们逼出来的。三个都属于「不修就会静默丢数据 / 永久降级」的级别：

| # | 缺陷 | 不修的后果 | 修法 |
|---|---|---|---|
| A | `_load()` 没把 `store.problems` 冒泡 | 快照损坏时 `ok` 仍是 `True`，账本被静默重置为空，**旧 note 全丢且无人察觉** | 快照读不出来即降级 |
| B | 尾部半行只在内存里忽略，没从磁盘截掉 | 崩溃后**下一笔隐私交易**会与半行拼成一行，该记录永远读不回，且此后每次启动都因这一行解析失败而**永久降级** | 检测到半行即把文件截断到干净长度 |
| C | `read_journal()` 固定以全零作为哈希链起点 | 压实后日志从快照 head 续写，下次启动误判「哈希链断裂」→ **永久降级** | 起点改由快照 `head` 决定 |

三个都写了回归用例，落在 `tests/test_privacy_chain.py` 第 7 / 8 / 9 节。

### 五、验证

- [x] `tests/` 8 个脚本 **536/536** 全绿（88 + 101 + 36 + 39 + **97** + 59 + 68 + 48），基线 439 项**一项不回退**
- [x] 部署包（`omnx-aws-deploy-new.tar.gz`，sha256 `87e6f606…7531`）按 `install.sh` 的目录布局暂存后，包内代码同样 97/97 通过
- [x] 隐私交易与普通转账**同区块混合打包**通过
- [x] 重启后 Merkle 根 / note 数 / nullifier 数 / journal head 完全一致；压实后不重复回放
- [x] `chain.json` 里不出现 `privacy` 键、不出现 `_sk`、不出现 note 明文金额与盲因子
      （正向对照：`chain.json` 里确实有该 note 的 `cm`，证明交易真的进块了）
- [x] 8 个 `exx_privacy*` RPC 方法端到端跑通（含 shield → 解密 → shieldedTransfer → 花费状态翻转）
- [x] `node/chain.py` / `node/rpc.py` / `node/modules_privacy.py` 语法与导入通过（Python 3.9.6）

### 六、回滚

保留 `node/chain.py.bak-20260924-210847`（`63f6080366aa2f32c2a642aea87106ef`）与
`node/rpc.py.bak-20260924-210847`（`06bbe696d67725ea5e3ebbfb2254e37f`）。
回滚 = 还原这两个 `.py` + 删除 `node/modules_privacy.py`。
`chain.json` **无需回滚**（旧代码读写不到隐私状态）；残留的 `privacy.json` / `privacy.log` 是惰性文件，留着不影响旧逻辑。

### 七、诚实边界（不要误读）

- 链上交易**仍是完全明文**（`from` / `to` / `value`），本层只是**新增了一个独立隐私账本**
- **T3 完成前没有资金通道**：`shield` 不扣 `chain.balances`，`unshield` 不给目标地址入账
- **发送方关联性未解决**（无环签名 / zk-SNARK）
- 手续费分账、质押折扣、回购销毁**尚未接入**隐私版口径
- 因此**金额可被反推**这个事实，在链上仍然成立
- `exx_privacyNewKey` 由节点进程生成密钥，**仅供测试网联调**；生产环境必须在客户端本地生成

---

## 2026-09-24 · 隐私账本第 1+3 期：note 账本引擎落地 —— 双花、造币、越权全部封死

> AWS 部署保持搁置。锁定的数据闸门不变：**完成前不得导入任何真实业务数据**。
> **本次仍为测试网，主网闸门保持关闭。**

### 一、这一期做了什么

把前两期的密码学原语拼成了**完整业务闭环**，新增 `node/notes.py`（纯新增文件，
不改动 `chain.py` / `rpc.py` / `evm_layer.py` / `modules_*.py` 一行）。

| 能力 | 实现 |
|---|---|
| Merkle 稀疏树 | 零填充，深度 32，可承载 2^32 张 note |
| note 派生量 | `cm = SHA3(owner_pk ‖ cv ‖ rho ‖ memo_hash)` |
| nullifier | `nf = SHA3(owner_pk ‖ rho)` |
| 所有权证明 | Schnorr 知识证明，转录绑定 nf + Merkle 根 |
| 三种操作 | `shield`（透明→私密）/ `transfer`（私密→私密）/ `unshield`（私密→透明） |
| L0 公开视图 | `public_view()` 只出聚合计数与根，**不含任何单笔明细** |

链上只出现三样东西：`cv`（Pedersen 金额承诺）、`cm`（Merkle 叶子）、`enc`（密文）。
`value` 与盲因子 `r` 永不出现。

### 二、攻击面矩阵（16 项，全部拒绝）

| 攻击 | 被哪一步挡下 |
|---|---|
| 同一张 note 二次花费 | nullifier 集合 |
| 偷换 `cv` 但保留 `cm` | 公开字段与 `cm` 不匹配 |
| 伪造 `cm`（不在树中） | Merkle 路径不成立 |
| 篡改 Merkle 路径 / 替换根 | 路径重算 ≠ 交易根 |
| 伪造 nullifier | nullifier 构造校验 |
| 用别人的私钥签所有权 | Schnorr 所有权证明 |
| 篡改输出范围证明 | 范围证明校验 |
| 换掉输出 `cv` 使金额变大 | 范围证明校验 |
| 手续费承诺偷换 / 凭空多加输出 | 守恒校验 |
| 同交易内 nullifier 重复 | 交易内去重 |
| 篡改 txid | 内容指纹比对 |
| 畸形交易（0/1/3 字段） | 一律返回 `False`，不抛异常 |

### 三、⚠️ 踩到并修掉的问题（都写进测试防回归）

1. **盲因子没配平** —— Pedersen 守恒要求「值配平 **且** 盲因子配平」。
   `shield` 里 note 的盲因子与手续费盲因子之和必须等于公开侧盲因子；
   `transfer` 要把输入盲因子拆给多个输出。最初漏了这层，守恒必然失败。
2. **`unshield` 无找零时不能随机取 `r_pub`** —— 没有找零就少了一个兜底自由度，
   必须让 `r_pub` 吸收全部差额，否则守恒失败。
3. **测试自身的假通过** —— 篡改字段后没重算 txid，于是所有篡改都被
   「txid 与内容不一致」提前拦下，测的根本不是被测的那一项。
   修法：加 `reforge()` 重算 txid，并**对每条篡改断言具体拒绝原因**。

### 四、⚠️ 仍然没有解决的问题（必须如实说）

- **链上交易仍是完全明文** —— 本期做的是**独立引擎**，一行都还没接进 `chain.py` / `rpc.py`
- 隐私账本是**内存态**，Merkle 树与 nullifier 集合没有任何持久化
- **发送方关联性未解决**：没有环签名、没有 zk-SNARK；花费时公开的 `owner_pk`
  可被关联（note 密钥与账户地址解耦能缓解，但不等于匿名）
- **手续费分账未接**：0.1% 底座费、70/30 分账、质押折扣、回购销毁仍是第 4 期的事
- 范围证明 10.4 KB / 张，链上带宽与存储代价未评估

> **引擎完成 ≠ 链上隐私生效。**

### 五、回归结果

| 项 | 结果 |
|---|---|
| `tests/test_notes.py` | ✅ **101/101** |
| `tests/test_rangeproof.py` | ✅ 59/59 |
| `tests/test_privacy.py` | ✅ 39/39 |
| `tests/test_omnimx_dex.py` / `test_platform.py` / `test_testnet.py` / `test_ws_subscribe.py` | ✅ 88 / 36 / 68 / 48 |
| tests/ 合计 | **439**（原 338） |
| `run-local-gate.sh` | 测试清单接入 `test_notes.py`（6 → 7 个脚本，计数 338 → 439） |
| `bundle.sh` | BUILD-INFO md5 清单加入 `node/notes.py` |

### 六、导入真实数据的闸门条件（更新）

1. `notes.py` 接入 `chain.py` / `rpc.py`（链上状态 + 持久化）
2. 透明账本与隐私账本挂接（shield/unshield 真正扣加余额）
3. 全量回归通过

在第 1 步落地之前，**链上一切照旧是明文**。


## 2026-09-24 · 隐私账本第 2 期：金额范围证明落地 —— 第 0 期的通胀攻击被钉死

> 按用户决定：**AWS 部署搁置**，优先推进账本层与范围证明。
> 锁定的数据闸门不变：**完成前不得导入任何真实业务数据**。
> **本次仍为测试网，主网闸门保持关闭。**

### 一、这一期解决了什么

第 0 期留了一个真实可行的通胀攻击并刻意钉进测试：

```
输入：C(100)               # 真实面值 100
输出：C(150) + C(-50)      # 值上 150 + (-50) = 100，守恒校验照样通过
→ 净凭空增发 50
```

根因不是实现 bug，而是**守恒校验的数学能力边界**：它只能保证「进 = 出 + 手续费」，
管不了每一项是不是负数。要堵住就必须给每个金额配一份**范围证明**。

第 2 期把这件事做完了。

### 二、新增文件

| 文件 | 说明 |
|---|---|
| `node/rangeproof.py` | 金额范围证明：按位分解 + 批量 Chaum–Pedersen 或证明（Borromean 同源思路），无需可信设置 |
| `tests/test_rangeproof.py` | 59 项自检，含篡改矩阵、畸形输入鲁棒性、零知识性质与通胀攻击验收 |

### 三、构造

```
1. v 拆成 bits 个比特：v = Σ b_i·2^i
2. 每个比特单独承诺 C_i = b_i·H + r_i·G，盲因子满足 Σ r_i·2^i = r
   → 于是 Σ 2^i·C_i == C，验证方 n 次点运算即可确认分解与 C 一致
3. 每个 C_i 做一次「二选一」或证明：
      Y_{i,0} = C_i        （比特 0）
      Y_{i,1} = C_i − H    （比特 1）
   谁手里有 r_i，谁就知道其中一个位置的离散对数
4. 所有比特共用一个全局挑战 e（批量版 Borromean），
   每个比特只公开一个位置的分挑战 e_{i,0}，另一个由 e − e_{i,0} 推出
```

链上新增联合校验入口，两条检查缺一不可：

| 检查 | 作用 |
|---|---|
| `Σ 2^i·C_i == C` | 分解必须真的还原出链上承诺（防拼装） |
| 全局挑战闭链 | 每个比特确实要么是 0 要么是 1（防负数、防越界） |

```
rangeproof.verify_conservation_with_ranges(c_in, c_out, c_fee, proofs_out, proof_fee)
→ (ok, reason)
```

### 四、第 0 期那个攻击现在的下场

`tests/test_rangeproof.py` 第 8 节逐条复现并验收：

| 项 | 结果 |
|---|---|
| 前提：伪造确实配平（旧缺口本身仍在，已验证） | ✅ |
| 攻击者造不出 `C(-50)` 的范围证明（金额在模 n 下映射到 `n-50`，远超 `2^64`） | ✅ 抛错 |
| 退一步谎报面值 0，承诺对不上 | ✅ 拒绝 |
| **★ 整笔伪造交易被拒，拒绝原因是「第 1 张输出缺范围证明」** | ✅ |

### 五、性能与体积（诚实交代）

| 指标 | 64 位证明 |
|---|---|
| 体积 | **10,466 字节** |
| 生成 | 约 278 ms |
| 校验 | 约 411 ms |

组成：64 个比特承诺 4160 B + `1+3×64=193` 个标量 6176 B。

**Bulletproofs 同强度只要约 674 B**，但实现复杂度高一个量级。
测试网阶段取「先正确、后压缩」，压缩列为后续优化项 —— 这一条必须写清楚，
不能对外宣称体积已经达到生产水平。

### 六、⚠️ 边界：密码学能力 ≠ 链上已经隐私

这一点必须讲明白，别把第 2 期误读成「隐私做完了」：

- 范围证明**已能正确工作**，但**还没有任何链上路径在用它**
- 链上交易仍是完全明文（`from` / `to` / `value`）
- 没有 note 模型、没有 nullifier 集合、没有 shield/unshield 桥
- **「金额可被反推」这个事实在链上仍然成立**
- 区块浏览器与 RPC 仍展示明文金额与地址

> 导入真实数据的闸门条件因此**没有放宽**，只是更精确了：
> **第 1 期链上 note/nullifier 集合 + 第 3 期 shield/unshield 桥完成并通过回归之后**，
> 才允许导入真实数据。范围证明是**必要不充分**条件。

### 七、回归结果

| 项 | 结果 |
|---|---|
| `tests/test_rangeproof.py` | ✅ **59/59** |
| `tests/test_privacy.py` | ✅ 39/39（新增 1 条说明性断言） |
| `run-local-gate.sh` | 测试清单接入 `test_rangeproof.py`（5 → 6 个脚本，计数 278 → 338） |
| `bundle.sh` | BUILD-INFO md5 清单加入 `node/rangeproof.py` |

### 八、分期状态

0 期 ✅ → 1 期 部分 → **2 期 ✅** → 3 期 shield/unshield 桥（下一步，关键路径）
→ 4 期 手续费/质押/回购改造 → 5 期 RWA/股权分层 → 6 期 匿名凭证 → 7 期 浏览器/RPC 分层呈现。


## 2026-09-24 · 隐私账本分层改造（第 0 期）：密码学底座落地 + 范围证明缺口钉进测试

> 需求：交易金额、from/to 地址做隐私加密上链；RWA 采用分层口径
> （总发行量/储备/审计/池总额公开，单笔明细隐私）；
> 股权类资产只做凭证存证与流转存证。手续费分账、质押折扣、回购销毁同步按隐私版重设计。
> **改造完成前不得导入真实业务数据。**
> **本次仍为测试网，主网闸门保持关闭。**

### 一、先说结论：EVM 账户模型下隐藏金额做不到

这不是实现问题，是模型冲突，必须先讲清楚：

| 位置 | 事实 |
|---|---|
| `node/chain.py` 状态结构 | 余额是**明文数字**（`balances`） |
| `node/evm_layer.py` 取余额 | **直接读**该明文 |
| `node/evm_layer.py` 转账前校验 | 同样按明文比对 |

余额公开 + 转账前后可读 ⇒ **单笔金额必然被反推**。
所以「只加密 from/to」或「只加密 tx.data」都挡不住金额泄露。

**结论：隐私转账必须换 note 模型**（Pedersen 承诺 + nullifier），
而不是在现有账户模型上打补丁。设计见 `docs/隐私账本分层设计.md`。

### 二、新增文件

| 文件 | 说明 |
|---|---|
| `docs/隐私账本分层设计.md` | 完整设计稿：L0/L1/L2 分层、双账本架构、密码学构造、原机制重设计、RWA/股权分层、KYC 匿名凭证、8 期实施表 |
| `node/privacy.py` | 密码学底座：点位运算 / SEC1 编解码 / hash-to-curve / Pedersen 承诺 / ECDH / note 加解密 / nullifier |
| `tests/test_privacy.py` | 38 项自检，已接入本地回归闸门 |

`node/privacy.py` 只提供**已被实测验证可用**的密码学原语，
不碰链上状态、不碰 EVM 层，因此**接入它是一个纯新增动作，零改动现有节点代码**。

### 三、绕开的三个 pycryptodome 限制（均已实测确认）

| 限制 | 应对 |
|---|---|
| `EccPoint` 没有减法运算符 | 用 `(n-1)` 取反后相加 |
| 标量乘不接受负数 | 负标量统一映射为 `n-k` |
| `EccPoint` 没有序列化方法 | 手工拼 SEC1（`0x04‖x‖y`，65 字节），已验证往返一致 |

### 四、本轮修掉的 3 个真 bug（都是自检抓出来的）

1. `conservation_ok` **符号写反**：原为 `sum(in) + fee − sum(out)`，
   正确是 `sum(C_in) − sum(C_out) − C_fee`。
2. `encrypt_note` **少一个字节**：`ser()[4:]` 应为 `ser()[2:]`
   （只去 `0x` 前缀、保留 `04`），否则 `decrypt_note` 的 SEC1 前缀校验必失败。
3. 自检脚本里两条**无效断言**（`... if False else True`、伪造构造错误）已重写。

### 五、⚠️ 安全边界：范围证明缺口（刻意的反面用例）

`tests/test_privacy.py` 第 9 节**故意保留**了一个真实可行的通胀攻击并钉进测试：

```
输入：C(100)                  # 真实面值 100
输出：C(150) + C(-50)         # 绕过入口检查直接构造 H*(n-50)
盲因子配平：rX + rY == rIn
→ 守恒校验照样通过，净凭空增发 50（150 可花，-50 不可花）
```

**这是刻意留的**：把安全边界钉死，防止以后有人误以为隐私层已经可用。

> **在范围证明（第 2 期）落地之前，`node/privacy.py` 产出的承诺不得承载真实价值，
> 不得导入任何真实业务数据。**

### 六、回归结果

| 项 | 结果 |
|---|---|
| `run-local-gate.sh` | ✅ **382/382**（原 344 + 隐私 38） |
| `preflight.sh --no-creds` | 通过 25 ｜ 失败 1（仅 `aws CLI 缺失`，待装）｜ 警告 6 |
| 部署包 | `deploy/omnx-aws-deploy-new.tar.gz` · 7,579,071 字节 · sha256 `bb0bdfd60700340a88fef56835434bacf66a43c3c249212d88815eaca88c4fb4` |

闸门已同步：`run-local-gate.sh` 测试清单加入 `tests/test_privacy.py`（4 → 5 个自检脚本，
计数注释 240 → 278）；`bundle.sh` 的 BUILD-INFO md5 清单加入 `node/privacy.py`，
便于上云后与服务端比对。

### 七、后续分期（第 0 期仅完成底座）

0 期（**本轮**：密码学底座）→ 1 note/nullifier → **2 范围证明（关键路径，未做）**
→ 3 shield/unshield 桥 → 4 手续费/质押/回购按隐私版重设计 → 5 RWA/股权分层
→ 6 KYC 匿名凭证 → 7 浏览器/RPC 分层呈现。


## 2026-09-24 · 单机四组件架构定稿：新增 seed 网关 + 安全组收紧 + 隔离自检误报修复

> 架构已确认：**单台 EC2（us-east-1 / t2.micro）+ 1 个 EIP** 承载
> 公链底座、rpc、seed、explorer 四套组件，靠 nginx 按域名做路由分发。
> 8545/8546 仅内网使用，对外唯一入口是 443。
> **本次仍为测试网，主网闸门保持关闭。**

### 一、新增：seed.omnxchain.com 的 P2P 种子网关

原先只有 rpc / explorer / docs / stats 四个 vhost，**没有 seed**。本轮补齐：

- 新增 `deploy/rpc-gateway/nginx/seed.omnxchain.com.conf`
- 接入 `install-gateway.sh`（安装 + `base-rpc` 占位符替换）

设计要点 —— **本链 P2P 是 HTTP Gossip，不是 devp2p，没有 30303 端口**，
对等同步复用 HTTP 端口的 `/net/*`，所以 seed 走 443 网关即可。该 vhost 只放开：

| 接口 | 用途 |
|---|---|
| `GET /health` | 存活探测 |
| `GET /net/status` | 节点发现 |
| `GET /net/blocks?from=N` / `POST /net/blocks` | 区块同步 / 广播 |
| `POST /net/tx` | 交易广播 |
| `GET /net/peers` | 对等拓扑 —— **仅白名单可见**（暴露拓扑属内部信息） |

其余路径一律 404。限流按 P2P 特征放宽（每 IP burst 200），全局仍设上限。

### 二、安全组收紧：8545/8546 从「VPC 网段」改为「运维 IP + VPC」

按确认口径（8545/8546、底座内部端口仅开放给运维 IP，禁止全网公开）调整：

```
22   ← 运维 IP        SSH
80   ← 0.0.0.0/0      certbot HTTP-01
443  ← 0.0.0.0/0      Nginx 网关对外入口（rpc / seed / explorer）
8545 ← 运维 IP + VPC  底座 RPC      （绝不出现 0.0.0.0/0）
8546 ← 运维 IP + VPC  底座 WS 订阅  （绝不出现 0.0.0.0/0）
```

`provision-ec2.sh` 原有的**红线复核**保留：一旦发现 8545/8546 对 `0.0.0.0/0` 放行，
脚本立即 `die` 中止。CloudFormation 备选模板同步对齐（`SshCidr` 也放行 8545/8546）。

### 三、修复：`verify-cloud.sh` 的隔离自检会 100% 误报

该脚本的红线自检是「从本机 `nc` 探测 `$EIP_IP:8545`，不通才算达标」。
但按新口径 8545 对**运维 IP** 是放行的，而执行脚本的这台机器通常就在运维白名单里 ——
于是自检必然探测成功，把「设计预期」误判成「底座暴露」，**报红且给出错误的处置指令**。

现已区分两种来源：

- 本机在运维白名单内 → 只 `warn`，提示「属设计预期，请再从非白名单网络复核一次」；
- 本机不在白名单 → 仍然 `bad`（真暴露，立即收紧）。

### 四、同步更新的文件

- `aws-deploy.env` / `.example`：新增 `SEED_HOST=seed.omnxchain.com`
- `preflight.sh`：`SEED_HOST` 纳入必填项与域名预解析检查
- `issue-cert.sh`：证书域名加入 `seed`，并设定期望状态码（seed 根路径按设计返回 404）
- `cloudflare-dns.sh`：`apply` 与解析校验都纳入 `SEED_HOST`
- `docs/内网RPC与WS配置.md`：补充「P2P 没有独立端口」与「安全组口径」两节

> ⚠️ 隐私公链这条：本轮**未做任何隐私实现**（只做了核对）。
> 当前链上交易仍是明文（`from` / `to` / `value`），与「链上无法获取交易金额」相反。
> 隐私档位（只隐藏身份 / 连金额一起隐藏）尚未定，定之前不动这块。

## 2026-09-24 · 上云编排定稿：9 步顺序固化 + 档位全链路透传 + CFN 模板硬伤修复

> 本轮把「先域名、再清泛解析、再主站、再 AWS、最后配链子域」这条执行顺序**固化进脚本**，
> 顺手修掉 CloudFormation 备选模板里两处会让整份模板**根本解析不了**的硬伤。
> **本次仅测试网，主网闸门保持关闭。**

### 一、`deploy-all.sh` 按执行计划重排为 9 步

原先的一键脚本没有覆盖「主站发布」和「泛解析清理」，与既定执行计划对不上。现按计划重排：

| 步骤 | 脚本 | 说明 |
|---|---|---|
| ① | `preflight.sh` | 回归全绿 + 包哈希一致 + 档位 / 域名 / 凭证 |
| ② | `dns-lookup.py` | 域名 NS 必须已托管到 Cloudflare（`--wait-ns` 可轮询等待） |
| ③ | `cf-dns-tool.py` | **清理 `*` 泛解析**，消除子域公网泄露 |
| ④ | `landing/deploy-pages.py` | 发布 `omnxchain.com` 主站（8 语言） |
| ⑤ | `provision-ec2.sh` | 建机 + 绑定 EIP + 远程安装（档位取配置） |
| ⑥ | `cloudflare-dns.sh` | `rpc` / `explorer` / `docs` / `stats` → EIP（全灰云） |
| ⑦ | `issue-cert.sh` | DNS 生效后签 SSL |
| ⑧ | `verify-cloud.sh` | 公网 RPC / TLS / 出块 / 限流 / 隔离 / 钱包 |
| ⑨ | `acceptance-report.sh` | 生成验收报告 |

两个刻意的顺序决定，写进脚本注释避免以后被改回去：

- **主站(④) 排在建机(⑤) 之前** —— 主站与公链零依赖，可以先上线，不必等 AWS 资源；
- **泛解析清理(③) 紧跟 NS 迁移(②)** —— 迁移完成的那一刻是子域暴露风险最大的窗口。

`--plan` 演练模式下，闸门未通过**不中止、继续打印**剩余步骤，便于一次看全链路；
且**刻意不调用 `provision-ec2.sh`**（它需要 aws CLI + 有效凭证），
演练的价值恰恰在于「还没配好凭证时也能把整条链路看一遍」。

### 二、档位全链路透传：切 full 只改 3 行配置

按「demo-lite 也要按生产标准固化，后续只切档位、不重构代码」的要求，
把实例规格从脚本文案里**全部参数化**，档位一路透传到远端：

```
deploy-all.sh  →  provision-ec2.sh  →  bootstrap-ec2.sh  →  install.sh  →  /etc/omnx.env
                                                              （写入 OMNX_DEPLOY_PROFILE，供事后审计）
```

切到付费高配正式环境只需改 `aws-deploy.env` 三行，**其余一律不动**：

1. `DEPLOY_PROFILE=full`
2. `INSTANCE_TYPE=t3.large`（full 要求 >= 2vCPU/8GB）
3. `STACK_NAME=omnx-prod`（换新栈名，避免与测试网资源重名冲突）

工程结构 / 目录布局 / 域名体系 / DNS 与泛解析清理逻辑 / 前端页面 / 网关配置
**全部与测试网同一套**，档位差异只体现为「装不装扩展套件」。
`full` 档在扩展套件缺失时会被 `install.sh` **拒绝继续**（失败关闭，而不是装完只有
18 个合约却当成完整版对外声称）。

#### 附带修复：`full` 档原先会产出「假完整版」

`install.sh` 的套件识别原为「`suite` / `mainnet` / `omnimx` 任一目录存在即可」，而：

- `deploy/omnimx/` 其实是 `Omnimx*` / `OmnxFactory` 的 **ABI 产物**
  （地址 `0x…B1`~`0x…B9`），属创世里的 18 个**基础**系统合约，不是扩展套件 ——
  它一直在包里，于是 `full` 档**恒可放行**；
- 放行之后，`install.sh` **并不会拷贝任何扩展套件**，只是打印一行「将安装扩展套件」。

两者叠加的后果：`DEPLOY_PROFILE=full` 装完只有 18 个合约，日志却显示装过套件 ——
正是要避免的「假完整版」。现已改为**按清单识别**：

- 只认 `suite/` 或 `mainnet/` 下带 `SUITE-MANIFEST.json` 且清单**非空**的目录；
- 不满足则拒绝安装并退出（失败关闭）；
- 识别通过后**真的拷贝**到 `/opt/omnx/deploy/<套件名>/`（沿用既有 `deploy/` 约定）；
- `preflight.sh` 同步改为同一口径，避免两边判定不一致；
- `SUITE-MANIFEST.json` 格式与「`omnimx/` 不是扩展套件」已写进
  `deploy/aws/README-AWS部署说明.md` 第八节，作为后续交付套件的契约。

已用 6 种包布局实测：`omnimx`-only / 套件缺清单 / 清单为空 → `full` 一律拒绝；
带清单的套件 → 放行并打印清单合约数；`demo-lite` 三种布局均正常跳过。

### 三、修复：CloudFormation 备选模板两处硬伤

`deploy/aws/cloudformation-omnx.yaml` 是备用路径（主路径是 `provision-ec2.sh`），
但两处缺陷会让它**连解析都过不了**：

1. **`Fn::Sub` 里裸写 shell 变量**：`HOST=${PRIV_IP}` / `OMNX_WS_HOST=${PRIV_IP}`。
   `!Sub` 会把 `${PRIV_IP}` 当成 CloudFormation 变量去解析，而它既不是参数也不是伪参数
   → 报 `variable PRIV_IP is not defined`。已改为 `${!PRIV_IP}`（转义为字面量）。
   同时新增的 swap 与档位变量也按同一规则转义。
2. **`GroupDescription` 的值含 `: ` 却未加引号**：
   `OMNX node: 443/80 public, ...` 在 YAML 里是非法标量 → 整份模板解析失败。已加引号。

顺带把该模板与 `bootstrap-ec2.sh` **对齐**，避免两条路径行为不一致：

- `InstanceType.AllowedValues` 纳入免费额度档 `t2.micro` / `t3.micro`（原先不含，无法表达 demo-lite）；
- 新增 `DeployProfile` 参数（`demo-lite` / `full`），写入 `/etc/omnx.env` 的 `OMNX_DEPLOY_PROFILE`；
- UserData 增加小内存实例的 **2GB swap 兜底**，与 `bootstrap-ec2.sh` 第④步同口径。

### 四、主站页脚补版本备注（8 语言）

执行计划要求「底部保留版本备注：单验证者确定性轮转 + HTTP Gossip，不具备拜占庭容错」。
原先该免责块位于页脚**之前**的最后一个 section，页脚本身只有版权与链接。
现新增 `foot.build` 词条（8 语言）并渲染进 `<footer>`，版本口径落在真正的底部：

```
Testnet build · single-validator deterministic rotation + HTTP gossip · no Byzantine fault tolerance
```

### 五、验证

| 项目 | 结果 |
|---|---|
| `deploy-all.sh --plan` | ✅ 9 步全打印，收尾「PLAN 完成，未改动任何线上资源」，退出码 0 |
| shell 语法 | ✅ `bash -n` 36 个 `.sh` / `.command` 全部通过 |
| Python 语法 | ✅ `ast.parse` 574 个 `.py` 全部通过 |
| CFN 模板 | ✅ YAML 解析通过；`!Sub` 内仅剩合法 CFN 参数引用 |
| i18n | ✅ 真实 JS 引擎下 8 语言 × 49 词条全部有值，无空值、无回退到 key |
| 前置闸门 | 通过 23 / 失败 2 —— 两项均为外部前置：**aws CLI 未安装**、**NS 尚未迁 Cloudflare** |

### 六、修正：Cloudflare Token 权限说明不完整（会导致第 ④ 步 403）

DNS 相关脚本与主站发布脚本读的是**同一个** `~/.omnx/cloudflare.token`，
但两者需要的权限不同：

| 用途 | 权限 |
|---|---|
| DNS 解析 / 泛解析清理 | `Zone` → `DNS` → 编辑 |
| 主站发布（Cloudflare Pages） | `Account` → `Cloudflare Pages` → 编辑 |

而 `setup-secrets.sh` 向导原先只让用户选「编辑区域 DNS」模板 —— 按这个配，
第 ④ 步发布主站时会 403，且要等到那一刻才发现。现已改为：

- 向导明确要求用**创建自定义令牌**，并把两项权限都列出来；
- 向导结尾增加 `deploy-pages.py --check`，让**两项权限在配 Token 当场就能验完**；
- `aws-deploy.env` / `.example` / `AWS上云部署手册.md` 同步更正。

### 七、边界与红线（重申）

- **本次仅部署测试网**，主网闸门保持关闭；未收到明确文字「可以上主网」不得执行主网部署。
- 对外口径固定为「HTTP Gossip + 单验证者确定性轮转，**不具备拜占庭容错**」，严禁宣称 BFT。
- `base-rpc.omnxchain.com` 不进公共 DNS、不写进任何对外文档。
- 旧副本（`chain.py` / `rpc.py` / `modules_rwadex.py` / EVM 标准层 / 公网网关 / RPC 限流）
  **零改动、原样保留**。

## 2026-09-24 · 主站落地页上线准备 + 部署档位落地 + DNS 假安心修复

> 本轮围绕三件事：把 `omnxchain.com` 主站做出来（**不依赖公链、不依赖 AWS**）、
> 把「t2.micro 只跑轻量 Demo」这条策略写进配置与闸门、修掉 DNS 检查里
> 「把 dig 报错当成解析结果」的假安心缺陷。**本次仅测试网，主网闸门保持关闭。**

### 一、新增：主站落地页（Cloudflare Pages）

目录 `deploy/landing/`，独立发布单元，**与公链节点 / EC2 / RPC 网关零依赖关系**：

| 文件 | 说明 |
|---|---|
| `index.html` | 自包含单页（样式内联、脚本内联），无任何外部 CDN 依赖 |
| `assets/i18n.js` | 8 语言词典（54 词条 × 8 语言），`en` 为源语言与默认回退 |
| `assets/omnx-logo.png` | 与浏览器 / 交易所页面同一份品牌 logo |
| `_headers` | Cloudflare Pages 响应头：CSP / HSTS / 缓存策略 |
| `404.html` | 同主题 404 |
| `deploy-pages.py` / `.sh` | 发布脚本。用 **Cloudflare REST API 直传**，只用 Python 标准库 —— 因为本机是 macOS 自带 Python 3.9.6，**没有 Node，装不了 wrangler** |

关键设计：

- **视觉与现有页面对齐**：主题变量逐字沿用 `node/explorer.html`
  （`--bg:#0d1117 --card:#161b22 --line:#30363d --fg:#e6edf3 --muted:#8b949e --accent:#3b82f6`），
  字体栈、圆角、按钮风格一致；**未改动 `node/` 下任何前端文件**。
- **RPC 未上线不报错**：在线状态探测做优雅降级，节点没起时显示「RPC coming soon」，不弹错、不白屏。
- **多语言**：英语为源语言，页面 HTML 本身就写英文 —— 即使 `i18n.js` 加载失败，页面依然完整可读。
- **对外口径**：页面底部明确标注「单验证者确定性轮转 + HTTP Gossip，**不具备拜占庭容错能力**」，
  全文不出现 BFT 宣传口径。

> 上线顺序结论：**主站可以先上**。真正唯一需要等待的是「NS 托管到 Cloudflare」（最长 48h），
> 与公链、EC2 可以并行推进。

### 二、新增：部署档位（demo-lite / full）

应「t2.micro 免费实例只部署轻量 Demo，18 个基础系统合约，保留全部前端 UI 菜单，
不部署扩展合约」的策略，把档位做成**配置项 + 闸门校验**，而不是靠人记：

| 位置 | 改动 |
|---|---|
| `aws-deploy.env` / `.example` | 新增 `DEPLOY_PROFILE=demo-lite`（默认）/ `full`，并写明各自允许的实例规格 |
| `deploy/aws/install.sh` | 读档位并写入 `/etc/omnx.env` 的 `OMNX_DEPLOY_PROFILE`；demo-lite 显式打印「跳过哪些扩展套件」；**full 档若包内没有扩展套件则拒绝安装** —— 避免装完只有 18 个合约却被当成完整版对外声称 |
| `deploy/aws/preflight.sh` 第⑨段（新增） | 校验档位合法性 + 实例规格与档位是否匹配 + demo-lite 下基础合约必须恰好 18 个 + full 档扩展文件是否齐全 |

**同时如实记录一处缺口**：`node/modules_suite.py`、`deploy/suite/`、`tests/test_suite.py`
在更早的会话中丢失，全盘搜索确认已不存在。因此 **`full` 档目前被闸门拒绝**，
不会出现「以为装了完整版、实际只有 18 个」的静默偏差。

另：D1~D4（`modules_rwadex.py`）由 `chain.py:499` 与 `rpc.py` 内置加载，属**运行时行为**，
不在「装 / 不装」的可裁剪范围内 —— 要拆它必须改正在运行的节点代码，
与「零改动运行中节点」的约束冲突，故本次不动。

### 三、修复：DNS 检查的三处缺陷（其中两处是**假安心**）

| # | 问题 | 后果 | 修复 |
|---|---|---|---|
| 1 | `preflight.sh` 第⑤段调用了**未定义**的 `info()` | 一旦走到该分支就 `command not found` | 补上 `info()` 函数 |
| 2 | 所有 DNS 查询直接用 `dig +short` | 查询失败时 dig 把 `;; connection timed out` 打到 **stdout**，`+short` 拦不住 → 脚本**把报错文本当成解析结果**。NS 判定显示乱码；主域已有的 A 记录被过滤成空后**误报「✅ 无 A 记录」** | 新增 `deploy/aws/dns-lookup.py`（DoH 优先、dig 兜底），**严格区分「查到 0 条」与「根本没查成」**，用退出码 0/1/2/3 表达 |
| 3 | `dnsq ... | grep | tr` 后再取 `$?` | `$?` 拿到的是管道末端 `tr` 的退出码（恒 0），「查询通道不可用」分支**永远不会触发** | 先把输出收进变量再取 `$?`，`preflight.sh` 与 `cloudflare-dns.sh` 同步修正 |

顺带修正 `base-rpc.omnxchain.com` 的判定：实测它**能被公网解析** ——
但根因不是节点暴露，而是 Porkbun 的 `*` 通配符泛解析。现在会区分这两种情况分别提示。

### 四、新增：泛解析清理工具

实测确认注册商存在 `*` 通配符，**任何子域都会被公网解析**（含必须仅内网可见的 `base-rpc`）。
新增 `deploy/aws/cf-dns-tool.py`：

```bash
python3 deploy/aws/cf-dns-tool.py list-wildcard       # 列出 *. 记录（A/AAAA/CNAME）
python3 deploy/aws/cf-dns-tool.py purge-wildcard      # 删除（支持 --dry-run）
python3 deploy/aws/cf-dns-tool.py verify              # DoH 实测复核，无需 Token
```

`cloudflare-dns.sh` 新增同名子命令 `purge-wildcard` 转发调用。
另修：`check`（纯公共 DNS 查询）原先强制要求先配 Cloudflare Token 才能跑，现改为**不需要凭证**。

### 五、打包

| 项 | 值 |
|---|---|
| 部署包 | `deploy/omnx-aws-deploy-new.tar.gz` |
| 大小 / sha256 | **见同目录 `omnx-aws-deploy-new.tar.gz.sha256`**（见下方说明） |
| 逐文件 md5 清单 | 见包内 `BUILD-INFO.txt` |
| 新增入包 | `landing/`（主站落地页） |
| 脏文件 | 0 |

> **为什么这里不写死大小 / sha256 / 条目数**：本文档本身随包发布。
> 在文档里写入本包自己的体积或哈希，一写入就改变了包内容，那个值随即失效 —— 自相矛盾。
> 因此改为：**权威校验值只放在随包发布的 `omnx-aws-deploy-new.tar.gz.sha256`**；
> 开发机上的 `deploy/aws/aws-deploy.env` 由 `bundle.sh` 每次打包自动回写真实哈希。
> `preflight.sh` 在 `PKG_SHA256` 为空时会自动回落到 `.sha256` 文件读取。

另修：`preflight.sh` 第⑦段的内嵌 Python 用了
`f"{'\033[32m...' if ...}"` —— **Python 3.9 的 f-string 表达式部分不允许出现反斜杠**
（该限制到 3.12 才放开），本机正是 macOS 自带 Python 3.9.6，一执行就 `SyntaxError`。
这个缺陷此前一直没暴露，因为闸门文件不存在时走的是 else 分支、根本不会执行到它 ——
**只有在回归全部通过、准备上云的那一刻才会炸**。现已把 ANSI 转义提到普通语句里先算好。
并已批量复查全部 shell 脚本中的内嵌 Python 块（7 个），确认均可在 3.9.6 下编译。

修复：`bundle.sh` 的排除规则原为 `*.bak-*`，**漏掉了裸命名的 `*.bak`**，
导致 3 个历史备份文件混入包内。现已同时排除 `*.bak` 与 `*.bak-*`，
`preflight.sh` 的脏文件检测正则同步收紧为 `\.bak($|[-.])`。

### 六、修复：本地闸门把 RPC 与 WS 分到同一个端口，导致 WS 永远不通

`run-local-gate.sh` 的 `pick_port()` 只探测「此刻能否绑定」，
而 RPC 端口这时**还没有真正 bind**（节点要到下一步才启动），
于是 WS 顺着往上找，又挑回了同一个号（实测两者都是 8587）。

后果很隐蔽：HTTP 先绑成功 → WS 的 `HUB.start()` 抛 `OSError` →
被 `rpc.py` 捕获后**只打印一行 ⚠️ 警告就继续跑**。表现是
「节点一切正常、RPC 正常、但 WebSocket 永远握手失败」，
而 `check-node.sh` 的 WS 探针会打到 HTTP 端口上，拿到 `HTTP/1.0 200 OK` 而非 `101`。

修复：`pick_port()` 增加「排除本轮已分配端口」参数，并在分配后断言两者不同号。

> 修复前后对比：本地闸门 **342/344 → 344/344**。
> 修复后 WS 实测：`eth_subscribe(newHeads)` 收到 4 条实时推送，高度 81 → 84 递增正常。

### 七、验证

- [x] 本地全量回归闸门 **344/344 全绿**（check-node 7 / check-wallet-tx 19 / check-evm-dex 42 / tests 240 / shell-syntax 36）
- [x] WS 实时推送实测通过（收到 4 条 `newHeads`，高度递增）
- [x] `bash -n` 全部 shell 脚本 **36 个，0 错误**
- [x] `ast.parse` 全部 Python 文件 **574 个，0 错误**
- [x] 全部 shell 脚本内嵌的 Python 块（7 个）在 Python 3.9.6 下编译通过
- [x] `index.html` 标签全部配对；CSS 变量定义 9 / 使用 9，无未定义
- [x] i18n 词条：HTML 引用 48 个**全部有定义**，54 个词条**每个均为 8 种语言**
- [x] `i18n.js` 与首页内联脚本经 JavaScriptCore 实际执行，语法与顶层逻辑通过
- [x] 首页无外部 CDN 依赖；全文无 BFT 宣传口径
- [x] 部署包泄漏自检：不含 `aws-deploy.env`、`.gate/`、`reports/`、`.pem`、token 文件

---

## 2026-09-24 · AWS 测试网上云：部署工具链补齐 + 两处「新机静默失败」硬伤修复

> 目标：把「从零到公网可访问」的上云流程做成可重复执行的自动化，并把新机首次安装
> 会踩的坑在本地就堵死。**本次仅测试网，主网闸门保持关闭。**

### 一、修复两处真实缺陷（都会在**全新机器**上触发，且第二处是静默失败）

| # | 文件 | 缺陷 | 后果 | 修复 |
|---|---|---|---|---|
| 1 | `deploy/aws/install.sh` | 只在 `$AWS_DIR/node` 找代码，而部署包是**扁平布局**（`<root>/node`），该路径不存在 | 新机执行安装**直接 `exit 1`**，整个上云流程卡在第一步 | 新增 `resolve_asset()`，自动兼容 `<root>/…`、`<root>/aws/…`、`<root>/../…` 三种布局 |
| 2 | `deploy/aws/install.sh` | 创世被复制到 `/opt/omnx/genesis.json`，但节点默认读 `<node>/../deploy/genesis.json` | 未设 `OMNX_GENESIS` 时 `load_genesis_config()` 返回 `None` → **静默回退到内置 `DEFAULT_CONFIG`**：链正常出块、不报任何错，但总量、18 个系统合约地址、代币分配全是默认值 | ① 创世同时写入 `/opt/omnx/deploy/genesis.json`（节点默认路径）与 `/opt/omnx/genesis.json`；② `/etc/omnx.env` 固化 `OMNX_GENESIS` / `OMNX_PLATFORM_CFG`；③ **启动前硬校验**：`load_genesis_config` 返回 `None` 或系统合约数 ≠ 18 就拒绝启动 |

第 2 条是本次最危险的一处：**它不会报错，只会让链以错误的创世运行**。现在被启动前硬校验挡住。

> 另修：`install.sh` 的健康检查原先固定探测 `127.0.0.1:8545`，
> 而节点按设计绑定内网 IP（禁止 `0.0.0.0`）→ 必然误报"未就绪"。
> 现改为按 `/etc/omnx.env` 里的真实 `HOST` 探测。

### 二、新增上云自动化（`deploy/aws/`，11 个脚本）

| 脚本 | 作用 |
|---|---|
| `preflight.sh` | 前置闸门：工具链 / AWS 凭证与区域可用性 / 包完整性 / 本地回归记录 / 创世核对 |
| `run-local-gate.sh` | 独立数据目录 + 独立端口从零建链，跑全量回归并写出闸门记录 |
| `install-aws-cli.sh` | AWS CLI 免 sudo 安装器（`pip --user` → `pkgutil` 展开 → 官方 pkg 三级兜底） |
| `provision-ec2.sh` | 安全组 / `t3.medium` / EIP / 上传部署包 / 触发远程安装，支持 `--plan` 演练 |
| `bootstrap-ec2.sh` | EC2 端：解包 → 落 `.env` → 装节点 → 装新版网关 → 6 项自检 |
| `cloudflare-dns.sh` | Cloudflare DNS 增删改查 + 强制清理 `base-rpc` 公共解析 |
| `issue-cert.sh` | DNS 生效后签发 Let's Encrypt 证书（4 个域名）并校验 |
| `verify-cloud.sh` | 公网端到端验收（9 组 40+ 项），产出机器可读 JSON |
| `acceptance-report.sh` + `build-acceptance-report.py` | 生成中文验收报告（Markdown） |
| `deploy-all.sh` | 一键编排，支持 `--plan` 演练与 `--from N` 断点续跑 |
| 桌面 `OMNX测试网-上云部署.command` | 双击入口（**新建**，不覆盖既有的本地节点启动器） |

### 三、闸门机制：防止「用没测过的包上云」

`run-local-gate.sh` 把**当时部署包的 sha256** 一并写进闸门记录；
`preflight.sh` 在放行前比对「闸门记录的包哈希」与「当前待发包哈希」。
**测试之后只要重新打过包，哈希就对不上，闸门自动失效、必须重跑回归。**

这条是为了堵住「本地测过 A 包、上云却传了 B 包」这类问题。

### 四、执行顺序上的一个关键点

Let's Encrypt 的 HTTP-01 校验要求域名**已解析到本机公网 IP**。
首次上云时 DNS 还没配，因此：

```
建机 → 装网关(SKIP_CERT=1，只落地配置) → 配 DNS → 等解析生效 → issue-cert.sh 签证书 → 验收
```

**先签证书再配 DNS 必然失败**，这是首次上云最常见的坑，已固化为脚本顺序。

### 五、本轮验证结果

| 项目 | 结果 | 说明 |
|---|---|---|
| `tests/` 四个自检脚本 | **240/240**（88+36+68+48） | 本轮实测重跑，全绿 |
| 部署包 sha256 | 见同目录 `omnx-aws-deploy-new.tar.gz.sha256` | 本轮重打包（含修复版 install.sh）；bundle.sh 已改为自动回写，不再手工同步 |
| 创世硬校验 | ✅ 有效 | 合法创世 → 18 合约 / 10 亿；非法路径 → `None` 并拒绝启动 |
| 验收报告生成器 | ✅ 正常 | 无云端数据时优雅降级，正确标出「多签 1/5」缺口 |
| `deploy-all.sh --plan` | ✅ 闸门正确拦截 | 缺 aws CLI / 无回归记录 → 拒绝上云，未创建任何资源 |
| 全部 shell 脚本 | ✅ 语法通过 | `bash -n` 全绿 |

> ⚠️ 本次沙箱环境**禁止一切 TCP 绑定与外网访问**，因此：
> `run-local-gate.sh`、`provision-ec2.sh`、`verify-cloud.sh` 的**联网端到端执行未在本机沙箱内跑通**，
> 需在你的 Mac 终端（无沙箱限制）执行。脚本逻辑与语法已验证。
> 测试脚本本身是完全离线的（内置 mock），所以 240/240 是本轮真实结果。

### 六、修正过时文件

- `deploy/aws/cloudformation-omnx.yaml`：区域写死 `us-east-1` 的 AMI（在 `ap-southeast-1` 会启动失败）、
  默认 `t3.small`、`VpcCidr` 默认 `10.0.0.0/8`、卷 20G 均已修正；
  AMI 改为 SSM 动态引用（区域自适应）；并加显著标注：**这是备选路径，主流程用 `provision-ec2.sh`，两条路径不要混用。**

### 七、边界与红线（重申）

- 本次**仅测试网**；主网闸门保持关闭，未收到明确文字指令不执行任何主网操作。
- 对外口径：**HTTP Gossip + 单验证者确定性轮转**，**不具备拜占庭容错能力，严禁宣称 BFT 共识**。
- 原有 18 个系统合约**零改动**；桌面既有的 `OMNX公链-在线版启动.command` **未改动**。
- 已知缺口（不掩饰）：创世 `platform.authority` 当前为 **1 个地址**，目标是 5 个理事会 EOA。
  测试网可继续运行，但**主网前必须补齐并完成权限轮换**。

---

## 2026-09-24 · AWS 测试网上云前置：打包修复 + EVM 接口修复 + 全量回归

> 目标：修复旧包硬伤，重新打包，本地全量回归 Pass 之后才允许上云。
> 结果：**本地全绿**（节点自检 5/5、EVM 钱包 19/19、EVM+DEX 42/42、回归 240/240）。
> 主网部署闸门**保持关闭**，本轮只做测试网。

### 一、打包硬伤修复

旧包 `omnx-aws-deploy.tar.gz`（9 月 8 日）缺文件，**禁止直接上云**。

| 文件 | 改动 |
|---|---|
| `deploy/aws/bundle.sh` | ① `rpc-gateway/` 全套入包；② 新增 `BUILD-INFO.txt`（构建时间 + 关键文件 md5 + 「node/evm_layer.py 必须存在」提醒）；③ 新增 `EXCLUDES` + `copy_tree()`：排除 `*.bak-*` / `*.pre-*` / `data/` / `__pycache__` / `*.log` / `*.pid`；④ 排除历史误操作残留的空目录链（`modules_dex.py node/…`，只做不进包，不动本机文件）；⑤ 同时产出 `omnx-aws-deploy.tar.gz` 与 `omnx-aws-deploy-new.tar.gz` |
| `deploy/aws/install.sh` | **不再安装旧版 `nginx-omnx-rpc.conf`**（无 QPS 限流 / 无非法 JSON 过滤 / 无上游断连重试 + JSON 错误封装，正是 `Unexpected end of JSON input` 的成因）；改为引导执行新版 `rpc-gateway/scripts/install-gateway.sh`，找不到新版配置时拒绝安装并给出明确提示 |

**包内容核验**：`__pycache__`/`bak-`/`.pre-`/`.pyc`/`corrupt` 均为 0；`node/data` 已排除；
`node/evm_layer.py`、`node/rpc-config.js`、`rpc-gateway/nginx/rpc.omnxchain.com.conf`、`genesis.json` 全部在包内。

### 二、EVM 标准接口修复（4 个真实缺陷）

用新增的 `scripts/check-evm-dex.py` 逐条打出来的，不是推测：

| # | 文件 | 缺陷 | 影响 | 修复 |
|---|---|---|---|---|
| 1 | `node/rpc.py` `_ETH_SELECTORS` | `allPairsLength()` 的选择器写成 `0x1e3dd18b`——那是 `allPairs(uint256)` 的选择器 | 钱包查交易对总数返回空值 | 更正为 `0x574f2ba3`，并把 `0x1e3dd18b` 正名为 `allPairs` |
| 2 | `node/rpc.py` `_eth_call_shim` | 工厂 `getPair` 把 `CONTRACT_ZONE` 的**键名**（`OMNIMX_TOKEN`/`MOCK_USDT`）当代币符号传给 `DexFactory.get_pair` | `getPair(OMNX,USDT)` 永远返回零地址 | 改为映射到真实符号 `OMNX`/`USDT` |
| 3 | `node/chain.py` `add_tx` / `add_tx_external` / `_apply_tx` | 入池时不归一 `to` 的大小写，而账本 key、`eth_getBalance`、EVM 层一律小写 | **所有真实钱包（MetaMask/ethers）发的是 EIP-55 校验和地址**，转账会记到另一个 key 上 → 「转账成功但查不到余额」，资金不可达 | 入池与出块落账两处统一 `to.lower()`，并加 EIP-55 回归用例 |
| 4 | `node/rpc.py` `_eth_call_shim` | `symbol()` / `name()` 返回右对齐整数（前导零），不是标准 ABI `string` | ethers.js / web3.js 解码得到空串，代币名称符号在钱包里显示不出来 | 新增 `_abi_string()`，按 `offset(0x20) + length + 右补零数据` 标准编码返回 |

**连带归一**：`modules_platform.py` 的 `FEE_WALLET`(`0x…C1`) / `BLACKHOLE`(`0x…dEaD`) 与
`modules_dex.py` 的 `DEAD`(`0x…dEaD`) 统一改为全小写。
地址数值完全不变，只统一大小写——否则「转到回购钱包」会写进另一个 key，形成同名地址两套余额。
（`DexToken._balance/_set_balance`、`evm_layer._clamp_addr` 本来就 lower，这两个常量是仅有的例外。）

### 三、DEX 引导脚本修复（新机首次上线的真实阻塞）

`scripts/deploy-omnimx.py` 在「水龙头对外发放已关闭」的新机上跑不通，三处修复：

| # | 问题 | 修复 |
|---|---|---|
| 1 | 依赖 `exx_faucet` 给引导账户发币，而发放链路默认关闭 → 新机直接报错中断 | 新增 `fund()`：优先水龙头，被关闭时回退为「创世水龙头账户直转」（创世 1% 分配，内部引导，不对外发放） |
| 2 | 5 次补水不等待进块，同账户 nonce 冲突只成功 1 笔 → 余额不足，OMNX/USDT、OMNX/USDC 加池全部失败 | 每笔都走 `send()` 等待进块；单笔额度略高于水龙头以覆盖后续十几笔 gas（本链 `gas_price=1`、单笔基础 gas 21000，即每笔约 21000 OMNX） |
| 3 | 时间锁 `eta` 只留 +1 秒余量，而节点在**出块时**校验 `eta >= now + min_delay`，提交到进块本身要 3~6 秒 → 排队必失败 | 余量加到 +10 秒 |

### 四、新增回归脚本

| 文件 | 覆盖 | 结果 |
|---|---|---|
| `scripts/check-evm-dex.py`（新增，42 项） | `eth_call` 只读（代币 / 池子 / 路由 / 工厂）、钱包签名写交易（approve / transfer / receipt / nonce / EIP-55）、DEX 池子交互（多费率报价 / 兑换 / 滑点保护 / 加减流动性闭环）、AMM 计价公式**独立复算** | 42/42 |
| `scripts/check-wallet-tx.py`（修复） | 原脚本硬编码 `/private/tmp/omxpy` 且默认端口 18545 → 改为自动定位仓库内置 `node/vendor`（离线与 AWS 通用），RPC 可命令行传入 | 19/19 |

**顺带验证到的既有能力**：`exx_dexQuote` 带 `address` 参数时，会按该地址的 OMNX 持仓档位下调有效费率
（实测管理员档位 30 bps → 有效 21 bps，折扣 3000 bps = 7 折），兑换成交额与「有效费率 + 恒定乘积」复算完全一致。

### 五、本机 8545 端口阻塞说明

本机 8545 存在一个由用户手工启动、沙箱无权限结束的旧节点进程（可写账本、能正常出块）。
因此本轮全量回归是在**独立数据目录的全新链**上跑的（`DATA_DIR=/tmp/omnx-regress/data*`，临时端口 8585~8592），
这恰好等价于 AWS 上「从零建链」的路径，并且顺带验证了 `deploy-omnimx.py` 的全新机器引导流程。
端口 8545 上那个进程需要用户自行重启（双击「OMNX公链-在线版启动.command」）才会加载本轮修复的代码。

### 六、验证步骤（可复现）

```bash
cd /Users/mac/Desktop/OMNX公链/02-测试网

# 全新链从零跑通（等价 AWS 首次上线）
DATA_DIR=/tmp/omnx-regress/data PORT=8589 OMNX_WS_PORT=8590 python3 node/rpc.py &
NODE_URL=http://127.0.0.1:8589 WS_URL=ws://127.0.0.1:8590 bash scripts/check-node.sh   # 5/5
python3 scripts/deploy-omnimx.py --rpc http://127.0.0.1:8589/rpc                        # 全流程引导通过
python3 scripts/check-wallet-tx.py http://127.0.0.1:8589/rpc                            # 19/19
python3 scripts/check-evm-dex.py   http://127.0.0.1:8589/rpc                            # 42/42
for t in tests/test_*.py; do python3 "$t"; done                                          # 240/240

# 重新打包
bash deploy/aws/bundle.sh
```

### 七、交付

| 交付物 | 说明 |
|---|---|
| `deploy/omnx-aws-deploy-new.tar.gz` | 本轮完整部署包（含 `rpc-gateway/`、`node/evm_layer.py`、`BUILD-INFO.txt`） |
| `deploy/omnx-aws-deploy-new.tar.gz.sha256` | 包校验值（包外单独一份） |
| `deploy/aws/AWS上云部署手册.md`（新增） | 从零到公网可访问完整上云文档：EC2 建机 / 安全组 / Cloudflare DNS / SSL / 上线验收清单 / 故障排查 / 宣传红线 |
| `deploy/aws/install.sh` | 已改为只允许新版分层网关配置 |
| `scripts/check-evm-dex.py` | 新增 EVM + DEX 回归 |

### 八、边界与红线（重申）

- 本轮**只部署测试网**；主网部署闸门保持关闭，未收到明确书面指令不得执行主网部署。
- 原有 18 个系统合约全部保留、隔离，本轮不改动其地址与逻辑。
- 对外宣传口径：P2P 为 **HTTP Gossip 广播，不是 BFT 共识层**；出块为单验证者确定性轮转，
  **不具备拜占庭容错能力，严禁对外描述为 BFT 共识**。
- `base-rpc.omnxchain.com` 绝不对外公布；对外唯一 RPC 入口是 `rpc.omnxchain.com`。

## 2026-09-24 · 节点出块故障根因修复 + RPC 分层架构（rpc.omnxchain.com）

### 一、故障：链停止出块 / `chain.json` 读不出来

**现象**：区块高度卡死，区块浏览器报 `RPC 错误：exx_status`，`node/data/chain.json` 无法解析。

**根因（两个并发写入方共用同一个临时文件）**

| # | 位置 | 问题 |
|---|---|---|
| 1 | `node/chain.py:948 _save()` | 临时文件名写死 `chain.json.tmp`，多写入方共用 → 互相截断 |
| 2 | `node/rpc.py:133` | `threading.Thread(target=autominer).start()` 写在**模块顶层**，被 import 就多起一个出块线程 |
| 3 | `node/evm_layer.py:706` | `eth_call` 里 `import rpc as _rpc`；以脚本方式启动时模块名是 `__main__`，这行会**二次执行 rpc.py** → 第二个 Chain 实例 + 第二个出块线程 + 第二个 8546 绑定 |

三者叠加：同一份 `chain.json` 被两个进程交错写入，块数组与状态区被交叉截断。

### 二、数据修复（可证明，非猜测）

- 以 `"timestamp":` 为锚点切块，对每块穷举候选，用 `sha3(json.dumps(core, sort_keys=True, default=str))` 命中文件里的 `hash` 才算修复；
- **500/500 块哈希自洽**、索引连续 `124518…125017`、前后链接零断裂、出块间隔稳定 3 秒；
- 尾部状态区 4 处拼接伤按明文上下文推导修复（orderbook→dex 边界、USDT 转账被截断的 sender/to、LiquidityAdded 缺失的 `"kind"`、金库快照被截断的 `0x…0007` 项）；
- 修复后 `json.load` 通过，顶层键 22 个，`height = 125018`；
- 损坏原件另存 `node/data/chain.json.corrupt-20260924-085146`。

### 三、根因修复（三处加固）

| 文件 | 改动 |
|---|---|
| `node/chain.py` | `_save()` 临时文件名加 `pid + thread_id`，写完 `fsync` 再 `os.replace`，最后清理残留 |
| `node/rpc.py` | 出块线程抽成 `start_autominer()`，只在 `if __name__ == "__main__"` 里调用，import 不再有副作用 |
| `node/evm_layer.py` | `import rpc as _rpc` 改为从 `sys.modules` 取运行中的 `rpc`/`__main__`，杜绝二次执行 |

### 四、RPC 分层架构（新增 `deploy/rpc-gateway/`）

**前端故障**：`Failed to execute 'json' on 'Response': Unexpected end of JSON input`
**根因**：前端直连底座 RPC，中间无缓冲；长尾响应被中断时半截 JSON 直接落到浏览器。

| 层 | 域名 | 对外 |
|---|---|---|
| 公共网关 | `rpc.omnxchain.com` | ✅ 前端 / DApp / 交易所 / 开发者唯一入口 |
| 底座节点 | `base-rpc.omnxchain.com` | ❌ 仅内网，安全组 + 防火墙双白名单 |
| 浏览器 / 文档 / 监控 | `explorer` / `docs` / `stats` | ✅ / ✅ / ⚠️ 白名单 |

**交付物**

| # | 文件 | 内容 |
|---|---|---|
| 1 | `node/rpc-config.js`（新增） | 前端统一 RPC 配置：线上默认 `https://rpc.omnxchain.com`，本机走同源 `/rpc`，含空体/坏 JSON 退避重试 |
| 2 | `node/explorer.html` `dex.html` `demo.html` `OMNX公链浏览器-离线版（双击打开）.html` | 接入 `rpc-config.js`；DEX 钱包网络配置不再硬编码 `127.0.0.1` |
| 3 | `scripts/serve-web.py` | 新增 `/rpc-config.js`、`/explorer.html`、`/demo` 路由 |
| 4 | `deploy/rpc-gateway/nginx/*.conf` | 限流 / 黑名单 / 非法方法拦截 / `proxy_buffering` + `proxy_next_upstream` 重试 3 次 / 上游 5xx 统一封装成合法 JSON-RPC 错误体 |
| 5 | `deploy/rpc-gateway/nginx/lua/omnx_json_guard.lua` | 【可选】OpenResty 严格 JSON 校验（batch 上限、危险方法拦截） |
| 6 | `deploy/rpc-gateway/dns/*` | Porkbun / Cloudflare 解析记录清单 + 批量导入 JSON |
| 7 | `deploy/rpc-gateway/firewall/base-rpc-whitelist.sh` | AWS 安全组 + ufw/iptables 三层白名单模板 |
| 8 | `deploy/rpc-gateway/scripts/*` | install / deploy-web / render-config / verify / doc-leak |
| 9 | `deploy/rpc-gateway/reports/测试报告-RPC分层架构-20260924.md` | 实测报告 |

### 五、验证

- [x] 节点恢复出块：`height` 连续增长（3 秒/块），`/health` → `{"ok":true,"chain":"omnx-1"}`
- [x] `chain.json` 500 块全部哈希自洽，无 `.tmp` 残留
- [x] `nginx -t` 全部配置语法通过（用真实配置 + 真实节点在容器内实测）
- [x] 网关：`POST /rpc` 200 / `GET /rpc` 405 / 错 Content-Type 400 / 未知路径 404，全部返回**规范 JSON**
- [x] **连续 20 次请求 0 次 JSON 截断**
- [x] 并发 300 请求 → 200×95、429×205（限流按设计生效）
- [x] 停掉节点后网关返回 `503 + 合法 JSON 错误体`，**不再向前端吐半截响应**；节点恢复后自动恢复
- [x] 前端 RPC 解析真值表 7/7 通过；4 个页面 JS 语法全部通过
- [x] `check-doc-leak.sh`：对外内容（docs / 前端）**无底座域名泄漏**

### 六、注意

- 前端 UI 排版、配色、按钮布局**零改动**，只接入统一 RPC 配置与重试逻辑；
- `base-rpc` 绝不进公共 DNS、绝不写进对外文档；
- 主网部署闸门保持关闭。

---

## 2026-09-16 · 区块浏览器多语言（8 语言 · 默认英语 · 纯追加不动 UI）

### 背景
需求：区块浏览器加入 8 种语言——英语、越南、法语、印度（印地语）、韩国、日本、中国台湾地区（繁体）、泰国，
**英语为主语言**。约束：保留现有蓝色主题、布局、按钮位置、表格结构不变。

### 本轮改动（全部为「追加」）
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/explorer.html` | 47975 → 122708 字节：`</body>` 前**追加** i18n 引擎块。原标记、原 `<style>`、原脚本一行未改 |
| 2 | `node/demo.html` | 82438 → 155148 字节：同上 |
| 3 | `OMNX公链浏览器-离线版（双击打开）.html` | 82438 → 155148 字节：同上（与 demo 同源） |
| 4 | `deploy/i18n/cn-keys.json`（新增） | 从 3 个页面抽取的中文词条元数据 |
| 5 | `deploy/i18n/dict.json`（新增） | 主词典：**557 条 × 8 语言**，顺序固定 `[en, zh-TW, vi, fr, hi, ko, ja, th]` |
| 6 | `deploy/i18n/inject.py`（新增） | 幂等注入器：追加引擎 + 词典；可重复运行 |
| 7 | `deploy/i18n/backup/*.orig`（新增） | 3 个页面的改动前原版备份 |
| 8 | `docs/浏览器多语言说明.md`（新增） | 语言清单、匹配优先级、切换方式、词条维护、验收结果 |

### 语言与选择逻辑
- 下拉框顺序：English（默认）、繁體中文、Tiếng Việt、Français、हिन्दी、한국어、日本語、ไทย
- 优先级：`?lang=xx` URL 参数 → `localStorage` 记忆 → 浏览器语言 → 兜底 **en**
- `zh-CN` 等清单外语言一律回落到 **en**（「主语言英语」的既定行为）
- 下拉框插在原顶部栏 `<header>` 末端，沿用 `var(--card)/var(--line)/var(--muted)` 与 20px 圆角，未新增配色

### 关键实现
- **单遍最长匹配**翻译：译文不再被二次替换。早期「全局最长优先替换」会让日文掉字
  （`合成资产交易` → `合成資産引`），已修正；改后除日文外 7 种语言输出逐字节不变
- `WeakMap` 保存节点中文原文，8 语言往返后英文渲染**逐字节一致**（源串不丢）
- `MutationObserver` + `requestAnimationFrame` 增量翻译，覆盖每 3 秒刷新的最新区块表格
- 属性同步翻译：`placeholder` / `title` / `alt` / `value`

### 验证
- [x] 纯追加：3 个页面剥离注入块后与原文**逐字节相同**
- [x] 幂等：连续注入 3 次 md5 不变（`aa96e58c1c71c980b987b52fc1af8098` / `ef566f2e4589ae98cd5d98e4d97344ad`）
- [x] JavaScriptCore 真实引擎解析 + 运行无异常，下拉框 8 项
- [x] 渲染残留汉字：en / vi / fr / hi / ko / th **0 处**；zh-TW / ja 残留为合法繁体与日文汉字
- [x] 动态节点、属性翻译、语言往返（6400+ 字符逐字节一致）全部通过
- [x] 后端回归 **342/342 全绿**（68 / 88 / 36 / 48 / 102）
- [x] AWS 包重打：`deploy/omnx-aws-deploy.tar.gz` 内 `node/explorer.html`、`node/demo.html` 与仓库 md5 一致
- [x] 桌面正式副本 `/Users/mac/Desktop/OMNX公链/02-测试网/` 同步这 3 个页面，原版备份在 `i18n-backup-20260916/`

### 边界说明
- 未改动链上逻辑、系统合约、节点程序、脚本；冻结的主网闸门状态不变（**仍未上主网**）
- 本次只覆盖区块浏览器页面；DEX 页 `node/dex.html` 属独立工程，未纳入
- 词典为机器初稿 + 人工校对，小语种专业术语建议由各地区运营复核后再对外发布

## 2026-09-15 · 主网对接交付物：接入参数 / 公开节点规范 / 治理多签轮换手册（**仍未上主网**）

### 背景
用户下发「主网部署确认指令」，但**未包含放行文字「可以上主网」**，且指令自述「主网闸门保持按既定计划管控」。
按既定规则，**未执行任何主网部署**；本轮只产出对接与治理类前置交付物。

### 本轮改动（全部为新增文档与清单更新，未改动链上逻辑、未改动原有 18 个系统合约）
| # | 文件 | 改动 |
|---|---|---|
| 1 | `docs/主网接入参数.md`（新增） | 交付交易所/DApp 端：网络标识（`omnx-1` / `18888` / `0x49c8`）、端点表、创世链参数、创世分配明细、22 个系统合约地址表、RPC 接口面、EIP-3085 钱包参数、对接注意事项。域名/内网 IP 以 `⟨TBD⟩` 占位 |
| 2 | `docs/公开节点规范.md`（新增） | 第三方全节点接入：节点类型、接入流程、硬件网络要求、端口与启动参数、v1 轻量 P2P 协议（`/net/status|peers|blocks|tx`）、对外接口面、验证者准入、监控升级、合规禁止项；**含开放边界 A/B 方案待拍板** |
| 3 | `docs/治理多签轮换手册.md`（新增） | 8 处高权地址清单、轮换顺序（低风险→高风险）、逐步验证命令、回滚预案、签字栏、禁止事项 |
| 4 | `docs/主网前置准备-检查清单.md` | 新增「八、放行前必须收到的输入」（4 项缺口）与「九、关联文档」 |
| 5 | `docs/更新日志.md` | 本条记录 |
| 6 | `deploy/mainnet/ARTIFACTS.md`（新增） | 产物清单与校验：可复现哈希（送审包、参数快照）、AWS 包结构校验、重建顺序、校验命令 |

### 本轮核验到的关键技术事实
- **链上无法部署 Solidity 多签合约**：`node/rpc.py` 中 `eth_sendRawTransaction` 数量为 **0**；`eth_call` 为标准选择器只读映射；合约部署走原生模块 `exx_deploy`（固定模板）。因此 Gnosis Safe 类多签合约**当前无法上链**。
- 故多签落地给出三方案：①链下多签+单执行地址（立即可用、链上无强制力）②原生多签治理模块（**新增合约，超出已确认 E1–E4 范围，需拍板**）③待闸门 1 完成后用标准 EVM 多签。
- 开放边界存在冲突：2026-09-08 记录「节点程序保持闭源」，本轮指令要求「节点协议对外开放、第三方可运行全节点」。已在 `docs/公开节点规范.md` 第一节列出 A/B 方案待拍板。

### 放行前 4 项缺口
1. 明确文字「可以上主网」；2. 5 个理事会 EOA 地址；3. 多签实现方案选定；4. 节点开放边界选定。

### 验收结论（2026-09-15）
- [x] 三份对接/规范文档就位，参数与 `deploy/genesis.json`、`node/chain.py` 逐一核对一致
- [x] 文档参数自检 21 项全通过（链 ID / 供应 / 出块 / Gas / 手续费 / 池费率 / 锁仓 / 合约地址 / 合约总数 / 分配加总）
- [x] 送审包重建为 14 条目、CRC 通过；AWS 包重建（顶层 `omnx-aws-deploy/`、零 `._*` 残留）
- [x] 原有 18 个系统合约、前端 HTML、桌面副本零改动
- [x] 主网部署闸门维持关闭，**未执行任何主网创世、节点上线或合约上链动作**

---

## 2026-09-12 · 主网前置准备：参数冻结 + 送审包 + 八道闸门（**未执行主网部署**）

### 背景
v4 套件（E1–E4）已在测试网锁版（锁定验收 39/39）。用户确认「测试网版本彻底锁定，可以启动主网前置准备工作」，
同时重申硬闸门：**必须完成真实签名、BFT 共识、第三方审计、≥3/5 多签接管、全参数核对，
并收到明确文字「可以上主网」之后，才允许执行主网部署。**

### 本轮改动（全部为新增文档与打包，**未改动任何链上逻辑、未改动原有 18 个系统合约、未部署主网**）
| # | 文件 | 改动 |
|---|---|---|
| 1 | `scripts/verify-mainnet-params.py`（新增） | 主网全参数冻结核对：把共识关键参数 + 业务参数 + 地址表冻结成可签字清单，并计算 `snapshot_sha256` 用于上线前后零漂移比对；判定分 ✅PASS / ❌FAIL / 📝TODO 三档 |
| 2 | `deploy/mainnet/param-checklist.md` / `.json`（新增） | 冻结清单产物：52/52 项一致、FAIL 0、待办 8；含签字栏（技术负责人／安全审计／治理多签／合规／最终批准） |
| 3 | `deploy/mainnet/audit-package/`（新增） | 送审包暂存目录：`README-AUDIT.md` + 4 份 Solidity + 4 份 ABI + `suite-api.md` + `主网前置准备-检查清单.md` + 3 份报告（共 14 个文件） |
| 4 | `deploy/mainnet/OMNX-Suite-Audit-Package.zip`（新增） | 送审包打包：14 个文件（中文文件名带 UTF-8 标志位）。当日版本哈希 `aff584d8…d4a2`；**最新产物哈希统一见 `deploy/mainnet/ARTIFACTS.md`** |
| 5 | `docs/主网前置准备-检查清单.md`（新增） | 八道闸门（共识签名／多签接管／第三方审计／多验证者／生产加固／全参数核对／合规前置／权限收口复核）+ 5 个签字角色 + 上线顺序 0–8 步 + 回滚预案 + 明确禁止项 |
| 6 | `deploy/mainnet/multisig-governance.template.json`（新增） | 3/5 多签治理配置**占位模板**：8 个高权地址的目标值、切换 6 步、核验命令、回滚方式；**不含任何真实私钥** |
| 7 | `主网部署清单.md` | 纳入 v4 套件（E1–E4）状态、新增第 9/10 项前置要求（权限收口、全参数核对）、指向检查清单 |
| 8 | `deploy/aws/bundle.sh` | 打包时纳入 `mainnet/`（送审包 + 参数清单 + 多签模板）；兼容 `outputs/` 与桌面两套目录布局；`COPYFILE_DISABLE=1` 排除 macOS `._` 资源分叉文件，避免 Linux 解包残留垃圾文件 |

### 参数冻结基线
- 快照哈希：**`382b73e9cd89d62a21362529160a5baf64781c7221c3b4a660de4bf59b368fab`**
- 核对结果：**52/52 项一致 ｜ FAIL 0 ｜ 待办 8**
- 上线前后重跑 `python3 scripts/verify-mainnet-params.py` 比对该哈希，即可证明参数零漂移

### 主网八道闸门（当前状态）
| # | 闸门 | 状态 |
|---|---|---|
| 1 | 共识与签名替换（BFT + secp256k1/ed25519） | ⬜ 待办 |
| 2 | 多签（≥3/5）接管全部高权地址 | ⬜ 待办 |
| 3 | 第三方安全审计（E1–E4 + 原有系统合约 + 跨链路由） | ⬜ 待办（送审包已就绪） |
| 4 | 多验证者网络（≥5 节点） | ⬜ 待办 |
| 5 | 生产加固（内网暴露 + `OMNX_DISABLE_RESET=1` + Nginx/HTTPS + 备份） | ⬜ 待办 |
| 6 | 全参数核对（重跑比对快照哈希并签字） | 🟡 基线已冻结，待上线前复跑签字 |
| 7 | 合规前置（RWA 托管 / KYC-AML / 平台币属性评估） | ⬜ 待办 |
| 8 | 权限收口复核（无 admin 后门、无直转通道） | ✅ 已闭环（`verify-suite-lockin.py` 39/39） |

### 验收结论（2026-09-12）
- [x] `scripts/verify-suite-lockin.py` **39/39 通过**（测试网口径锁定未漂移）
- [x] `scripts/verify-mainnet-params.py` **52/52 一致，FAIL 0，待办 8**
- [x] 送审包 `deploy/mainnet/OMNX-Suite-Audit-Package.zip` 已生成（14 文件，含检查清单）
- [x] `mainnet/` 已纳入 AWS 部署包
- [x] **本轮未执行任何主网部署**；主网部署仍等待明确文字放行

---

## 2026-09-12 · v4 业务合约套件：4 套全新独立合约（地址段 0x…00E1~E4｜只新增｜与旧 D1~D4 并存）

### 地址决议（先看这条）
桌面副本里另一套 RWA-DEX 应用层合约（`node/modules_rwadex.py`）占用 `0x…00D1~D4`，
业务口径与本轮需求不同（FeePool 不分成、RwaProof 会卡 DEX 激活、SeatRegistry 校验质押门槛、参数走 authority）。
**决议：两套并存，互不覆盖、不合并、不删除。** 本套件一律使用新地址段：

| 合约 | 地址 | 说明 |
|---|---|---|
| `OmnxFeeSplitPool` | `0x…00E1` | DEX 手续费池 & 自动分账 |
| `OmnxStakeDiscount` | `0x…00E2` | OMNX 质押-阶梯手续费抵扣 |
| `OmnxRwaHashRegistry` | `0x…00E3` | RWA 资产哈希存证 |
| `OmnxInstitutionSeat` | `0x…00E4` | 机构质押席位登记槽 |

旧 `0x…00D1~D4` 及其所在副本的 EVM 标准层、公网网关、RPC 限流**全部保持原样，未作任何修改**。

### 需求
在 omnx-1 测试网新增 4 套业务合约：① DEX 手续费池 & 自动分账；② OMNX 质押-阶梯手续费抵扣；
③ RWA 资产哈希存证（现有 RWA 模块的平行补充）；④ 机构质押席位链上登记槽。
硬性约束：**只新增**，原有 18 个已部署系统合约（DAO 国库 / RWA 模块等）**不得修改、升级、重部署**；
30% 手续费分成只做**外部转账**到既有 DAO 国库地址；治理**复用现有治理体系**，不另造一套；
白名单 / 限时免手续费 / 资产真实性校验等灵活运营逻辑**一律不上链**。

### 改动内容（全部为新增，未改动既有合约代码/存储/权限）
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/modules_suite.py`（新增） | 4 套合约原生实现 + 治理复用：`FeeSplitPool`（基准 0.05%，70%/30% 自动分账，30% 外部转账至既有国库；生态池支出仅治理可达）、`StakeDiscount`（1 万/5 万/20 万 → 抵 50%/70%/90%，无锁仓、解押即失效、不增发）、`RwaHashRegistry`（只存 64 位十六进制哈希指纹，拒绝原文/重复，无删改接口，按项目 ID 公开可查）、`InstitutionSeat`（授权打标，不收费、不判资质、不动用余额）；`SuiteGovernance` 复用 `PlatformEco._new_proposal` + 既有投票/结算；`SuiteRouter` 直通表**不含** `eco_spend`（防空转、防挪用） |
| 2 | `node/chain.py` | `CONTRACT_ZONE` 新增地址段 `0x…00D1~D4`（与 A/B/C 段完全分开）；`SYSTEM_CONTRACTS` 追加 4 条（原有 18 条一字未改）；新增 `self.suite` 状态 + 幂等 `SuiteRegistry.bootstrap`；`_apply_tx` 增 `module=suite` 路由；`load_genesis_config` 白名单增 `suite`；`snapshot()/_load()` 持久化 suite |
| 3 | `node/modules_platform.py` | `PlatformEco.settle` **仅新增一个 `elif prop["kind"] == "suite"` 分支**（转调 `SuiteGovernance.apply_settled`），既有 listing/subchain 分支行为完全不变 —— 实现「参数变更走现有治理流程」 |
| 4 | `node/rpc.py` | 新增只读 `exx_suiteStatus/Contracts/FeeQuote/FeeDeposits/DaoTransfers/EcoPool/ParamLog/StakeTiers/StakeOf/RwaGet/RwaRecords/Seats/SeatOf/Proposals/Events` 与写入 `exx_suiteFeeDeposit/Stake/Unstake/RwaRegister/SeatGrant/SeatRevoke/Propose`；`exx_status` 增 suite 汇总；`exx_getDeployment` 增 `suite_contracts` |
| 5 | `deploy/genesis.json` | 增 `suite` 段（费率 5 bps、生态分成 7000 bps、国库地址、3 档阶梯、存证/席位授权地址），全部可经治理改参 |
| 6 | `contracts/OmnxFeeSplitPool.sol`、`OmnxStakeDiscount.sol`、`OmnxRwaHashRegistry.sol`、`OmnxInstitutionSeat.sol`（新增） | Solidity 0.8.28 生产迁移参考实现（与测试网原生逻辑口径一致） |
| 7 | `deploy/suite/`（新增） | `suite-deploy.json` 部署清单 + `abis/*.json` 4 份 ABI |
| 8 | `docs/suite-api.md`（新增） | 交付交易所的接口文档：每个合约的地址、方法、入参、返回值、调用示例、治理三步流程、禁止事项 |
| 9 | `tests/test_suite.py`（新增） | 102 项测试（见下）；同步更新 `test_testnet.py` / `test_omnimx_dex.py` 的合约数量断言 18 → 22 |
| 10 | `scripts/deploy-suite.py`（新增） | 部署/校验脚本：地址注册与隔离校验 → 临时目录跑 5 步链上冒烟 → 输出部署日志；不触碰运行中节点的 `node/data` |
| 11 | `deploy/suite/deploy-log.json`（新增） | 部署日志：地址、区块高度、交易哈希、校验结论（本次 14/14 通过） |

### 关键设计取舍
- **30% 转出目标**：原有 18 个系统合约中没有单独命名「DAO 国库」的合约，故固定指向现有生态/基金会金库
  `0x…0006`，并写进 `deploy/genesis.json`；只做外部转账，不读、不改其内部逻辑。
- **不使用任何独立管理员 / 独立治理**：套件内没有 authority / admin 名单，也没有绕过治理的直转通道
  （`SuiteRouter.OPS` 不含 `eco_spend`）；生态池支出与全部参数变更只能走公链**原有完整 DAO 治理提案流程**
  （提案 → 质押 OMNX 权重投票 → 到期结算）。`rwa_registry.submitters` / `institution_seat.registrars`
  只是「交易所后台的授权调用地址」，不构成独立治理，其变更同样走既有治理提案。
- **0.05% 基准费率**：作为新合约内的链上名义基准（治理可调），**不改动既有 DEX 池费率档位**
  （既有 DEX 仍为 0.05%/0.1%/0.3%/1% 多费率池，默认 30 bps 不变），确保「不改原有合约」。
- **全精度计费**：0.05% 基准叠加 90% 抵扣（0.005% = 0.5 bps）时按 `amount × nominal × (10000-折扣) / 1e8`
  计算，避免整数 bps 去整算成 0。

### 验收结论（2026-09-12 锁定）
- [x] `scripts/verify-suite-lockin.py` **39/39 通过**，报告 `deploy/suite/lockin-verification.json`：
      ① 桌面副本零改动、新旧两套完全隔离；② E1~E4 口径行为级复核（分账/抵扣/存证/席位）；
      ③ 无 authority/admin 字段、无直通道、治理 type=`kind="suite"`、投票权重以质押 OMNX 为准；
      ④ 交付物 14 个文件齐备且 ABI 与清单地址一致；⑤ 未执行任何主网部署
- [x] 口径锁定：E1 0.05% 双向 / 70% 生态池 + 30% 外转 `0x…0006` / 生态池支出仅经 DAO 提案 / Gas 独立不抵扣；
      E2 1 万-5 万-20 万抵 50%-70%-90%、无锁仓、解押即失效、不增发；
      E3 仅存 64 位指纹、无删改入口、与原有 RWA 模块源码零引用、不控制 DEX 激活；
      E4 只打状态标记、不校验门槛、不判资质、不收费、合约余额恒为 0
- [x] ⛔ 主网闸门：多签共识 + 源码审计 + 参数核对全部完成前，不得执行主网部署（见 `主网部署清单.md`）

### 验证
- [x] `scripts/deploy-suite.py` 14/14 校验通过（地址注册 / 与 18 原合约及 D1~D4 零重叠 /
      分账 10000→生态 7000+国库 3000 / 质押 5 万抵 70% / 存证可查 / 席位不打款 / 提案进既有治理池）
- [x] `tests/test_suite.py` 102/102 通过（含：原 18 合约地址逐一比对未被改动、70/30 分账与国库到账、
      30% 转账链路可查、生态池支出必须走治理（未通过分文未动）、阶梯边界 9999/10000/49999/50000/199999/200000/自定义、
      解押即失效、存证拒绝原文与重复且无删改接口、席位不收费不动用余额、治理改参（费率/阶梯/国库地址）自动生效、
      持久化与旧数据幂等升级、既有 0.1% 底座费与 DEX 默认费率不受影响、Gas 费独立不抵扣）
- [x] 回归全绿：`test_testnet.py` 68/68、`test_omnimx_dex.py` 88/88、`test_platform.py` 36/36、`test_ws_subscribe.py` 48/48
- [x] RPC 层端到端实测（`rpc_handle` + 出块）：入池 10000 → 生态 7000 / 国库 3000 并可查转账记录；
      质押 5 万 → 抵扣 70% → 100 万手续费报价 150；存证/席位/提案（`kind:"suite"` 进入既有提案池）均正常
- [x] 仅测试网（omnx-1），严禁主网；BNB 测试网不受影响；桌面副本（含 EVM 层/网关/限流/旧 D1~D4）未改动


## 2026-09-08 · OMNX 单币一体化：唯一原生资产 + 0.1% 底座费 + 持仓快照折扣 + 质押权重治理 + 手动回购销毁

### 需求
OMNX 为本链唯一原生资产（Gas / 质押 / 治理抵押 / 子链开通抵押 / 底座费统一以 OMNX 计，不新增第二平台币）；
AMM DEX / 交易所撮合层为链下记账，不重复收取 0.1% 底座费，改为按用户 OMNX 持仓快照档位给予撮合手续费折扣；
上币与子链开通投票权重 = 质押 OMNX 数量；底座收入 / 挂牌费 / 撮合手续费分账归集回购钱包，authority 手动触发回购销毁（无后门，记录上链可查）。
本次仅动主链 + DEX 撮合层；孵化方社区子链 / K 线 / DAO / 邀请码等已交付功能一律不改。

### 改动内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/modules_platform.py`（新增） | PlatformEco + PlatformRouter：创世固化回购钱包 `FEE_WALLET=0x…C1`、黑洞 `BLACKHOLE=0x…dEaD`；0.1% 底座费（base_fee_bps=10，仅原生转账扣，转回购钱包/黑洞不计费）；定时持仓快照；折扣档位读 `deploy/platform_discount.json`（缺文件即启动报错，禁止硬编码）；质押权重投票（上币挂牌费/子链开通抵押均以 OMNX 归集，未通过子链抵押原路退回）；`deposit_fee_share` 归集 + `buyback` 手动销毁（amount<=0 清空钱包，记录 txhash/时间/数量/黑洞，无 restore/unburn） |
| 2 | `node/chain.py` | DEFAULT_CONFIG 增 `platform` 段（默认 enabled=False，旧链行为不变）；构造后无条件 `PlatformEco.bootstrap`（幂等升级旧数据）；`_apply_tx` 原生转账经 `_platform_transfer_net` 扣 0.1% 底座费；`module=platform` 路由；`_finalize_block` 尾调 `_platform_on_block`（快照）；`snapshot()/_load()` 持久化 platform 状态 |
| 3 | `node/modules_dex.py` | `_discounted_fee(fee_bps, discount_bps)`；`get_amount_out/swap_hop/get_amounts_out` 增折扣参数；`DexRouter.swap_exact` 按发送方 OMNX 档位折扣（try/except 容错，未启用平台时等价原费率），RouterSwap 事件带 `discountBps` |
| 4 | `node/rpc.py` | `exx_dexQuote` 支持 `address` 参数返回 discountBps/effectiveFees；新增 `exx_platformStatus/Snapshot/Tier/VotePower/Proposals/Burns/Incomes/ApplyListing/ApplySubchain/Vote/Settle/DepositFeeShare/Buyback`；`exx_status` 增 platform 汇总 |
| 5 | `deploy/genesis.json` | 增 `platform: {enabled:true, base_fee_bps:10, snapshot_interval:120, vote_blocks:300, quorum_bps:500, listing_fee:10000, subchain_bond:50000, authority:[管理员 0x499d…]}` |
| 6 | `deploy/platform_discount.json`（新增） | 4 档：0–999 无折扣 / 1000–9999 9 折 / 10000–49999 8 折 / 50000+ 7 折（参数仅配置文件） |
| 7 | `node/dex.html` | 新增「OMNX 单币平台」页（增量区块，不改既有 UI/配色）：我的 OMNX 持仓与折扣档位、质押与投票权重（只读）、回购销毁公示（每笔 txhash/时间/销毁数量只读展示） |
| 8 | `tests/test_platform.py`（新增） | 36 项平台测试：底座费口径/快照边界/折扣折算/质押权重投票/回购销毁无后门/持久化/缺配置报错 |

### 验证
- [x] `tests/test_platform.py` 36/36 通过（含非 authority 拒回购、销毁守恒 circulating=总量-已销毁）
- [x] 回归：`test_ws_subscribe.py` 48/48、`test_omnimx_dex.py` 88/88、`test_testnet.py` 68/68 通过
- [x] dex.html JS `node --check` 语法通过；只做增量区块，Swap/流动性/代币/时间锁/合约页原样保留
- [x] 三副本 md5 同步（outputs / Desktop `02-测试网` / `work/桌面恢复-冗余备份`）；AWS 包 rebuild
- [x] 仅测试网（omnx-1），严禁主网；BNB 测试网不受影响；子链/K线/DAO/邀请码未改动

# OMNX Chain（omnx-1）· 更新日志

## 2026-09-01 · 高度模型修复：512 以上节点高度不再卡死 + check-node 实时推送校验

### 根因
`node/chain.py` 的 `_push_block` 用 `len(self.blocks)` 作为区块 `index`，且内存裁剪到最近 500 块。
链超过 500 个区块后所有区块 `index` 都变为 500，导致 RPC/WS 高度计算（`len(blocks)-1`）全部卡死：
`exx_status` 高度卡在 499、`eth_blockNumber` 恒为 `0x1f3`、`exx_mine` 返回 `mined:false`、
WS `eth_subscribe("newHeads")` 能返回订阅 id 但从不推送。

### 改动内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/chain.py` | `_push_block` 改按绝对高度 `self.height` 编索引；`_pick_proposer` 用 `self.height`；`apply_received_block` 校验 `idx == self.height`；恢复时 `self.height = 最后区块 index + 1` |
| 2 | `node/network.py` | `handle_status`/`handle_block` 返回绝对高度；`handle_blocks` 按绝对 `index` 过滤；`_sync_once` 同步游标用绝对高度 |
| 3 | `node/rpc.py` | `eth_blockNumber`、`eth_getBlockByNumber`、`exx_status`、`exx_netInfo`、`exx_mine`、`exx_getBlock` 全部改绝对高度语义 |
| 4 | `node/ws_subscribe.py` | 高度回退对齐、`eth_getLogs` latest、WS pump 游标全部改绝对高度 |
| 5 | `scripts/check-ws.py` | 新增 `--live --wait N` 实时推送校验：订阅 `newHeads` 后等待真实出块推送，并比对 `eth_blockNumber` 递增（覆盖「订阅成功但推不了」问题） |
| 6 | `scripts/check-node.sh` | 第 4 步升级为 `check-ws.py --live --wait 8`，退出码 2 单独提示高度模型异常 |
| 7 | `tests/test_ws_subscribe.py` | 新增「高度模型回归」：510 次出块后 index 严格单调、保留 500 块、绝对高度 511、WS pump 推送绝对高度 511 |

### 验证
- [x] `tests/test_ws_subscribe.py` 48/48、`test_omnimx_dex.py` 88/88、`test_testnet.py` 68/68 通过
- [x] check-node.sh 在重启后的节点上：RPC 8545 / WS 8546 / eth_subscribe 实时推送 / chainId=18888 / 仅内网绑定全部通过
- [x] 仅测试网（omnx-1），严禁主网；BNB 测试网不受影响

# OMNX Chain（omnx-1）· 更新日志

## 2026-09-01 · 内网 RPC/WS 完善：eth_getLogs + 多节点 WS 端口 + 健康自检

### 需求
在 8546 WS 订阅基础上继续完善公链侧：适配器 RPC 轮询兜底接口（eth_getLogs）、
多节点脚本 WS 端口冲突修复、WS 运行状态可见性、一键健康自检覆盖 WS。

### 改动内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/ws_subscribe.py` | 新增 `block_transfer_logs()`（按区块范围导出 Transfer 日志）与 `TRANSFER_SIG`；`_block_no` 支持 `latest/pending` 语义 |
| 2 | `node/rpc.py` | 新增 `eth_getLogs`（fromBlock/toBlock/address/topics 过滤；标准 + 适配器双字段）；`HUB` 提为模块级；`exx_status` 返回 `ws_endpoint/ws_clients/ws_subscriptions`；`exx_netInfo` 返回 `ws` 对象 |
| 3 | `deploy/aws/multi-node.sh` | 修复 8546 冲突：节点 HTTP 8545/8546/8547 与 WS 9545/9546/9547 独立端口（`START_WS` 可自定义） |
| 4 | `scripts/check-ws.py`（新增） | 纯标准库 WS 自检：握手 + `eth_subscribe(newHeads)` + 退订，退出码 0/1 |
| 5 | `scripts/check-health.sh` | 新增第 5 项 WS 8546 自检（调用 check-ws.py） |
| 6 | `docs/api.md` / `docs/内网RPC与WS配置.md` / `deploy/aws/README-AWS部署说明.md` | 补充 eth_getLogs、WS 状态字段、多节点 WS 端口、验证清单 |
| 7 | `tests/test_ws_subscribe.py` | 33 → 43 项：eth_getLogs 全量/范围/地址/topics 过滤、check-ws.py 端到端（socketpair 模拟服务端） |

### 事件与日志契约
- `eth_getLogs` 返回：`address`（OMNX→0x…B1 / USDT→0x…B2）、`topics[0]=Transfer(address,address,uint256)`、
  `data`、`blockNumber`、`transactionHash` + `txHash`（适配器用）、`type/symbol/amount/from/to`。
- `exx_status` 增加 `ws_endpoint/ws_clients/ws_subscriptions`；`exx_netInfo` 增加 `ws` 对象。

### 验证
- [x] `tests/test_ws_subscribe.py` 43/43、`test_omnimx_dex.py` 88/88、`test_testnet.py` 68/68 通过
- [x] multi-node.sh / check-health.sh bash 语法通过；三处副本同步 + md5 一致
- [x] 仅测试网（omnx-1），严禁主网；BNB 测试网不受影响


## 2026-09-01 · 内网 RPC/WebSocket 配置：8546 + eth_subscribe 事件订阅

### 需求
公链内网开放 8545（HTTP-RPC）、8546（WebSocket），开启 `eth_subscribe` 事件订阅；
8545/8546 禁止暴露公网；chainId 保持 18888；输出节点启动参数示例、防火墙规则与部署文档。
其他功能保持不变，BNB 测试网不受影响（本窗口只负责 OMNX-Chain 公链节点）。

### 改动内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/ws_subscribe.py`（新增） | 纯 Python 3.9 标准库 WebSocket 服务：RFC6455 握手 + 文本帧收发 + ping/pong；`eth_subscribe`（newHeads / logs / transfer）与 `eth_unsubscribe`；`subscribe` / `unsubscribe`（OMNX-RPC-Adapter §3.2 自定义契约）；事件泵约 1s 增量扫描；可注入 `rpc_fn` 回退普通 JSON-RPC 方法 |
| 2 | `node/rpc.py` | 8546 WS 服务接入：`OMNX_WS_PORT`/`OMNX_WS_HOST` 环境变量（默认 127.0.0.1，禁止 0.0.0.0）；启动时打印 WS 端点；Ctrl+C 一并停止；8545 HTTP 对订阅方法返回明确提示（仅 WS 支持） |
| 3 | `docs/api.md` | 新增「十二、WebSocket 事件订阅」：端点、eth_subscribe/subscribe 协议、Transfer 消息契约、客户端示例 |
| 4 | `docs/内网RPC与WS配置.md`（新增） | 端口总览、启动参数示例（本地/单节点/云上多节点）、AWS 安全组 / ufw / iptables 防火墙规则、nginx 网关约束、环境变量速查、验证清单 |
| 5 | `tests/test_ws_subscribe.py`（新增） | 33/33 项离线测试：握手密钥、帧编解码、订阅/退订、事件转换（txHash 命中区块交易）、pump_once 增量推送、高度回退对齐、rpc_fn 回退 |

### 事件契约（与 OMNX-RPC-Adapter §3.2 一致）
- 订阅：`{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}` → `"0x1"`；
  自定义：`{"method":"subscribe","params":{"event":"Transfer","chainId":18888}}` → `{"subscription":"0x1"}`。
- 推送（logs/transfer）：`{"type":"Transfer","txHash":"0x…","blockNumber":N,"from":…,"to":…,"symbol":"OMNX","amount":"…","logIndex":i}`；
  newHeads：`{"type":"newHeads","number":"0x…","hash":…,"parentHash":…,"timestamp":…,"proposer":…,"txCount":n}`。
- `txHash`/`blockNumber` 由 WS 端按最近区块交易尽力匹配补全；断线重连从新水位恢复，不重放历史事件。

### 安全
- 8545/8546 默认仅绑 `127.0.0.1`；云上设内网 IP + 安全组限定 VPC 来源，**严禁 0.0.0.0/0 放行**。
- nginx 只反代 127.0.0.1:8545 的 `/rpc`、`/health`、`/net/*` 与浏览器页面，不反代 8545/8546 公网直连。

### 验证
- [x] `tests/test_ws_subscribe.py` 33/33 通过（含 socketpair 真实套接字冒烟：握手/订阅/推送/退订）
- [x] `tests/test_omnimx_dex.py` 88/88、`tests/test_testnet.py` 68/68 回归通过
- [x] chainId=18888（omnx-1）、8545 逻辑不变；仅测试网，严禁主网


## 2026-09-01 · 工程分工边界：适配器独立窗口开发，本窗口仅负责 OMNX-Chain

### 约定
- `OMNX-RPC-Adapter`（公链⇄交易所适配层）已启用**独立窗口**开发，后续适配器开发全部在新窗口执行。
- 本窗口 / 本仓库（OMNX 公链测试网）**只负责 OMNX-Chain 公链节点配置**，不再处理适配器相关任务。
- 公链侧配合项（如 8546 WS 事件端点、链上 `Transfer` 事件契约）如有需要，由适配器工程提出需求后在本窗口按迭代实现。

### 改动
| # | 文件 | 改动 |
|---|---|---|
| 1 | `架构说明.md` | 〇章新增工程分工边界：适配器独立工程/独立窗口，本仓库只负责公链节点配置 |

### 验证
- [x] 三处副本（桌面 / outputs / work 备份）同步一致


## 2026-09-01 · 代币统一：OMNX 为唯一原生代币，移除 OMNIMX 平台币

### 约定
- **OMNX 为公链唯一原生代币**，承担链上 Gas 支付、节点质押、生态治理。
- **不再额外单独发行交易所平台币**；交易所、AMM、RWA 各业务模块统一复用 OMNX 作为生态通证。
- **系统合约 OmnimxToken 即 OMNX 原生币**，创世固化地址 `0x…00B1` 保持不变。

### 改动内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/modules_dex.py` | 移除 OMNIMX 代币语义：分红持仓币改为 OMNX（原生账本 `chain.balances` 快照分配）；原生 OMNX 禁铸币/禁销毁（唯一原生代币）；USDT/USDC 为测试网模拟稳定币 |
| 2 | `node/chain.py` | DEFAULT_CONFIG `dex.tokens`：OMNIMX → USDC；OmnimxToken/Pool/Router/Dividend 系统合约 role 说明更新 |
| 3 | `deploy/genesis.json` | `dex.tokens`：移除 OMNIMX（1e24 预铸），新增 USDC 模拟稳定币 |
| 4 | `node/rpc.py` | `eth_call` shim：OmnimxToken 地址（0x…B1）映射为 OMNX；`exx_dexInfo` 代币列表 OMNX/USDT/USDC |
| 5 | `node/dex.html` | 全部下拉/余额/授权/铸币从 OMNIMX 改为 USDC；分红文案改按 OMNX 持仓；产品名「OMNIMX DEX」保留 |
| 6 | `scripts/deploy-omnimx.py` | 闭环流程改用 OMNX/USDT/USDC 交易对与 OMNX 持仓分红 |
| 7 | `deploy/omnimx/omnimx-deploy.json` | OmnimxToken role 改「OMNX 原生币」；新增 `tokenomics_note` |
| 8 | `tests/test_omnimx_dex.py` | OMNIMX 全部改写为 OMNX/USDC/DAI；新增「OMNIMX 平台币已移除」「原生 OMNX 不可销毁」断言；分红按 OMNX 持仓 |

### 验证
- [x] `tests/test_omnimx_dex.py` 88/88、`tests/test_testnet.py` 68/68 全部通过
- [x] OmnimxToken 创世地址 0x…B1 保持不变；18 个系统合约清单不变
- [x] 分红按 OMNX 持仓比例正确派发（原生账本快照）；OMNX 禁增发/禁销毁
- [x] 全部仅测试网，严禁主网


## 2026-09-01 · 架构边界约定：公链为后台节点服务，上层应用独立开发

### 约定
- OMNX 公链为**后台节点服务**，运行于云服务器；**不开发移动端客户端**。
- 钱包、交易所、各类 RWA 平台、AMM 前端均属于**上层应用**，通过标准 RPC（`POST /rpc`）调用公链，独立开发。
- 公链只负责账本、合约执行、出块、P2P 节点同步。

### 改动
| # | 文件 | 改动 |
|---|---|---|
| 1 | `架构说明.md` | 新增「〇、系统定位与架构边界」章节（职责边界 + 调用关系图 + 接口契约 + 前端定位） |
| 2 | `README.md` | 增加系统定位说明（后台节点服务、上层应用经标准 RPC 独立接入） |

### 验证
- [x] 三处副本（桌面 / outputs / work 备份）同步一致


## 2026-09-01 · OMNIMX DEX 多费率池改造（0.05% / 0.1% / 0.3% / 1%）

### 需求
AMM 池取消固定 0.3% 手续费，改为多费率池：预设档位 0.05% / 0.1% / 0.3% / 1%；
创建交易对时选定档位，池子一经创建费率不可直接修改；所有费率调整、新增档位统一由
OmnimxTimelock 时间锁管控，禁止管理员随意改参数；路由自动识别不同池子费率；
前端每个交易对明确展示当前池子手续费率。底层链参数不变（chainId=18888、gas_price=1、3s 出块）。

### 改动内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/modules_dex.py` | 新增 `FEE_TIERS=(5,10,30,100)`；`DexFactory.create_pair` 支持按档位建池并校验（非法档位拦截）；`fee_tiers` 创世写死、随 chain.json 持久化；时间锁 `set_pair_fee`（直接改已有池子费率）移除，改为 `add_fee_tier`（仅可新增档位，重复档位拦截） |
| 2 | `node/rpc.py` | `exx_dexCreatePair` 透传 `fee_bps`；`exx_dexQuote` 返回每跳 `fees`；`exx_dexInfo` 返回 `fee_tiers`；新增 `exx_dexFeeTiers` |
| 3 | `node/dex.html` | 创建交易对增加费率下拉（0.05%/0.1%/0.3%/1%）；报价展示实际池子手续费；时间锁 UI 改为 `add_fee_tier`（新增档位）；交易对列表费率展示保留（仅文字/逻辑，UI 主题布局不动） |
| 4 | `node/chain.py` | `DexToken.setup` 初始化 `fee_tiers`（支持 genesis 配置）；`_init` dex 状态默认 `[5,10,30,100]` |
| 5 | `deploy/genesis.json` | dex 段新增 `"fee_tiers": [5, 10, 30, 100]` |
| 6 | `scripts/deploy-omnimx.py` | 演示建池改为多费率（OMNX/USDT=0.3%、OMNIMX/USDT=0.05%、OMNIMX/OMNX=1%）；时间锁演示改为 `add_fee_tier`；部署清单输出 `fee_tiers` |
| 7 | `deploy/omnimx/omnimx-deploy.json` | 新增 `fee_tiers` 与说明；Pool/Router/Timelock 角色说明更新为多费率语义 |
| 8 | `tests/test_omnimx_dex.py` | 新增多费率用例（四档建池、非法档位拦截、逐档 Swap 换算、路由自动识别费率、滑点、分红、时间锁新增/重复档位、禁止改已有池子），时间锁测试从 `set_pair_fee` 迁移到 `add_fee_tier` |

### 验证
- [x] `tests/test_omnimx_dex.py` 85/85、`tests/test_testnet.py` 68/68 全部通过
- [x] 老池兼容：已有池默认 30 bps（0.3%）不受影响；`fee_tiers` 缺省时自动回填默认档位
- [x] 时间锁：`set_pair_fee` 已从 `SUPPORTED_TIMELOCK_OPS` 移除，队列该操作直接报错；`add_fee_tier` 只能新增档位
- [x] MockUSDT、OmnimxToken 等系统合约保持不变；底层链参数（chainId 18888 / gas_price=1 / 3s 出块）不动
- [x] 全部仅测试网，严禁主网


## 2026-09-01 · OMNIMX DEX 正式合约（审计包）本地部署落地

### 背景
用户上传 `OMNIMX-Contracts-Audit-Package.zip`（OmnimxToken / OmnimxPool / OmnimxRouter / OmnimxTimelock / OmnimxInterfaces，
Solidity ^0.8.24，OZ ^5.6.1，hardhat evmVersion=cancun + viaIR）。要求用该包合约在本地 OMNX 测试网完成部署、ABI、
前端对接与全链路闭环，不做任何代码改动，仅测试网，严禁主网。

### 交付内容
| # | 文件 | 说明 |
|---|---|---|
| 1 | `contracts/omnimx-audit/` | 上传包 5 份 .sol + 审计 README 原样归档（未改动） |
| 2 | `deploy/omnimx/omnimx-deploy.json` | 9 个 DEX 合约地址（B1~B9）+ 5 份 ABI + 链参数 + 钱包配置 |
| 3 | `deploy/omnimx/abis/OmnimxInterfaces.json` | 新增 Interfaces ABI（IOmnimxPool + IOmnimxRouter，与源码一致） |
| 4 | `docs/OMNIMX-EVM兼容性评估.md` | Solidity 0.8.28 特性逐合约对照、gas/区块参数、字节码边界与生产路径 |
| 5 | `node/dex.html` | ABI 说明文字更新（仅文字，不动 UI/逻辑） |

### EVM 兼容性结论
- 链为轻量 EVM 兼容应用层：原生模块（`modules_dex.py`）按同一 ABI 执行合约逻辑，`eth_call` 标准选择器只读映射。
- ABI 层与 Solidity 0.8.28 编译产物 100% 一致；字节码层需完整 EVM 解释器（生产二选一：部署到完整 EVM 链，或升级链 VM）。
- gas：`gas_price=1`；区块：`3.0s` 出块；手续费 0.3%（30 bps）；滑点/K 恒积校验与 Solidity 语义一致。

### 验证
- [x] `tests/test_omnimx_dex.py` 58/58、`tests/test_testnet.py` 68/68 全部通过
- [x] ABI 与上传源码逐一比对一致；9 合约创世部署地址唯一
- [x] 钱包参数：ChainID 18888、RPC `http://127.0.0.1:8545/rpc`、符号 OMNX(18)、浏览器 `http://127.0.0.1:8090/index.html`
- [x] 三处副本（桌面 / outputs / work 备份）同步一致

## 2026-09-01 · 修复启动器：健康检查 grep 匹配 + set -u 崩溃

### 问题现象
双击「OMNX公链-在线版启动.command」：
1) 报 `PORT?: unbound variable` 后退出（19:22 会话）；
2) 修复后报「节点未就绪」，但节点其实已启动（19:30 会话，PID 13980 正常监听 8545 并持续出块）。

### 根因
| # | 根因 | 说明 |
|---|---|---|
| 1 | `echo()` 函数覆盖 + `set -u` | 日志打印用 `echo(){ builtin echo "$@" \| tee ...; }` 覆盖内建 echo，与 `set -u`（nounset）叠加后，引用环境变量（PORT）时触发 `unbound variable` 崩溃 |
| 2 | 健康检查 grep 匹配不上 | `/health` 实际返回 `{"ok": true, "chain": "omnx-1"}`（冒号后有空格），脚本 `grep '"chain":"omnx-1"'`（无空格）永远匹配失败 → 节点正常也报「节点未就绪」（原版遗留 bug） |

### 修复内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `scripts/fix-rpc.sh` | 移除 `echo()` 覆盖；`set -uo pipefail` → `set -o pipefail`；端口改独立变量 `OMNX_PORT`（默认 8545），不再读取用户 shell 的 `PORT`；grep 改 `"chain": *"omnx-1"` 容忍空格 |
| 2 | `scripts/start-local.sh` | 同上（grep + set -u + OMNX_PORT）；节点启动用 `env -u PORT PORT=$RPC_PORT` 强制端口 |
| 3 | 桌面「OMNX公链-在线版启动.command」/「启动OMNX测试网.command」 | 日志统一写入 `node/data/launcher.log`；失败保持窗口并显示日志路径与手动兜底命令 |

### 验证
- [x] `PORT` 为空/只读/`-u` 模式下均不崩溃（三种环境模拟通过）
- [x] 新 grep 能匹配实际健康响应 `{"ok": true, "chain": "omnx-1"}`
- [x] 修复期间节点持续出块（chain.json 正常增长）
- [x] 三处副本（桌面 / outputs / work 备份）md5 一致

## 2026-08-26 · 修复 RPC 连接失败（Failed to fetch）· 启动流程重做

### 问题现象
浏览器提示「无法连接链节点 http://127.0.0.1:8545/rpc 错误：Failed to fetch」。

### 根因
8545 端口被**孤儿旧节点进程**（PID 16818）占用：该进程从已清空的旧目录
`/Users/mac/Desktop/OMNX公链-测试网/node` 启动，页面文件（demo.html/explorer.html）
缺失导致 HTTP 无响应，且旧进程仍在持续写残留 `node/data/chain.json`。

### 修复内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `scripts/start-local.sh` | 8545 被旧 `rpc.py` 占用时自动清理重启；被其他程序占用时明确报错；**禁止跳转到 8546** |
| 2 | `scripts/fix-rpc.sh`（新增） | 一键修复：停旧进程 → 从正确目录启动新节点（固定 8545）→ 等待 `/health` 返回 `omnx-1` → 打开浏览器 |
| 3 | `scripts/cleanup-legacy.sh`（新增） | 停止旧路径进程后，把旧残留目录移入 macOS 回收站（可恢复，不误删） |
| 4 | 桌面「OMNX公链-在线版启动.command」 | 指向 `fix-rpc.sh` |
| 5 | 「启动测试网节点.command」「双击打开浏览器.command」 | 路径同步到 `02-测试网/scripts/` |
| 6 | `部署说明.md` | 新增「两步启动指引」+「Failed to fetch 故障排查」小节 |
| 7 | `node/chain.py` / `rpc.py` / `network.py` | 前置已完成：P2P 广播、区块同步、创世加载、交易/账户查询 RPC（chain-id=`omnx-1`） |

### 兼容性验证（Python 3.9.6，macOS 系统自带）
- [x] `import chain / network / rpc` 全部成功
- [x] `chain_id = omnx-1`；旧 `chain.json` 加载正常；`rpc.py` 模块级初始化成功
- [x] 既有单元测试 68/68 通过
- [x] 全部 shell 脚本 `bash -n` 语法通过

### 启动步骤（两步走）
1. 双击桌面「OMNX公链-在线版启动.command」启动链节点（自动修复 8545 冲突）
2. 浏览器自动打开 `http://127.0.0.1:8545/`，或双击「OMNX公链-离线版启动.command」打开页面

### 验证命令
```bash
curl http://127.0.0.1:8545/health        # {"ok":true,"chain":"omnx-1"}
curl http://127.0.0.1:8545/net/status    # height/validators/mempool 正常
curl http://127.0.0.1:8545/rpc -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"exx_status","params":{}}'
```

## 2026-08-26 · 本地闭环确认 + 完整 AWS 部署包输出

### 本地闭环（双击「OMNX公链-在线版启动.command」）
1. 自动停止孤儿旧节点进程（仅限 rpc.py，端口占用自动清理，禁止跳 8546）
2. 启动新版链节点 + RPC，监听 `127.0.0.1:8545`
3. 等待 `/health` 返回 `{"ok":true,"chain":"omnx-1"}` 后自动打开区块浏览器

### 可选清理
`scripts/cleanup-legacy.sh`：停止旧路径进程后，把 `/Users/mac/Desktop/OMNX公链-测试网`
残留目录移入 macOS 回收站（可恢复），不直接删除。

### 接口健康校验（实测标准）
- `curl http://127.0.0.1:8545/health` → `{"ok":true,"chain":"omnx-1"}`
- `curl http://127.0.0.1:8545/net/status` → `chain_id=omnx-1`、height、validators、mempool
- `exx_status` → `total_supply=1000000000`、`deployments=9`

### 完整 AWS 部署包
`deploy/omnx-aws-deploy.tar.gz`（334 KB / 34 文件）：
- `node/` 节点源码（chain/rpc/network/modules/modules_rwa/vm）
- `scripts/` 启动/修复/清理/合约校验脚本
- `aws/` 云端部署脚本（install/systemd/nginx/cloudformation/multi-node/bundle/healthcheck）
- `genesis.json` 创世配置（chain-id=`omnx-1`，总量 10 亿）
- `explorer.html` 区块浏览器前端页面（UI 未改动）
- `docs/` API 文档、目录结构、更新日志、部署说明
- `tests/` 单元测试（68/68 通过）

### 运行环境
macOS 系统自带 Python 3.9.6 已验证：旧 chain.json 加载正常、rpc.py 初始化成功、
全部 RPC 方法可用。

## 2026-08-26 · 修复 file:// 跨域：本地 Web 服务 + 同源 RPC 代理

### 问题现象
直接双击 HTML（file:// 协议）时，即使节点 RPC 已正常运行在 127.0.0.1:8545，
浏览器跨域安全策略仍拦截请求，前端报 `Failed to fetch`。

### 修复内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `scripts/serve-web.py`（新增） | 本地 Web 服务：`http://127.0.0.1:8090/index.html` 托管浏览器页面，同源代理 `/rpc`、`/health`、`/net/*` 到链节点 8545 |
| 2 | `scripts/fix-rpc.sh` | 停旧进程 → 起节点（8545）→ 起 Web 服务（8090，占用自动递增）→ 等待 `/health` → 打开 `http://127.0.0.1:8090/index.html` |
| 3 | `scripts/start-local.sh` | 后台/前台模式均集成 Web 服务；已运行分支同样打开 8090 页面 |
| 4 | `scripts/stop-local.sh` | 停止 Web 服务 + 链节点 |
| 5 | `双击打开公链浏览器.command` | 优先探测并打开 8090 Web 服务页面；未启动时提示先运行在线版启动 |
| 6 | `部署说明.md` | 标注「不要直接双击 HTML（file:// 会被跨域拦截），必须由启动脚本唤起本地 Web 服务」 |

### 访问方式（推荐）
```
http://127.0.0.1:8090/index.html   ← 本地 Web 服务（同源 /rpc，无跨域）
http://127.0.0.1:8545/             ← 节点自带页面（同样 http://，无跨域）
```
`OMNX公链-离线版启动.command` 保留 file:// 打开，仅作静态演示。

### 校验
- `curl http://127.0.0.1:8545/health` → `{"ok":true,"chain":"omnx-1"}`
- `curl http://127.0.0.1:8090/health` → 代理正常（200）
- 浏览器页面连接警告消失，正常读取链上区块数据

## 2026-08-29 · 运行状态确认与文档收尾
- 旧孤儿进程 PID 16818 已确认停止（8545 冲突源已消除）
- 节点 8-28 实际运行并持续出块（`node/data/chain.json` 正常更新）；本地 Web 服务
  `web.pid` 曾运行，当前本地服务为停止状态（需双击「OMNX公链-在线版启动.command」启动）
- 旧残留目录 `/Users/mac/Desktop/OMNX公链-测试网/` 仍在，可运行
  `scripts/cleanup-legacy.sh` 移入回收站
- `README.md` 已更新访问方式：推荐 `http://127.0.0.1:8090/index.html`，
  标注勿用 file:// 直接打开 HTML

## 2026-09-01 · 补充健康自检与文档完善
- 新增 `scripts/check-health.sh`：一键自检链节点 `/health`、`/net/status`、
  本地 Web 服务页面、RPC `exx_status`（chain_id=omnx-1、系统合约 9 个）
- `docs/directory.md` 补录 serve-web.py / fix-rpc.sh / cleanup-legacy.sh /
  check-health.sh 与访问方式说明

## 2026-09-01 · OMNIMX DEX 原生模块接入测试网（v3）

### 交付内容
| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/modules_dex.py`（新增，635 行） | OMNIMX DEX 原生实现：DexToken（ERC20，OMNX 原生账本 + USDT/OMNIMX 模块账本）、DexFactory（交易对工厂）、DexPair（恒定乘积 AMM，0.3% 手续费，最小流动性锁仓）、DexRouter（流动性/Swap/滑点保护）、DexTimelock（排队/执行/取消）、DexDividend（按 OMNIMX 持仓分红）、DexPartner（合伙人返佣） |
| 2 | `node/chain.py` | 系统合约清单 9 → 18（新增 9 个 DEX 合约 B1~B9）；`dex` 状态 + 持久化；`mod==dex` 分发 |
| 3 | `node/rpc.py` | 新增 `exx_dex*` / `exx_erc20*` 全套 DEX RPC；`eth_chainId` 改为数字链 ID 18888；新增 `eth_call` 标准选择器映射、`eth_getCode/getBlockByHash/getTransactionByHash/estimateGas`；`/dex` 页面路由 |
| 4 | `deploy/genesis.json` | `chain_id_num=18888`；`dex.tokens`（USDT/OMNIMX 创世预铸）+ `dex.timelock`；system_contracts 18 个 |
| 5 | `scripts/deploy-omnimx.py`（新增） | 线上部署 + 内部闭环引导（建池/流动性/兑换/时间锁/分红/合伙人），输出 `deploy/omnimx/omnimx-deploy.json` |
| 6 | `node/dex.html`（新增） | DEX 前端页（蓝色主题）：内置钱包连接、Swap 兑换、添加/移除流动性、ERC20 转账/授权、时间锁/分红/合伙人、合约地址与 ABI |
| 7 | `scripts/serve-web.py` | `/dex` 路由（同源代理 RPC） |
| 8 | `tests/test_omnimx_dex.py`（新增） | 58/58 项离线闭环测试 |
| 9 | `docs/OMNIMX-DEX测试网部署说明.md` / `docs/钱包网络配置-omnx-testnet.json` | 部署说明 + MetaMask/TP 钱包网络配置（ChainID 18888） |
| 10 | `deploy/omnimx/abis/` | OmnimxPool/Router/Timelock/Token ABI（与 Solidity 编译产物一致） |

### 验证
- [x] 原测试网回归 68/68 通过（系统合约断言 9 → 18 同步更新）
- [x] OMNIMX DEX 闭环 58/58 通过：转账/授权/销毁/暂停/建池/流动性/Swap/滑点/时间锁/分红/返佣/权限/持久化
- [x] `chain.py` / `rpc.py` / `modules_dex.py` 语法与导入通过（Python 3.9.6）

### 钱包网络参数（MetaMask/TP 手动添加）
- RPC：`http://127.0.0.1:8545/rpc`　ChainID：`18888`　符号：`OMNX`(18)　浏览器：`http://127.0.0.1:8090/index.html`

### 边界说明
- DEX 合约在创世固化（B1~B9），与既有系统合约同一模式；用户后续提供的正式 Solidity
  合约到位后，以 `deploy/omnimx/abis/` + 地址表校准即可，接口保持不变。
- 测试网为简化 HMAC 签名模型；MetaMask 可添加网络查余额，发送交易用 DEX 页内置钱包。
- 现阶段仅本地测试网；AWS/BSC 待内部验证通过后再部署，严禁主网。
