关于 testing
面向 Midnight 开发者的 Compact 测试与调试技能,讲解编译和运行时错误、披露追踪、快速迭代及版本协调。
主要功能
- 01区分静态错误与运行时错误
- 02解读编译器消息和披露追踪
- 03开发时使用 --skip-zk
- 04协调六个组件的版本
- 05排查常见 Compact 和环境问题
适用场景
- 01诊断未声明 disclose() 的错误
- 02不生成证明密钥测试合约逻辑
- 03解决 Midnight 组件版本不匹配
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kali-decoder/ midnight-skills --skill testing安装后在 Claude Code 中输入/testing确认已加载。
详细介绍
范围
本技能涵盖 Compact 中的两层错误(静态错误与动态错误)、如何阅读编译器消息(尤其是未声明披露的追踪信息)、使用 --skip-zk 的快速开发循环、6 个组件的版本管理,以及最常见的调试模式。官方测试文档“即将推出”——本文内容经真实开发实践验证。
1) 两层错误
Compact 有两种错误,而不是一种:
静态错误 — 编译器在生成任何输出之前捕获的错误。开发时,你会在终端中看到这些错误。静态错误是你的好帮手——编译器会准确指出问题所在及其位置。
动态错误 — 由生成的 JavaScript 在运行时捕获的错误。电路执行时,你会看到这些错误。这类错误更难调试,因为它们发生在你没有编写的生成代码中。
静态错误(编译时)
| 错误类型 | 含义 | 捕获时机 |
|---|---|---|
| 语法 | 代码格式错误 | 解析器 |
| 类型不匹配 | 使用了错误的类型 | 类型检查器 |
| 未声明披露 | 缺少 disclose() | 见证保护 |
| 未定义引用 | 未知标识符 | 名称解析器 |
| 泛型未专门化 | 在顶层使用了泛型实体 | 作用域检查器 |
| 递归结构体 | 结构体引用自身 | 声明检查器 |
| 递归电路 | 电路调用自身 | 声明检查器 |
for 中的 return | 循环内的 return | 语句检查器 |
| 密封账本写入 | 在电路中写入密封字段 | 声明检查器 |
如果编译器没有生成输出,说明至少存在一个静态错误。请检查错误消息。
动态错误(运行时)
| 错误类型 | 含义 | 示例 |
|---|---|---|
| 类型不匹配 | 参数类型或数量错误 | 使用错误的参数调用 |
| 溢出 | 转换后的值对目标类型来说过大 | 1000 as Uint<8> |
| 下溢 | 计数器递减至零以下 | 计数器为 0 时执行 counter -= 1 |
| 嵌套值未初始化 | 嵌套账本状态未初始化 | 插入前调用 map.lookup(k).lookup(k2) |
| 默克尔树已满 | 向已满的树中插入 | isFull() 为真时调用 tree.insert() |
动态错误更难调试。请根据错误消息中的行号,在源文件中定位问题。
2) 阅读编译器错误消息
类型错误
/path/contract.compact line 12 char 5:
type error: expected Uint<64>, got Field
解读方式:行号:字符位置、预期类型、实际类型。插入符号(^)指向有问题的标记。
未声明披露
Exception: /path/contract.compact line 6 char 11:
potential witness-value disclosure must be declared but is not:
witness value potentially disclosed:
the return value of witness getBalance at line 2 char 1
nature of the disclosure:
ledger operation might disclose the witness value
via this path through the program:
the right-hand side of = at line 6 char 11
请从下往上阅读。 该路径追踪见证数据的流向:
- 来源: 第 2 行的
getBalance() - 路径: 经过赋值语句右侧
- 目标: 第 6 行的账本操作
修复方法: 沿着该路径添加 disclose(),尽可能靠近披露点。
返回值缺少 disclose()
Exception: line 5 char 3:
potential witness-value disclosure must be declared but is not:
witness value potentially disclosed:
the return value of witness getBalance at line 2 char 1
nature of the disclosure:
the value returned from exported circuit check might disclose
the result of a comparison involving the witness value
即使是 Boolean 比较结果,也算作披露。请用 disclose() 包裹见证调用或返回值。
版本不匹配
Error: runtime version mismatch: expected 0.15.0, got 0.14.2
已编译的合约所需的运行时版本不同。请参阅下文的版本管理。
3) 使用 --skip-zk 的开发循环
生成证明密钥很慢。在迭代开发期间,可以跳过这一步:
compact compile --skip-zk contracts/contract.compact contracts/managed/contract
这会生成 contract/index.js 和 compiler/contract-info.json,足以用于测试逻辑。最终构建时请重新启用。
4) 版本管理
Midnight 有 6 个组件必须保持同步。如果版本不一致,就会遇到含义不明的运行时版本不匹配错误。
| 组件 | 含义 | 检查方法 |
|---|---|---|
| CLI 工具 | compact 二进制文件 | compact --version |
| 编译器 | compactc | compact compile --version |
| 运行时 | @midnight-ntwrk/compact-runtime | npm list |
| 账本 | @midnight-ntwrk/ledger-v8 | npm list |
| JS 库 | @midnight-ntwrk/midnight-js-* | npm list |
| 证明服务器 | Docker 镜像 | 镜像标签 |
检查当前版本
compact --version
compact compile --version
npm list @midnight-ntwrk/compact-runtime
npm list @midnight-ntwrk/ledger-v8
查看兼容性矩阵
官方版本兼容性矩阵是权威依据。未经核对,切勿混用不同版本。
在 package.json 中锁定精确版本
{
"dependencies": {
"@midnight-ntwrk/compact-runtime": "0.15.0",
"@midnight-ntwrk/ledger-v8": "8.0.3"
}
}
不要使用 ^ 或 ~——它们会自动更新,可能在不知情的情况下破坏兼容性。
使用 npm ci 进行可复现安装
rm -rf node_modules
npm ci
npm ci 会严格安装 package-lock.json 中指定的内容。npm install 会获取符合条件的最新版本。
更新任何组件后
- 一并更新所有相关组件
- 重新编译合约
- 使用新的 Docker 镜像重启证明服务
- 运行测试套件
5) 常见错误及修复方法
Ledger 写入时忘记使用 disclose()
// wrong — compiler error
balance = getBalance();
// correct
balance = disclose(getBalance());
返回值忘记使用 disclose()
// wrong — compiler error (comparison of witness data)
export circuit check(n: Uint<64>): Boolean {
return getSecret() > n;
}
// correct — declare the disclosure
export circuit check(n: Uint<64>): Boolean {
return disclose(getSecret()) > n;
}
在 for 循环中使用 return
// wrong — static error
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
for (const x of v) {
if (x == target) return true;
}
return false;
}
// correct — use fold
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
return fold((found, x) => found || x == target, false, v);
}
递归电路
// wrong — static error: recursion not allowed
circuit factorial(n: Uint<64>): Uint<64> {
return n == 0 ? 1 : n * factorial(n - 1);
}
改用 fold 或显式展开。Compact 要求电路必须有限。
窄化转换在运行时溢出
const x: Uint<64> = 1000;
const y = x as Uint<8>; // dynamic error: 1000 doesn't fit
转换前务必验证数值是否在范围内。使用 assert 或有界类型。
嵌套 Ledger 状态未初始化
ledger fld: Map<Boolean, Map<Field, Counter>>;
// wrong — dynamic error (inner map not initialized)
export circuit increment(b: Boolean, n: Field): [] {
fld.lookup(b).lookup(n) += 1;
}
// correct — initialize first
export circuit init(b: Boolean): [] {
fld.insert(disclose(b), default<Map<Field, Counter>>);
}
使用 transientHash 的结果时未调用 disclose()
// wrong — compiler error (witness-tainted)
ledger h: Field;
export circuit store(v: Field): [] {
h = transientHash<Field>(v);
}
// correct — declare disclosure
h = disclose(transientHash<Field>(v));
// or use transientCommit (nonce provides hiding, no disclose needed)
h = transientCommit<Field>(v, nonce);
6) 常见环境问题
| 错误 | 原因 | 修复方法 |
|---|---|---|
compact: command not found | 二进制文件不在 PATH 中 | export PATH="$HOME/.compact/bin:$PATH" |
ERR_UNSUPPORTED_DIR_IMPORT | Node.js 尝试导入目录 | 打开新终端,清除缓存 |
| Docker 连接错误 | Docker Desktop 未运行 | 启动 Docker Desktop |
| 端口 6300 已被占用 | 另一个容器使用了同一端口 | -p 6301:6300 |
| 运行时版本不匹配 | 运行时包已过时 | 查看兼容性矩阵并更新 |
7) 版本检查脚本
提交错误报告前运行此脚本:
#!/bin/bash
echo "=== Midnight Version Check ==="
echo "CLI:"; compact --version || echo "not found"
echo "Compiler:"; compact compile --version || echo "not found"
echo "Runtime:"
npm list --depth=0 | grep @midnight-ntwrk || echo "none found"
echo "Node.js:"; node --version
echo "Compare with: docs.midnight.network/relnotes/support-matrix"
8) 获取帮助
查看这些说明后仍无法解决问题:
- Discord
#dev-chat——发布错误消息和版本详情 - 常见问题——docs.midnight.network/troubleshoot/faq
- 论坛——forum.midnight.network
请务必附上:
- 版本检查脚本的输出
- 完整的错误消息
.compact文件(或相关摘录)- 预期结果与实际情况
9) 快速回顾
- 静态错误——编译器会在生成输出前捕获。请检查错误消息。
- 动态错误——会在生成代码的运行时发生。请查看行号。
- 未声明的披露追踪——从底部向上阅读,它会追踪从来源到披露点的路径。
- 开发期间使用
--skip-zk。最终构建时启用。 - 在
package.json中锁定精确版本。使用npm ci。 - 更新任何组件前,先检查兼容性矩阵。
- **每次更新后:**重新编译、重启证明服务并运行测试。