name: warp-route-check description: 一个技能,允许用户验证在应用某些配置后,warp路由部署将处于预期状态
Warp路由部署检查器
您是一个专门用于检查warp路由部署和更新的代理,使用Hyperlane CLI和Heimdall CLI。
先决条件
此技能需要以下工具和设置:
必需工具
- Hyperlane CLI:
npm install -g @hyperlane-xyz/cli - Heimdall: Ethereum交易解码器
- Foundry (cast):
curl -L https://foundry.paradigm.xyz | bash && foundryup - jq: JSON处理器 (
brew install jq或apt-get install jq) - Python 3: 带有标准库
- pnpm: 包管理器
必需设置
-
Hyperlane Registry 克隆(默认位置:
$HOME/hyperlane-registry):git clone https://github.com/hyperlane-xyz/hyperlane-registry.git $HOME/hyperlane-registry -
Hyperlane Monorepo 克隆(用于http-registry):
git clone https://github.com/hyperlane-xyz/hyperlane-monorepo.git export HYPERLANE_MONOREPO="/path/to/hyperlane-monorepo" -
可选: http_registry shell函数(添加到 ~/.zshrc 或 ~/.bashrc):
function http_registry() { pnpm -C $HYPERLANE_MONOREPO/typescript/infra start:http-registry }
环境变量(可选)
export HYPERLANE_REGISTRY="$HOME/hyperlane-registry" # 自定义注册表位置
export HYPERLANE_MONOREPO="/path/to/monorepo" # Monorepo位置
您的任务
按照以下步骤验证warp路由部署:
步骤 0: 验证环境
在继续之前验证所有必需的工具和路径:
# 检查必需的命令行工具
command -v hyperlane >/dev/null 2>&1 || { echo "❌ hyperlane CLI 未安装。运行: npm install -g @hyperlane-xyz/cli"; exit 1; }
command -v heimdall >/dev/null 2>&1 || { echo "❌ heimdall 未安装"; exit 1; }
command -v cast >/dev/null 2>&1 || { echo "❌ foundry 未安装。运行: curl -L https://foundry.paradigm.xyz | bash"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "❌ jq 未安装"; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "❌ python3 未安装"; exit 1; }
echo "✅ 所有必需工具已安装"
# 确定注册表位置(可通过环境变量配置)
REGISTRY_PATH="${HYPERLANE_REGISTRY:-$HOME/hyperlane-registry}"
# 验证注册表是否存在
if [ ! -d "$REGISTRY_PATH" ]; then
echo "❌ 注册表未找到于 $REGISTRY_PATH"
echo "克隆它: git clone https://github.com/hyperlane-xyz/hyperlane-registry.git $REGISTRY_PATH"
exit 1
fi
echo "✅ 注册表找到于 $REGISTRY_PATH"
# 确定HTTP注册表启动命令
if type http_registry >/dev/null 2>&1; then
HTTP_REGISTRY_CMD="http_registry"
elif [ -n "$HYPERLANE_MONOREPO" ] && [ -d "$HYPERLANE_MONOREPO/typescript/infra" ]; then
HTTP_REGISTRY_CMD="pnpm -C $HYPERLANE_MONOREPO/typescript/infra start:http-registry"
else
echo "❌ 无法启动HTTP注册表。请执行以下任一操作:"
echo " 1. 设置HYPERLANE_MONOREPO环境变量指向monorepo,或"
echo " 2. 在您的shell中定义http_registry函数"
exit 1
fi
echo "✅ HTTP注册表命令可用"
步骤 1: 获取Warp路由ID
- 如果未提供,请向用户询问warp路由ID
- warp路由ID应为Hyperlane注册表使用的格式
常见链ID参考(用于解析交易):
1 = ethereum 8453 = base
10 = optimism 42161 = arbitrum
56 = bsc 43114 = avalanche
88 = viction 59144 = linea
130 = unichain 137 = polygon
检查fork输出以获取完整映射
步骤 2: 验证注册表分支
- 关键: hyperlane-registry位置位于
$REGISTRY_PATH(默认:$HOME/hyperlane-registry) - 与用户确认注册表指向正确的分支
- 注册表应包含预期的最终配置
- 如果未确认,请让用户指定分支名称
- 使用
git -C "$REGISTRY_PATH" branch --show-current验证当前分支 - 如果不在正确分支上,请让用户先切换分支
- 记住原始分支 - 您需要在最后切换回
main
步骤 3: 启动HTTP注册表(后台)
- 在后台运行步骤0中确定的HTTP注册表命令
- 使用
run_in_background=true参数用于Bash工具 - 关键: 检查输出以找到实际端口(通常为3333,但可能变化)
- 在后台任务输出中查找带有端口号的“Server running”消息
- 记录注册表URL(例如,http://localhost:3333 或 http://localhost:XXXX)
- 不要假设端口3333 - 始终从输出验证
# 使用步骤0中确定的命令
$HTTP_REGISTRY_CMD > /tmp/http-registry.log 2>&1 &
步骤 4: Fork Warp路由(后台)
- 重要:
hyperlane warp fork也会阻塞 - 它运行一个forked注册表的服务器 - 使用已安装的
hyperlane命令 - 在后台运行
hyperlane warp fork并提供warp路由ID - 这创建本地forked链(anvil实例)并提供forked注册表
- 使用步骤3中的注册表URL作为源
- 关键: 检查输出以找到实际forked注册表端口(通常为8535,但可能变化)
- 查找带有端口号的消息(例如,
{ port: 8535 } Server running) - 记录forked注册表URL:
http://localhost:<实际端口> - 等待fork服务器准备就绪(检查输出中的“Server running”)
- 重要: warp路由中的每个链都在从8545开始的连续端口上获得自己的anvil实例
- 关键: 检查fork输出以获取每个链的实际端口分配 - 不要使用示例端口
# 使用步骤3中的实际注册表URL
hyperlane warp fork --id <warp-route-id> --registry <步骤3中的实际注册表URL>
仔细阅读fork输出以提取:
- Forked注册表服务器端口(例如,“{ port: 8535 } Server running”)
- 链特定的anvil端口(例如,“Successfully started Anvil node for chain ethereum at http://127.0.0.1:YYYY”)
创建一个端口映射从fork输出供后续使用。
步骤 5: 分析交易文件(关键 - 多个所有者检测)
重要: 在提交交易之前,您必须分析交易文件以检测是否每个链针对多个所有者。
5a. 识别有交易的链和合约
# 获取所有唯一的链ID
jq -r '.[].chainId' <交易文件> | sort -u
# 获取每个链的所有唯一合约地址
jq -r '.[] | "\(.chainId)|\(.to)"' <交易文件> | sort -u
映射链ID到名称使用fork输出(不要假设 - 验证实际链名称)。
5b. 查询所有合约地址的所有者
关键: 对于每个被针对的唯一合约地址,从其forked链查询当前所有者。
# 对于每个链上的每个合约(使用fork输出中的实际端口):
cast call <合约地址> "owner()(address)" --rpc-url http://localhost:<链的实际端口>
示例工作流(端口为示例 - 使用您fork输出中的实际端口):
# 示例假设fork输出显示:ethereum在端口8548,arbitrum在端口8545
# 您的端口可能不同 - 始终检查您的fork输出
# 查询ethereum合约(使用您fork输出中的实际端口)
cast call 0xe1De... "owner()(address)" --rpc-url http://localhost:<您的ETH端口>
cast call 0xcf4ec... "owner()(address)" --rpc-url http://localhost:<您的ETH端口>
# 查询arbitrum合约(使用您fork输出中的实际端口)
cast call 0xAd435... "owner()(address)" --rpc-url http://localhost:<您的ARB端口>
创建完整的所有者映射:
{
"<chainId>_<contractAddress>": "<实际所有者地址>",
"1_0xe1De9910fe71cC216490AC7FCF019e13a34481D7": "0x3965...",
"1_0xcf4ecA86606372B975FaF04a97e8eE3AfeA5a02D": "0x8Ff4..."
}
注意: 所有者可能是合约地址(多签、时间锁) - 这没关系,anvil可以模拟它们。
5c. 检测每个链的多个所有者
按链和所有者分组交易以检测是否需要拆分:
# 伪代码逻辑
for each chain:
unique_owners = set of owners for contracts on this chain
if len(unique_owners) > 1:
# 链有多个所有者 - 需要拆分
splitting_needed = True
如果任何链有多个所有者: 继续步骤5d(拆分交易) 如果所有链都有单个所有者: 跳到步骤5e(创建单个策略)
5d. 按所有者拆分交易(如果检测到多个所有者)
何时执行: 如果步骤5c检测到每个链有多个所有者。
如何拆分:
- 从步骤5b创建所有者到合约的映射
- 按目标合约的所有者对交易进行分组
- 为每个唯一所有者创建单独的交易文件
- 每个文件仅包含针对由该所有者拥有的合约的交易
# 按所有者拆分交易
owner_groups = group_by(transactions, lambda tx: owner_map[f"{tx.chainId}_{tx.to}"])
for owner, txs in owner_groups:
write_file(f"/tmp/transactions-owner-{owner}.json", txs)
示例: 如果171个交易针对6个链上的17个不同所有者,创建17个单独的贸易文件。
继续步骤5f以处理每个拆分文件。
5e. 创建单个策略文件(如果每个链有单个所有者)
何时执行: 如果步骤5c发现每个链没有多个所有者。
创建一个策略文件,每个链一个所有者:
<链名称>:
submitter:
chain: <链名称>
type: impersonatedAccount
userAddress: '<步骤5b中的实际所有者>'
保存到 /tmp/<路由>-strategy.yaml
继续步骤5g。
5f. 创建多个策略文件(如果交易被拆分)
何时执行: 如果步骤5d拆分交易。
对于每个拆分交易文件,创建相应的策略文件:
- 识别哪些链在该交易文件中
- 对于每个链,使用匹配拆分组的所有者
- 使用该所有者创建链特定策略
<链名称>:
submitter:
chain: <链名称>
type: impersonatedAccount
userAddress: '<此拆分的所有者>'
保存每个为 /tmp/strategy-owner-<所有者>.yaml
5g. 为所有所有者账户提供资金
使用 anvil_setBalance 在所有各自的链上为所有唯一所有者地址提供资金(使用fork输出中的实际端口):
BALANCE="0x56BC75E2D63100000" # 100 ETH
# 对于每个链上的每个所有者(使用您fork输出中的实际端口):
cast rpc anvil_setBalance <所有者地址> $BALANCE --rpc-url http://localhost:<您的实际端口>
示例(端口为示例 - 使用您fork输出中的实际端口):
# 示例假设fork输出显示ethereum在端口8548
# 您的端口可能不同 - 使用您fork输出中的实际端口
cast rpc anvil_setBalance 0x3965... $BALANCE --rpc-url http://localhost:<您的ETH端口>
cast rpc anvil_setBalance 0x8Ff4... $BALANCE --rpc-url http://localhost:<您的ETH端口>
为所有唯一所有者提供资金您从步骤5b识别的。
步骤 6: 提交交易
6a. 确定提交方法
- 单个策略: 使用一个交易文件提交一次(来自步骤5e)
- 多个策略: 提交多次,每个拆分文件一次(来自步骤5f)
6b. 提交每个交易文件
对于每个交易文件及其对应策略:
# 设置虚拟HYP_KEY(即使模拟也必需)
HYP_KEY="0x0000000000000000000000000000000000000000000000000000000000000001"
# 使用已安装的hyperlane命令和实际forked注册表URL提交交易
hyperlane submit \
--transactions <交易文件> \
--strategy <对应策略文件> \
--registry <您步骤4中的实际forked注册表URL> \
--yes
关键:
- 使用已安装的
hyperlane命令 - 不需要–id参数 - 提交自动从交易文件检测链
- 使用 您步骤4中的实际forked注册表URL(不是步骤3中的源注册表URL)
- 即使使用模拟,虚拟HYP_KEY也是必需的
跟踪提交结果:
- 计数成功提交
- 计数失败提交
- 记录任何错误消息
如果使用拆分文件: 顺序提交所有文件,跟踪每个的成功/失败。
步骤 7: 使用Heimdall解码交易
重要: 提交后,使用Heimdall解码所有交易以用于最终报告。
# 对于原始合并文件中的每个交易:
for tx in transactions:
decoded = run_command(f"heimdall decode {tx.data} --default")
store_decoded(tx.chainId, tx.to, tx.annotation, decoded)
保存解码输出为JSON以包含在最终报告中:
[
{
"index": 1,
"chain": "ethereum",
"to": "0x...",
"annotation": "...",
"decoded": "..."
}
]
保存到 /tmp/decoded-transactions.json
步骤 8: 运行Warp检查
对forked注册表执行 hyperlane warp check(使用您步骤4中的实际URL):
hyperlane warp check \
--id <warp-route-id> \
--registry <您步骤4中的实际forked注册表URL> \
--yes
捕获完整输出到 /tmp/warp-check.log 以用于最终报告。
注意: Warp检查可能显示新添加链的提供者错误 - 这是预期的。
步骤 9: 解释结果
仔细分析warp检查输出:
关键: 专注于有交易的链的违规。其他链上的违规是预期的,因为这些链未被修改。
预期违规(✅ 这些是正常的)
1. 没有交易的链上的违规: 总是预期的
avalanche: # ← 此链没有交易
proxyAdmin:
owner:
EXPECTED: '0x...'
ACTUAL: '0x...'
原因: 不在交易文件中的链未被修改。它们的违规显示当前状态和目标状态之间的差距。
2. 有交易的链上的所有权不匹配: 如果交易不包括所有权转移,则是预期的
ethereum: # ← 有交易,但所有权尚未转移
proxyAdmin:
owner:
EXPECTED: '0x...'
ACTUAL: '0x...'
原因: 注册表显示完整部署后的目标状态。如果交易配置功能但不转移所有权,所有权差异是预期的。
3. 费用配置差异: 可能是预期的
ethereum:
tokenFee:
maxFee:
ACTUAL: '115792...'
EXPECTED: ''
原因: 交易可能设置尚未在注册表配置中的费用值。
真实违规(❌ 这些是问题)
这些指示 有交易的链上 的实际配置问题:
ethereum: # ← 有交易但缺少预期配置
remoteRouters:
42161: # arbitrum域
ACTUAL: ''
EXPECTED: '0x...'
要寻找的真实问题:
- 缺少ISM配置,应该已被设置
- 缺少远程路由器注册,交易应该已添加
- 缺少目标燃气设置,交易应该已配置
- 错误的xERC20限制(除了显示0.0的新链)
关键: 只有那些有交易且应该已被这些交易修复的链上的违规才是真实问题。
步骤 10: 生成最终报告
创建一个全面的markdown报告,包含以下部分:
报告结构
# <WARP_ROUTE_ID> 验证报告
**日期:** <时间戳>
**分支:** <注册表分支>
**交易文件:** <文件名>
## 执行摘要
[总体通过/失败裁决]
[关键指标: 总交易数,修改的链,成功率]
## 端口检测(动态)
[列出从fork输出中提取的所有端口]
- HTTP注册表: <实际端口>
- Forked注册表: <实际端口>
- 链端口: [链: 端口映射]
## 交易细分
[表格显示有交易的链,计数,所有者]
## 提交结果
[交易提交的详细结果]
- 创建的拆分文件(如果适用)
- 提交成功/失败计数
- 遇到的任何错误
## Warp检查结果
### 有交易的链
[功能配置分析]
- 路由器: [状态]
- 目标燃气: [状态]
- ISM: [状态]
- 桥接限额: [状态]
### 没有交易的链
[列出未修改的链及其预期违规]
## 解码交易(Heimdall输出)
```json
[来自 /tmp/decoded-transactions.json 的完整解码交易输出]
```
Warp检查输出(原始)
[来自 /tmp/warp-check.log 的完整warp检查输出]
结论
[带有支持理由的最终裁决]
**保存报告**到 `/tmp/<路由>-validation-report.md`
### 步骤 11: 清理
- 清理 **两个** 后台进程:
- 停止 `hyperlane warp fork` 服务器
- 停止 `http_registry` 服务器
- **切换注册表分支回main**: `git -C "$REGISTRY_PATH" checkout main`
- 确认清理完成
```bash
# 杀死后台进程
pkill -f "hyperlane warp fork"
pkill -f "http_registry"
# 切换回main(使用$REGISTRY_PATH变量)
git -C "$REGISTRY_PATH" checkout main
常见问题及解决方案
“Ownable: caller is not the owner”
- 原因: 使用错误的所有者地址或单所有者策略用于多所有者交易
- 解决方案: 始终查询实际所有者(步骤5b),并在检测到多个所有者时拆分交易(步骤5d)
“Insufficient funds for intrinsic transaction cost”
- 原因: 模拟账户在forked链上没有余额
- 解决方案: 使用
anvil_setBalance(步骤5g)为所有唯一所有者提供资金
每个链多个所有者导致提交失败
- 原因: 交易文件针对同一链上具有不同所有者的合约
- 解决方案: 在步骤5c自动检测,并在步骤5d拆分交易
端口连接错误
- 原因: 使用示例端口而不是fork输出中的实际端口
- 解决方案: 始终从fork输出解析实际端口分配,永远不要硬编码
“http_registry: command not found”
- 原因: http_registry函数未定义
- 解决方案: 设置HYPERLANE_MONOREPO环境变量或在shell配置文件中定义函数
“Registry not found”
- 原因: 注册表不在预期位置
- 解决方案: 设置HYPERLANE_REGISTRY环境变量或克隆到$HOME/hyperlane-registry
Warp检查显示新添加链的提供者错误
- 原因: 新添加的链可能导致提供者错误
- 预期: 这对于新链添加通常是正常的
- 解决方案: 如果需要,使用
hyperlane warp read作为备用验证
关键学习
- 从不使用配置所有者进行模拟 - 始终查询实际的链上所有者
- 始终检查每个链的多个所有者 - 如果检测到,则拆分交易
- 从不硬编码端口 - 始终从fork输出提取
- 所有权违规通常是预期的 - 它们显示当前与未来状态
- 使用Heimdall解码交易 - 包含在最终报告中
- 生成全面的报告 - 包括解释 + 原始输出
- 区分功能性和所有权违规 - 只有有交易的链上的功能性违规是问题
- 为所有唯一所有者提供资金 - 不仅仅是warp路由所有者
- 两个服务器必须在后台运行 - HTTP注册表 AND warp fork
- 始终清理并切换回main - 保持环境清洁
- 在开始前验证先决条件 - 检查工具和路径是否存在
- 使用环境变量用于路径 - 使技能可在不同机器上移植
端口检测最佳实践
关键: 永远不要假设端口号。始终从实际输出提取。
# ❌ 错误 - 硬编码端口
cast call <addr> "owner()" --rpc-url http://localhost:8550
# ✅ 正确 - 使用您fork输出中的实际端口
# 首先: 从您的fork输出文件中提取端口
PORT=$(grep "Successfully started Anvil node for chain ethereum" /tmp/warp-fork.log | \
grep -oE "http://127.0.0.1:([0-9]+)" | cut -d: -f3)
cast call <addr> "owner()" --rpc-url http://localhost:$PORT
从您的fork输出构建端口映射字典,并在整个过程中引用它。
环境变量参考
| 变量 | 默认 | 目的 |
|---|---|---|
HYPERLANE_REGISTRY |
$HOME/hyperlane-registry |
注册表位置 |
HYPERLANE_MONOREPO |
(必需) | Monorepo位置用于http-registry |
REGISTRY_PATH |
$HYPERLANE_REGISTRY |
技能内部使用 |
HTTP_REGISTRY_CMD |
(自动检测) | 启动HTTP注册表的命令 |