# OMNX Chain · 蓝绿部署方案（平滑升级 · 第 5 项）

> 建立于 2026-09-24。**本次只交付方案与脚本，不执行任何云端操作** ——
> AWS 环境仍处于搁置状态，主网闸门保持关闭。
>
> 目标：节点升级时**服务不中断**。先在备用实例上把新版本验完，再切流量；出问题秒切回。

---

## 一、为什么不直接原地升级

原地升级有三个躲不开的坑：

1. **升级窗口内 RPC 不可用** —— 交易所适配器、区块浏览器、DApp 全部报错；
2. **新版本起不来就没有退路** —— 代码已经覆盖，想回退只能再拷一次，窗口被拉长；
3. **状态文件被新程序写过之后，旧程序可能读不了**（`min_reader_v` 抬高就是这种情况）。

蓝绿部署把「验证」和「切换」拆成两步：验证在流量之外做完，切换只花几秒。

---

## 二、拓扑

```
                    ┌──────────────────────────────┐
   rpc.omnxchain.com│  网关实例（常驻，永远在线）    │
   explorer...      │  nginx + 限流 + JSON 截断重试  │
        ──────────► │  upstream 指向 blue 或 green   │
                    └───────────┬──────────────────┘
                                │ 内网
                 ┌──────────────┴──────────────┐
                 │                             │
        ┌────────▼────────┐          ┌─────────▼────────┐
        │ blue  （现役）   │          │ green （待切换）  │
        │ 当前版本节点      │          │ 新版本节点        │
        │ 8545/8546（内网） │          │ 8545/8546（内网） │
        └─────────────────┘          └──────────────────┘
```

**关键点**：`8545` / `8546` **只开内网**，永远不直接对公网暴露。
外部只看到网关的 443。切流量 = 改 nginx upstream，或者把 EIP 换绑实例。

---

## 三、两种切换方式

| 方式 | 怎么做 | 优点 | 缺点 | 建议 |
|---|---|---|---|---|
| **A. nginx upstream 切换** | 网关常驻，`upstream` 从 blue 的内网 IP 改到 green | 不动公网 IP、不动 DNS、秒级；网关本身不重启 | 网关是单点（需另做冗余） | ✅ **默认用这个** |
| **B. EIP 换绑** | 把同一个弹性 IP 从 blue 摘下来绑到 green | 连网关都不用改 | 换绑瞬间连接会断（TCP 重建），且 EIP 是公网资源，操作有配额与风险 | 备用 |

两种方式都**不需要改 DNS**（`rpc.omnxchain.com` 解析到 EIP/网关，不动）。

---

## 四、完整流程

### 0. 前置

- [ ] 变更分类已判定（见 `docs/链升级兼容性规范.md` 第五节）。**C 类改动必须先出分叉预案**。
- [ ] 本地全量回归已全绿：`bash deploy/aws/run-local-gate.sh`
- [ ] 打包完成：`bash deploy/aws/bundle.sh`，记下 sha256
- [ ] 现役（blue）状态已快照备份：`python3 scripts/omnx-snapshot.py export`
- [ ] 公告窗口已定（对外 ≥ 24h）

### 1. 起 green

```bash
bash deploy/aws/bluegreen-switch.sh status
bash deploy/aws/bluegreen-switch.sh deploy-green --pkg deploy/omnx-aws-deploy-new.tar.gz --yes
```

green 用**独立数据目录**起链。两种起步方式：

- **从创世起**（改创世参数时）：green 自己跑 `install.sh` 建链；
- **从 blue 的快照起**（绝大多数情况）：把 blue 的快照 `import` 进 green 的数据目录，
  这样链高连续、账本一致。

```bash
python3 scripts/omnx-snapshot.py import <blue快照.tar.gz> --data /opt/omnx/node/data
```

### 2. 验 green（**流量还没切过来，出问题零影响**）

```bash
bash deploy/aws/bluegreen-switch.sh verify-green --yes
```

验收清单：

- [ ] `eth_chainId == 0x49D8`（18888）
- [ ] `eth_blockNumber` 在涨（≥ 3 个新区块）
- [ ] `chain.json` 的 `state_v` / `min_reader_v` 与预期一致
- [ ] `python3 scripts/omnx-snapshot.py info` 能读出高度与链头
- [ ] `run-local-gate.sh` 全绿（在 green 上跑一遍）
- [ ] **与 blue 对账**：同一高度下链头哈希一致（不一致说明状态分叉，立即停止）
- [ ] 8545/8546 未对公网开放（安全组 + `lsof` 双侧确认）

### 3. 切流量

```bash
bash deploy/aws/bluegreen-switch.sh cutover --yes
```

切完立刻验：

```bash
curl -s https://rpc.omnxchain.com -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
```

### 4. 观察期（≥ 30 分钟）

- 区块高度持续增长
- 网关 5xx 为 0
- 交易所适配器充值/提币任务无异常
- 保留 blue 不销毁，随时可切回

### 5. 收尾

观察期通过后：

```bash
bash deploy/aws/bluegreen-switch.sh destroy-green --yes   # 实际是销毁 blue（旧的）
```

⚠️ **反向操作**：下次升级时角色互换（green 变 blue）。脚本用 `ACTIVE` 记录当前生效的一方。

---

## 五、回滚

任何一步出问题都走这条：

```bash
bash deploy/aws/bluegreen-switch.sh rollback --yes
```

一秒钟把 upstream 指回去，**不需要重启任何节点、不需要改 DNS、不需要动数据**。

如果新版本已经把状态文件写成旧程序读不了的样子（`min_reader_v` 被抬高），
那么回滚必须连带恢复状态：

```bash
python3 scripts/omnx-snapshot.py import <升级前快照.tar.gz> --data /opt/omnx/node/data --force
```

**这正是「先备份再升级」不能省的原因。**

---

## 六、脚本

`deploy/aws/bluegreen-switch.sh`

| 子命令 | 作用 |
|---|---|
| `status` | 打印当前蓝绿角色、上游指向、两边高度与链头 |
| `deploy-green` | 在待命实例部署新版本包 |
| `verify-green` | 对待命实例跑验收清单（含与现役对账） |
| `cutover` | 切换 upstream，把流量导向待命实例 |
| `rollback` | 切回上一方 |
| `destroy-green` | 观察期通过后销毁旧的一侧 |

**安全约定**：

- 一切会改状态的子命令**默认不执行**，只打印将要做什么；必须显式 `--yes` 才动手；
- 所有子命令都要读 `aws-deploy.env`（含凭证），脚本本身**不含任何密钥**；
- `cutover` 前会**强制**检查「与现役高度/链头对账」，对不上就中止。

---

## 七、硬约束（别越线）

1. 8545 / 8546 / P2P 端口**只开内网**，公网只开 80/443；
2. 本次仍为**测试网**，不得当作主网对外；
3. 对外口径：HTTP Gossip + 单验证者确定性轮转，**不具备拜占庭容错**，严禁称 BFT；
4. 未收到明确文字「可以上主网」之前，不得执行任何主网部署。
