﻿# 面向矿工和矿池的 MoE 硬分叉升级指南

Pearl 即将进行一次硬分叉。在一个固定的区块高度，也就是**分叉高度**，区块会从 V1（dense）ZK 证书切换到新的 V2（MoE）ZK 证书。

| 网络 | 分叉高度（`MoEForkHeight`） |
| ---- | --------------------------- |
| 测试网 | TBD |
| 主网 | TBD |

**简短版：**

- 这次分叉会增加对**挖掘 MoE 模型**的支持。分叉后，你可以升级矿机来挖 MoE。这是可选项，见步骤 3。
- 你的**哈希矿机不需要改动**。旧的 dense proof 在分叉前后都能工作。
- 你的**节点**和你的 **ZK 证明代码**，也就是把普通 proof share 转换成 ZK proof 并提交区块的代码，**必须在分叉高度前升级**。

---

## 步骤 1：将节点升级到 v1.1.0

先做这一步。在分叉前的任何时间升级都是安全的。

- 在分叉高度之前，节点完全兼容旧矿机和旧的 V1 ZK proof。
- 唯一的 API 变化：`getblocktemplate` 现在会返回一个新字段 `requiredcertversion`。

```json
{
  "height": 12345,
  "requiredcertversion": 1,
  "...": "..."
}
```

- `1` = 分叉前，区块必须携带 V1（dense）证书。
- `2` = 分叉时及分叉后，区块必须携带 V2（MoE）证书。

会忽略未知 JSON 字段的旧矿机软件可以继续不做修改地运行。如果你的 `getblocktemplate` 解析器很严格，也就是会拒绝未知字段，请先修复这一点。

请使用这个字段来选择证书版本。不要在矿池中硬编码分叉高度。应从模板中读取版本。

参考：`node/btcjson/chainsvrresults.go`（`RequiredCertVersion`），`node/chaincfg/params.go`（`MoEForkHeight`，`RequiredCertVersion`）。

## 步骤 2：升级你的 ZK 证明代码（矿池）

如果你的矿工提交的是**普通 proof share**，然后由矿池在提交区块前完成 ZK 证明，那么这一步适用于你。你必须在**分叉高度前**部署这项升级，否则你在分叉后构建的每个区块都会被拒绝。

### 2.1：函数名发生了变化

在新的 `pearl-mining` Python 包（v0.2.0）中，每个证明函数都有明确的版本后缀：

- 新的 V2 prover：`generate_proof_v2`，`verify_proof_v2`，`verify_plain_proof_v2`。
- 旧的 V1 prover：`generate_proof_v1`，`verify_proof_v1`，`verify_plain_proof_v1`。

旧的无后缀名称（`generate_proof`，`verify_proof`，`verify_plain_proof`）已经**移除**。仍然使用这些名称的代码会在启动时报 `AttributeError`。

在运行时检查包版本：

- 新包：`pearl_mining.__version__ == "0.2.0"`。
- 旧包：没有 `pearl_mining.__version__` 属性。

通常你不需要直接调用带版本后缀的函数。请改用下面的分发函数。

### 2.2：使用按证书版本分发的函数

传入模板中的 `requiredcertversion`，库会自动为你选择正确的 prover：

```python
import pearl_mining as pm

def build_zk_proof(
    template: dict,
    header: pm.IncompleteBlockHeader,
    plain_proof: pm.PlainProof,
) -> tuple[pm.ZKProof, int]:
    cert_version = template["requiredcertversion"]

    ok, msg = pm.verify_plain_proof_for_cert_version(cert_version, header, plain_proof)
    if not ok:
        raise ValueError(f"share rejected: {msg}")

    # 在分叉前，如果传入 MoE share，会抛出 ValueError。
    zk_proof = pm.generate_proof_for_cert_version(cert_version, header, plain_proof)
    return zk_proof, cert_version
```

V2 prover 同时接受 dense（旧）和 MoE（新）普通 proof。分叉后，来自旧矿机的 share 仍然可以工作。它们会由 V2 prover 来证明。

