卡住消息调查Skill investigate-stuck-messages

这个技能用于调查区块链中继器队列中卡住的消息,通过查询Relayer API、分析GCP日志来诊断错误原因,如Gas估计失败、元数据获取问题等,并提供完整的消息ID以进行黑名单操作。适用于DevOps工程师、区块链运维人员,关键词包括:区块链、Hyperlane、中继器、消息队列、故障排查、监控、Prometheus、kubectl、GCP日志、跨链技术、节点运维。

节点运维 0 次安装 0 次浏览 更新于 3/11/2026

名称:调查卡住消息 描述:调查中继器队列中的卡住消息。当警报提到“队列长度 > 0”时使用,以诊断消息为何卡住,或获取消息ID以进行黑名单操作。

调查卡住消息

查询中继器API以调查卡住消息、其重试次数和错误原因。

何时使用

  1. 基于警报的触发:

    • 警报:“已知应用上下文中继器队列长度 > 0 持续40分钟”
    • 任何提到准备队列中卡住消息的警报
    • 特定应用上下文的高重试次数
  2. 用户请求触发:

    • “为什么 [应用上下文] 的消息卡住了?”
    • “在 [链] 上调查卡住消息”
    • “是什么导致队列警报?”
    • 粘贴 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_contextremote

工作流程

步骤1:解析输入并提取警报实例

如果提供 Grafana 警报 URL:

  1. 从 URL 中提取警报 UID(例如,从 .../alerting/grafana/cdg1ro5hi4vswb/view 中提取 cdg1ro5hi4vswb

  2. 使用 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
    
  3. 从每个结果中提取 app_contextremote 标签。

如果手动提供 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: 起源域 ID
  • operation.message.destination: 目标域 ID
  • operation.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 解析)