错误字典
每个错误码的成因和解决办法。你不该需要读源码才能从一个错误里恢复。
每个错误都带一个稳定的机器可读 code:
{ "code": "ID_BINDING_FAILED", "error": "node must use a did:svrp identity" }在 code 上分支,永远不要在 error 文本上分支。 code 是契约,不会在没有版本升级的 情况下改变。error 是给人看的,任何版本都可能改措辞。
SDK 的每个错误还带一个直达本页对应条目的 docs 链接。
身份
ID_BINDING_FAILED
HTTP 401。 DID 不是由随附的公钥推导出来的。
成因。 要么这一对确实不匹配,要么——远远更常见——密钥用错了编码解析。 GET /nodes 发的是 hex;GET /peers 发的是 base58。把 base58 当 hex 解析 不会报错,它会产出垃圾,导致每次检查都失败。
解决。 两种编码都接受:
key = bytes.fromhex(pk) if re.fullmatch(r"[0-9a-f]{64}", pk) else b58decode(pk)同时确认你处理了全部三种 DID 形式:8 位 hex 短形式、10 位 hex 碰撞回退、64 位 hex 全形式。 只硬编码 4 字节公式会拒绝合法身份。
如果所有身份突然全都失败,那是编码问题。如果只有一个失败,请认真对待—— 那要么是损坏的记录、要么是伪造,应该报告而不是跳过。
BAD_SIGNATURE
HTTP 401。 签名在规范字节上验证不通过。
成因。 几乎总是规范化漂移,而不是密钥坏了。注册表会用你发来的字段重建被签名的 字节,然后对着重建的那份验证。你的序列化只要差一个字节就会失败,而一切看起来都很正常。
常见元凶:
- 布尔值序列化成
true/false,而不是 Python 的True/False - 字段顺序按你的 JSON 库来,而不是按规范化器来
- 列表字段拼接前没有排序
- 时间戳带了时区偏移而不是
Z,或者带了毫秒
解决。 不要从文字描述里重新推导规范化。用已经过审计的实现: operator-console/lib/crypto/canonical.ts、svrp_crypto.py、或 ln-node bind。
这正是 SDK 拒绝重新实现签名步骤的原因。
会话
NO_SESSION · SESSION_EXPIRED
HTTP 401。 没有 bearer 头,或者会话超过了一小时。
解决。 重新登录。会话故意做得短且不可续期——永不过期的令牌是值得偷的令牌。 捕获这个错误然后重新认证,不要试图延长它。
SDK 在发请求之前就抛 NO_SESSION,所以你在调用处就能拿到它,而不是等一个来回之后。
BAD_CHALLENGE
HTTP 401。 登录挑战未知、过期或已被使用。
成因。 挑战活 300 秒,且首次使用即消耗。不能重放,也不能拿着它等用户去泡杯咖啡。
解决。 在签名前立即取新挑战。永远不要缓存。
绑定
NOT_FOUND(resolve 时)
HTTP 404。 绑定码无效、过期或已被使用。
这个歧义是故意的。 三种成因返回完全相同的响应。
如果它们可区分,这个端点就成了"测试某个码是否真实存在"的预言机。所以错的码和过期的码 看起来一模一样。更差的错误信息,是换取这个端点不可枚举的代价。
RATE_LIMITED
HTTP 429。 600 秒内对同一个码失败 5 次。
解决。 等窗口过去,然后用新码。成功的 resolve 会清零计数器。如果你合理地撞上了这个, 多半是在重试一个打错的码——让用户重新读一遍,而不是重试。
TTL_TOO_LONG · ALREADY_EXPIRED · BAD_STATE
HTTP 400 / 409。
| Code | 含义 | 解决 |
|---|---|---|
TTL_TOO_LONG | 会话窗口超过 600 秒或 ≤ 0 | 用 RFC3339 UTC(Z 后缀、秒精度),窗口保持在 10 分钟内 |
ALREADY_EXPIRED | expires_at 已是过去 | 检查机器时钟——偏差几秒就会让本地看起来正确的时间戳在远端被拒 |
BAD_STATE | 会话已处于终态 | 终态永不改变。开一个新会话,不要试图复活它 |
STALE_TS
HTTP 401。 节点的轮询时间戳与注册表时间相差超过 300 秒。
解决。 修节点的时钟。 用 NTP。这是一个伪装成认证问题的时钟问题,重试多少次都没用。
仅 SDK
NOT_YET_WIRED
你调用了一个没有数据源、或者根本没建好的东西。
这不是 bug。 另一种做法——返回一个看起来合理的零——会让一个未接通的余额被当成收益 展示给用户。
解决。 看 error.level:
level | 含义 | 怎么做 |
|---|---|---|
not_yet_wired | 端点在线,无数据源 | 用 *Raw() 读法,并把说明和数值一起展示 |
testing | 代码存在且测试通过,未部署 | 等。关注状态页 |
planned | 没做 | 现在不要围绕它做设计 |
TRANSPORT_ERROR
网络调用本身失败——DNS、TLS、超时、离线。
SDK 已经对瞬时失败(429、5xx、网络错误)做了指数退避加抖动的重试, 并且绝不重试 4xx——重放一个被拒的签名,只会更贵地被再拒一次。
这里没有你遇到的错误?
遇到本页没有记录的错误码,那就是文档 bug,我们想知道。请带上 code 和你当时在调什么。