名称:调查卡住消息 描述:调查中继器队列中的卡住消息。当警报提到“队列长度 > 0”时使用,以诊断消息为何卡住,或获取消息ID以进行黑名单操作。
调查卡住消息
查询中继器API以调查卡住消息、其重试次数和错误原因。
何时使用
-
基于警报的触发:
- 警报:“已知应用上下文中继器队列长度 > 0 持续40分钟”
- 任何提到准备队列中卡住消息的警报
- 特定应用上下文的高重试次数
-
用户请求触发:
- “为什么 [应用上下文] 的消息卡住了?”
- “在 [链] 上调查卡住消息”
- “是什么导致队列警报?”
- 粘贴 Grafana 警报 URL
输入参数
选项1:Grafana 警报 URL(推荐)
/investigate-stuck-messages https://abacusworks.grafana.net/alerting/grafana/cdg1ro5hi4vswb/view?tab=instances
选项2:手动指定
/investigate-stuck-messages app_context=EZETH/renzo-prod remote=linea
| 参数 | 必需 | 默认值 | 描述 |
|---|---|---|---|
alert_url |
否 | - | Grafana 警报 URL(从触发实例中提取 app_context/remote) |
app_context |
否* | - | 应用上下文(例如,EZETH/renzo-prod, oUSDT/production) |
remote |
否* | - | 目标链名称(例如,linea, ethereum, arbitrum) |
environment |
否 | mainnet3 |
部署环境 |
*必须提供 alert_url 或同时提供 app_context 和 remote。
工作流程
步骤1:解析输入并提取警报实例
如果提供 Grafana 警报 URL:
-
从 URL 中提取警报 UID(例如,从
.../alerting/grafana/cdg1ro5hi4vswb/view中提取cdg1ro5hi4vswb) -
使用
mcp__grafana__query_prometheus直接查询 Prometheus 获取触发实例:sum by (app_context, remote)( max_over_time( hyperlane_submitter_queue_length{ queue_name="prepare_queue", app_context!~"Unknown|merkly_eth|merkly_erc20|helloworld|velo_message_module", hyperlane_context!~"rc|vanguard0|vanguard1|vanguard2|vanguard3|vanguard4|vanguard5", operation_status!~"Retry\\(ApplicationReport\\(.*\\)\\)|FirstPrepareAttempt", hyperlane_deployment="mainnet3", }[2m] ) ) > 0 -
从每个结果中提取
app_context和remote标签。
如果手动提供 app_context/remote:
直接使用提供的值。
步骤2:设置到中继器的端口转发
检查端口 9090 是否已在使用:
lsof -i :9090
如果未使用,在后台启动端口转发:
kubectl port-forward omniscient-relayer-hyperlane-agent-relayer-0 9090 -n mainnet3 &
等待几秒钟让端口转发建立。
步骤3:获取链的域ID
从注册表中查找域ID:
cat node_modules/.pnpm/@hyperlane-xyz+registry@*/node_modules/@hyperlane-xyz/registry/dist/chains/<chain>/metadata.json | jq '.domainId'
常见域ID:
- ethereum: 1
- optimism: 10
- arbitrum: 42161
- polygon: 137
- base: 8453
- unichain: 130
- avalanche: 43114
步骤4:查询中继器 API
对于每个目标链,查询中继器 API:
curl -s 'http://localhost:9090/list_operations?destination_domain=<DOMAIN_ID>' > /tmp/<chain>.json
响应包含操作,其中:
id: 消息 ID (H256)operation.message.sender: 发送者地址operation.message.recipient: 接收者地址operation.num_retries: 重试次数(越高表示越卡住)operation.status: 错误状态(例如,{"Retry": "ErrorEstimatingGas"})operation.message.origin: 起源域 IDoperation.message.destination: 目标域 IDoperation.app_context: 应用上下文名称
步骤5:按应用上下文过滤消息
在 rust/main/app-contexts/mainnet_config.json 中查找 app_context:
jq '.metricAppContexts[] | select(.name == "<APP_CONTEXT>")' rust/main/app-contexts/mainnet_config.json
过滤 API 结果,仅包含以下消息:
operation.message.recipient匹配该目标域的recipientAddress值之一
重要:地址填充为32字节(H256格式)。
步骤6:查询 GCP 日志以获取实际错误
根据重试次数计算日志新鲜度:
中继器使用指数退避(参见 rust/main/agents/relayer/src/msg/pending_message.rs 中的 calculate_msg_backoff):
| 重试次数 | 退避/重试 | 累计时间 | 新鲜度标志 |
|---|---|---|---|
| 1-4 | 5s-1min | ~2min | --freshness=1h |
| 5-24 | 3min | ~1h | --freshness=3h |
| 25-39 | 5-26min | ~5h | --freshness=12h |
| 40-49 | 30min-1h | ~12h | --freshness=24h |
| 50-60 | 2-22h | ~35h | --freshness=3d |
| 60+ | 22h+ | 35h+ | --freshness=7d |
对于每个消息 ID,使用计算出的新鲜度查询 GCP 日志:
gcloud logging read 'resource.type=k8s_container AND resource.labels.namespace_name=mainnet3 AND resource.labels.pod_name:omniscient-relayer AND jsonPayload.span.id:<MESSAGE_ID> AND jsonPayload.fields.error:*' --project=abacus-labs-dev --limit=1 --format='value(jsonPayload.fields.error)' --freshness=<CALCULATED_FRESHNESS>
使用 sed(macOS 兼容)从响应中提取人类可读的错误:
echo "$raw_error" | sed -n 's/.*execution reverted: \([^"]*\)".*/\1/p' | head -1
常见错误模式:
"execution reverted: Nonce already used"→ “Nonce already used”"execution reverted: panic: arithmetic underflow"→ “Arithmetic underflow”
注意:不要使用 grep -P,因为它在 macOS 上不可用。
步骤7:展示调查结果
输出一个详细的摘要表,包含完整的消息ID和两个错误来源:
## [APP_CONTEXT] 的调查结果
### 摘要
- 总卡住消息数:X
- 受影响的目标链:[列表]
- 重新准备原因:ErrorEstimatingGas (N), CouldNotFetchMetadata (M)
### 消息
| 消息 ID | 重试次数 | 重新准备原因 | 错误 | 起源 |
|---------|---------|--------------|-----------|------|
| `0xaa18ebc1c79345e6d24984a0b9a5ab66c968d128d46b2357b641e56e71b8d30c` | 47 | ErrorEstimatingGas | Nonce already used | optimism |
| `0xd6aeef7c092a88aa23ad53227aeb834ae731d059b3ce749db8451e761f3f15ac` | 47 | ErrorEstimatingGas | Nonce already used | arbitrum |
**重要**:始终显示完整的66字符消息ID(0x + 64个十六进制字符)。不要截断。
### 错误分析
[根据找到的实际日志错误解释]
### 后续步骤
要黑名单这些消息,运行:
/denylist-stuck-messages <message_ids> app_context=APP_CONTEXT
列定义:
- 重新准备原因:来自中继器 API 的
operation.status(例如,ErrorEstimatingGas, CouldNotFetchMetadata) - 错误:来自 GCP 日志的实际回退原因(例如,“Nonce already used”, “Arithmetic underflow”)
步骤8:输出黑名单命令
在调查结果末尾,输出完整的黑名单命令:
### 后续步骤
要黑名单,运行:
/denylist-stuck-messages 0xaa18ebc1c79345e6d24984a0b9a5ab66c968d128d46b2357b641e56e71b8d30c 0xd6aeef7c092a88aa23ad53227aeb834ae731d059b3ce749db8451e761f3f15ac app_context=APP_CONTEXT
始终使用完整的消息ID,从不截断。
错误状态参考
| 状态 | 含义 | 操作 |
|---|---|---|
ErrorEstimatingGas |
气体估计失败(合约回退) | 通常黑名单 - 合约不会接受 |
CouldNotFetchMetadata |
无法获取 ISM 元数据 | 检查验证器,可能自行解决 |
ApplicationReport(...) |
应用特定错误 | 检查特定错误消息 |
GasPaymentNotFound |
无 IGP 支付 | 可能需要手动中继并支付气体 |
错误处理
- 端口转发失败:检查 kubectl 上下文:
kubectl config current-context - 未找到消息:队列可能已清除;警报可能已过时
- API 返回错误:检查中继器 pod:
kubectl get pods -n mainnet3 | grep relayer - 应用上下文未找到:可能是新的或自定义的;向用户询问发送者/接收者地址
前提条件
kubectl配置为可访问主网集群- Grafana MCP 服务器已连接(用于警报 URL 解析)