如果想在不实际生成证明的情况下低成本检查 share，例如在接收 share 时检查，可以使用 `pm.check_cert_version_eligible(cert_version, plain_proof)`。如果在分叉前传入 MoE share，它会抛出 `ValueError`。

`verify_plain_proof_for_cert_version` 以及带版本后缀的 `verify_plain_proof_v1` / `verify_plain_proof_v2` 都接受 `nbits_override`，用于按照矿池的 share 目标难度而不是区块目标难度来验证 share。

### 2.3：解析来自旧矿机的 share

`PlainProof.from_base64` 同时接受新格式和来自旧矿机的旧格式（分叉前格式）。不需要额外处理：

```python
plain_proof = pm.PlainProof.from_base64(raw_b64)  # 旧格式或新格式
```

**完整示例见 `py-pearl-mining/examples/v1_v2_gateway_example.py`。**
它演示了如何把矿工的 share 转换成区块所需的 ZK proof。

### 2.4：仅当你自己序列化证书时需要注意

如果你的矿池自己构建区块证书字节，而不是使用我们的 gateway 代码，那么 V2 有两处变化：

- **线上格式。** V2 拥有可变长度的 public data：

  ```text
  V1: Version(4) | HeaderHash(32) | PublicData(164) | ProofDataLen(4) | ProofData
  V2: Version(4) | HeaderHash(32) | PublicDataLen(4) | PublicData(N) | ProofDataLen(4) | ProofData
  ```

- **Proof commitment。** 区块头中的 proof commitment 是
  `double_sha256(cert_version_le32 + public_data)`。对于 V2 证书，版本前缀现在是 `2`。之前一直是 `1`。

参考：`miner/pearl-gateway/src/pearl_gateway/blockchain_utils/zk_certificate.py`。

### 2.5：如果你直接使用 Rust crate

如果你的矿池不是使用 Python 包，而是直接链接 `zk-pow` Rust crate，那么 Rust 中也提供了相同逻辑：

- 新的 V2 prover：`zk_pow::api::{prove, verify}`。
- 旧的 V1 prover：`zk_pow::v1::api::{prove, verify}`。注意，V1 使用它自己的 `IncompleteBlockHeader` 和 `MiningConfiguration` 类型，以及**单独的 circuit cache 类型**。两种 cache 都要分别保留一份。
- 两个模块中的 `verify_plain_proof` 都接受 `nbits_override`，用于 share 难度检查。

交叉切换规则和旧格式解析位于 `zk_pow::ffi::plain_proof`：

```rust
use zk_pow::ffi::plain_proof::{check_cert_version_eligible, CertificateVersion, PlainProof};

// 同时接受当前格式和旧版（分叉前）格式。
let proof = PlainProof::deserialize_compat(&share_bytes)?;

// 如果在分叉前传入 MoE share，或传入未知版本，会报错。
let zk_proof = match check_cert_version_eligible(required_cert_version, &proof)? {
    CertificateVersion::ZkDense => {
        zk_pow::v1::api::prove::zk_prove_plain_proof(v1_header, &proof, &mut v1_cache, true)?
    }
    CertificateVersion::ZkMoe => {
        zk_pow::api::prove::zk_prove_plain_proof(header, &proof, &mut v2_cache, true)?
    }
};
```

`proof.min_cert_version()` 会给出能够认证该 share 的最低证书版本；dense 为 V1，MoE 为 V2。如果你只想做低成本检查，可以使用它。

参考：`py-pearl-mining/src/lib.rs`（Python wrapper 只是对这些调用的薄封装）。

## 步骤 3（可选，分叉后）：升级你的矿机

只有在你希望挖掘 MoE 模型时，才需要这一步。如果你继续挖 dense 模型，现有矿机在分叉后无需修改即可继续工作。

新矿机可以生成 MoE proof。这是**可选的**，而且必须等到分叉后：

- **不要在分叉高度前部署支持 MoE 的矿机。** MoE share 在分叉前无法被认证。这是浪费算力，并且你的矿池必须拒绝它（`plain_proof.min_cert_version == 2`，但区块要求 `1`）。
- 分叉后，dense share 和 MoE share 都是有效的。

## 问题

如果有任何不清楚的地方，请通过常用渠道联系 Pearl 团队。