OpenZeppelin Monitor是一个区块链监控服务,可以实时监控链上活动,并根据配置的条件触发通知。它支持多链,具有可配置的监控计划,灵活的触发条件和可扩展的架构,可以方便地添加新的链。通过它,用户可以监视特定的事件和交易,并通过Slack、Discord、电子邮件等多种渠道发送警报。
此软件正处于 alpha 阶段。在生产环境中使用风险自负。 |
在快速发展的区块链技术世界中,有效的监控对于确保安全性和性能至关重要。OpenZeppelin 监控器 是一种区块链监控服务,它会监视特定的链上活动,并根据可配置的条件触发通知。该服务提供多链支持以及可配置的监控计划、灵活的触发条件和用于添加新链的可扩展架构。
实时监控:实时监控区块链网络中的特定事件和交易
智能过滤:使用灵活的表达式来精确定义要监控的内容
多通知支持:通过 Slack、Discord、电子邮件、Telegram、Webhooks 或自定义脚本发送警报
可配置的调度:使用 cron 表达式设置自定义监控计划
数据持久性:存储监控数据并从检查点恢复
可扩展的架构:易于添加对新区块链和通知类型的支持
EVM 兼容网络
Stellar
Slack - 将格式化的消息发送到 Slack 频道
Discord - 通过 webhooks 将警报发布到 Discord 频道
Email - 通过 SMTP 支持发送电子邮件通知
Telegram - 通过 bot API 向 Telegram 聊天发送消息
Webhooks - 将 HTTP 请求发送到自定义端点
Custom Scripts - 执行 Python、JavaScript 或 Bash 脚本
要立即开始,请参阅 快速入门。 |
Rust 2021 edition 或更高版本
Docker(可选,用于容器化部署)
git clone https://github.com/openzeppelin/openzeppelin-monitor
cd openzeppelin-monitor
cargo build --release
mv ./target/release/openzeppelin-monitor .
./openzeppelin-monitor --help
./openzeppelin-monitor --help
## 启用日志记录到文件
./openzeppelin-monitor --log-file
## 启用指标服务器
./openzeppelin-monitor --metrics
## 验证配置文件而不启动服务
./openzeppelin-monitor --check
git clone https://github.com/openzeppelin/openzeppelin-monitor
cd openzeppelin-monitor
cp .env.example .env
## 使用你的配置编辑 .env 文件
cargo make docker-compose-up
可以通过在 .env
文件中设置 METRICS_ENABLED=true
来启用指标服务器、Prometheus 和 Grafana。
你可以使用 Docker Compose 直接启动服务:
## 没有指标配置文件 (默认 METRICS_ENABLED=false)
docker compose up -d
## 启用指标
docker compose --profile metrics up -d
要在 UI 中查看 prometheus 指标,你可以在浏览器上使用 http://localhost:9090
。
要查看 grafana 仪表板,你可以在浏览器上使用 http://localhost:3000
。
默认情况下,预定义的指标会在 grafana 中填充到仪表板中。
网络配置:<network_type>_<network_name>.json
示例:ethereum_mainnet.json
、stellar_testnet.json
应与文件内的 slug
属性匹配
监控配置:<asset>_<action>_monitor.json
示例:usdc_transfer_monitor.json
、dai_liquidation_monitor.json
由监控器使用其 name
属性引用
触发器配置:<type>_<purpose>.json
示例:slack_notifications.json
、email_alerts.json
单个触发器由其配置键引用
监控器、网络和触发器名称在所有配置文件中必须是唯一的
监控器的 networks
数组必须包含来自网络配置文件的有效网络 slug
值
监控器的 triggers
数组必须包含有效的触发器配置键
示例有效引用:
// networks/ethereum_mainnet.json
{
"slug": "ethereum_mainnet",
...
}
// triggers/slack_notifications.json
{
"large_transfer_slack": {
...
}
}
// monitors/usdc_transfer_monitor.json
{
"networks": ["ethereum_mainnet"],
"triggers": ["large_transfer_slack"],
...
}
确保所有引用的 slugs 和触发器键都存在于它们各自的配置文件中。如果监控器无法解析这些引用,则会启动失败。 |
该监控器实现了跨不同组件的协议安全验证,并在检测到潜在的不安全配置时发出警告。虽然不安全协议不会被阻止,但我们强烈建议遵循这些安全指南:
####### RPC URLs
推荐使用 HTTPS:强烈建议对 RPC 端点使用 https://
推荐使用 WSS:对于 WebSocket 连接,强烈建议使用 wss://
(安全 WebSocket)
警告:使用 http://
或 ws://
会触发安全警告,因为它们会以未加密的方式传输数据
####### Webhook 通知
推荐使用 HTTPS:URLs 应使用 HTTPS 协议
推荐身份验证:包括以下任一项:
X-API-Key
标头
Authorization
标头
可选密钥:可以包含用于 HMAC 身份验证的密钥
当提供密钥时,监控器将:
生成以毫秒为单位的时间戳
创建 Payload 和时间戳的 HMAC-SHA256 签名
将签名添加到 X-Signature
标头中
将时间戳添加到 X-Timestamp
标头中
签名计算为:HMAC-SHA256(secret, payload + timestamp)
警告:非 HTTPS URLs 或缺少身份验证标头会触发安全警告
####### Slack 通知
推荐使用 HTTPS:Webhook URLs 应以 https://hooks.slack.com/
开头
警告:非 HTTPS URLs 会触发安全警告
####### Discord 通知
推荐使用 HTTPS:Webhook URLs 应以 https://discord.com/api/webhooks/
开头
警告:非 HTTPS URLs 会触发安全警告
####### Telegram 通知
协议: 使用 application/json
Payload 向 sendMessage
方法发送 POST
请求。
端点: https://api.telegram.org/bot<token>/sendMessage
安全:
需要 HTTPS: API 端点使用 HTTPS。
身份验证通过 URL 中的 Bot Token 进行处理。请确保此Token的安全。
格式: 消息以 parse_mode
设置为 MarkdownV2
发送。消息标题和正文中的特殊字符会自动转义,以防止格式错误。
####### 电子邮件通知
推荐使用安全端口: 以下端口被认为是安全的:
465:SMTPS(基于 SSL 的 SMTP)
587:带有 STARTTLS 的 SMTP
993:IMAPS(基于 SSL 的 IMAP)
警告:使用其他端口会触发安全警告
有效格式:电子邮件地址必须遵循 RFC 5322 格式
####### 通知重试策略
以下通知协议支持重试策略:
Slack
Discord
Telegram
Webhook
默认重试策略是使用指数退避,其参数如下:
参数 | 默认值 | 描述 |
max_retries |
3 |
在放弃之前进行的最大重试次数 |
base_for_backoff |
2 |
指数退避计算的基本持续时间(以秒为单位) |
initial_backoff |
250 |
初始退避持续时间(以毫秒为单位) |
max_backoff |
10 |
最大退避持续时间(以秒为单位) |
jitter |
Full |
应用于退避持续时间的抖动策略,目前支持 Full 和 None |
可以通过在触发器配置中的 retry_policy
字段中提供自定义 HttpRetryConfig
结构来覆盖这些参数。
####### 文件权限(Unix 系统)
限制写入权限:脚本文件不应具有过于宽松的写入权限
推荐权限:对脚本文件使用 644
( rw-r—r--
)
警告:模式为 022
或更宽松的文件会触发安全警告
示例设置推荐权限
chmod 644 ./config/filters/my_script.sh
该监控器实现了安全的密钥管理系统,支持多个密钥来源和自动内存归零。
监控器支持三种类型的密钥来源:
纯文本:直接密钥值(包装在 SecretString
中,用于安全内存处理)
环境变量:存储在环境变量中的密钥
Hashicorp Cloud Vault:存储在 Hashicorp Cloud Vault 中的密钥
自动归零:不再需要时,密钥会自动从内存中归零
类型安全解析:安全处理密钥解析,并进行适当的错误处理
配置支持:Serde 支持配置文件
可以在 JSON 文件中使用以下格式配置密钥:
{
"type": "Plain",
"value": "my-secret-value"
}
{
"type": "Environment",
"value": "MY_SECRET_ENV_VAR"
}
{
"type": "HashicorpCloudVault",
"value": "my-secret-name"
}
要使用 Hashicorp Cloud Vault,请配置以下环境变量:
环境变量 | 描述 |
---|---|
HCP_CLIENT_ID |
Hashicorp Cloud Vault 客户端 ID |
HCP_CLIENT_SECRET |
Hashicorp Cloud Vault 客户端密钥 |
HCP_ORG_ID |
Hashicorp Cloud Vault 组织 ID |
HCP_PROJECT_ID |
Hashicorp Cloud Vault 项目 ID |
HCP_APP_NAME |
Hashicorp Cloud Vault 应用程序名称 |
对生产密钥使用环境变量或 Vault
避免在配置文件中存储纯文本密钥
对 Vault 密钥使用适当的访问控制
监视 Vault 访问模式以查找可疑活动
复制示例环境变量文件并根据你的需要更新值
cp .env.example .env
此表列出了环境变量及其默认值。
环境变量 | 默认值 | 接受的值 | 描述 |
---|---|---|---|
RUST_LOG |
info |
info, debug, warn, error, trace |
日志级别。 |
LOG_MODE |
stdout |
stdout, file |
将日志写入控制台或文件。 |
LOG_DATA_DIR |
logs/ |
<any file path> |
在主机上写入日志文件的目录。 |
MONITOR_DATA_DIR |
null |
<any file path> |
在容器重启之间保留监控数据。 |
LOG_MAX_SIZE |
1073741824 |
<size in bytes or human-readable format (e.g., "1GB", "500MB")> |
日志需要回滚的大小。接受原始字节(例如,“1073741824”)或人类可读的格式(例如,“1GB”、“500MB”)。 |
METRICS_ENABLED |
false |
true , false |
启用指标服务器以供外部工具抓取指标。 |
METRICS_PORT |
8081 |
<any tcp port (preferably choose non-privileged ports i.e. (1024-65535))> |
用于指标服务器的端口。 |
HCP_CLIENT_ID |
- | <string> |
用于密钥管理的 Hashicorp Cloud Vault 客户端 ID。 |
HCP_CLIENT_SECRET |
- | <string> |
用于密钥管理的 Hashicorp Cloud Vault 客户端密钥。 |
HCP_ORG_ID |
- | <string> |
用于密钥管理的 Hashicorp Cloud Vault 组织 ID。 |
HCP_PROJECT_ID |
- | <string> |
用于密钥管理的 Hashicorp Cloud Vault 项目 ID。 |
HCP_APP_NAME |
- | <string> |
用于密钥管理的 Hashicorp Cloud Vault 应用程序名称。 |
## EVM 配置
cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json
cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json
## Stellar 配置
cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json
cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json
## 通知配置
cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json
cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json
## 筛选器配置
cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh
cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh
该监控器支持多个命令行选项,用于配置和控制:
选项 | 默认值 | 描述 |
---|---|---|
--log-file |
false |
将日志写入文件而不是 stdout |
--log-level |
info |
设置日志级别(跟踪、调试、信息、警告、错误) |
--log-path |
logs/ |
存储日志文件的路径 |
--log-max-size |
1GB |
回滚之前的最大日志文件大小 |
--metrics-address |
127.0.0.1:8081 |
启动指标服务器的地址 |
--metrics |
false |
启用指标服务器 |
--monitor-path |
- | 要执行的监控器的路径(用于测试) |
--network |
- | 要执行监控器的网络(用于测试) |
--block |
- | 要执行监控器的区块号(用于测试) |
--check |
false |
验证配置文件而不启动服务 |
监控器默认使用基于文件的存储。
当在网络配置中启用 store_blocks
时,监控器会存储:
已处理的区块:./data/<network_slug>_blocks_<timestamp>.json
遗漏的区块:./data/<network_slug>_missed_blocks.txt
(用于存储遗漏的区块)
missed_blocks.txt
文件的内容可能有助于根据网络的区块时间和监控器的 cron 计划确定正确的 max_past_blocks
值。
此外,监控器将始终存储:
./data/<network_slug>_last_block.txt
(启用从上次检查点恢复)网络配置定义了特定区块链网络的连接详细信息和操作参数,支持基于 EVM 和 Stellar 的链。
示例网络配置
{
"network_type": "Stellar",
"slug": "stellar_mainnet",
"name": "Stellar Mainnet",
"rpc_urls": [\
{\
"type_": "rpc",\
"url": {\
"type": "plain",\
"value": "https://soroban.stellar.org"\
},\
"weight": 100\
}\
],
"network_passphrase": "Public Global Stellar Network ; September 2015",
"block_time_ms": 5000,
"confirmation_blocks": 2,
"cron_schedule": "0 */1 * * * *",
"max_past_blocks": 20,
"store_blocks": true
}
字段 | 类型 | 描述 |
---|---|---|
network_type |
String |
区块链类型("EVM" 或 "Stellar") |
slug |
String |
必需 - 网络的加粗唯一标识符 |
name |
String |
必需 - 网络的加粗唯一人类可读名称 |
rpc_urls |
Array[Object] |
具有用于负载平衡的权重的 RPC 端点列表 |
chain_id |
Number |
网络链 ID(仅限 EVM) |
network_passphrase |
String |
网络标识符(仅限 Stellar) |
block_time_ms |
Number |
平均区块时间(以毫秒为单位) |
confirmation_blocks |
Number |
等待确认的区块数 |
cron_schedule |
String |
cron 格式的监控器调度 |
max_past_blocks |
Number |
要处理的最大过去区块数 |
store_blocks |
Boolean |
是否存储已处理的区块(默认输出到 ./data/ 目录) |
触发器定义了在满足监控条件时要执行的操作。触发器可以发送通知、发出 HTTP 请求或执行脚本。
示例触发器配置
{
"evm_large_transfer_usdc_slack": {
"name": "Large Transfer Slack Notification",
"trigger_type": "slack",
"config": {
"slack_url": {
"type": "plain",
"value": "https://hooks.slack.com/services/A/B/C"
},
"message": {
"title": "large_transfer_slack triggered",
"body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog"
}
}
},
"stellar_large_transfer_usdc_slack": {
"name": "Large Transfer Slack Notification",
"trigger_type": "slack",
"config": {
"slack_url": {
"type": "environment",
"value": "SLACK_WEBHOOK_URL"
},
"message": {
"title": "large_transfer_usdc_slack triggered",
"body": "${monitor.name} triggered because of a large transfer of ${functions.0.args.amount} USDC to ${functions.0.args.to} | https://stellar.expert/explorer/testnet/tx/${transaction.hash}"
}
}
}
}
{
"slack_url": {
"type": "HashicorpCloudVault",
"value": "slack-webhook-url"
},
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
字段 | 类型 | 描述 |
---|---|---|
name |
String |
必需 - 通知的加粗唯一人类可读名称 |
trigger_type |
String |
对于 Slack 通知,必须为 "slack" |
config.slack_url.type |
String |
密钥类型("Plain"、"Environment" 或 "HashicorpCloudVault") |
config.slack_url.value |
String |
密钥值(URL、环境变量名称或 Vault 密钥名称) |
config.message.title |
String |
出现在 Slack 消息中的标题 |
config.message.body |
String |
带有变量替换的消息模板 |
{
"host": "smtp.gmail.com",
"port": 465,
"username": {
"type": "plain",
"value": "sender@example.com"
},
"password": {
"type": "environment",
"value": "SMTP_PASSWORD"
},
"message": {
"title": "Alert Subject",
"body": "Alert message for ${transaction.hash}",
},
"sender": "sender@example.com",
"recipients": ["recipient@example.com"]
}
字段 | 类型 | 描述 |
---|---|---|
name |
String |
必需 - 通知的加粗唯一人类可读名称 |
trigger_type |
String |
对于电子邮件通知,必须为 "email" |
config.host |
String |
SMTP 服务器主机名 |
config.port |
Number |
SMTP 端口(默认为 465) |
config.username.type |
String |
密钥类型("Plain"、"Environment" 或 "HashicorpCloudVault") |
config.username.value |
String |
密钥值(用户名、环境变量名称或 Vault 密钥名称) |
config.password.type |
String |
密钥类型("Plain"、"Environment" 或 "HashicorpCloudVault") |
config.password.value |
String |
密钥值(密码、环境变量名称或 Vault 密钥名称) |
config.message.title |
String |
电子邮件主题行 |
config.message.body |
String |
带有变量替换的电子邮件正文模板 |
config.sender |
String |
发件人电子邮件地址 |
config.recipients |
Array[String] |
收件人电子邮件地址列表 |
{
"url": {
"type": "HashicorpCloudVault",
"value": "webhook-url"
},
"method": "POST",
"secret": {
"type": "environment",
"value": "WEBHOOK_SECRET"
},
"headers": {
"Content-Type": "application/json"
},
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
字段 | 类型 | 描述 |
---|---|---|
name |
String |
必需 - 通知的加粗唯一人类可读名称 |
trigger_type |
String |
对于 webhook 通知,必须为 "webhook" |
config.url.type |
String |
密钥类型("Plain"、"Environment" 或 "HashicorpCloudVault") |
config.url.value |
String |
密钥值(URL、环境变量名称或 Vault 密钥名称) |
config.method |
String |
HTTP 方法(POST、GET 等),默认为 POST |
config.secret.type |
String |
密钥类型("Plain"、"Environment" 或 "HashicorpCloudVault") |
config.secret.value |
String |
密钥值(HMAC 密钥、环境变量名称或 Vault 密钥名称) |
config.headers |
Object |
要包含在 webhook 请求中的标头 |
config.message.title |
String |
出现在 webhook 消息中的标题 |
config.message.body |
String |
带有变量替换的消息模板 |
{
"discord_url": {
"type": "plain",
"value": "https://discord.com/api/webhooks/123-456-789"
},
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
字段 | 类型 | 描述 |
---|---|---|
name |
String |
必需 - 通知的加粗唯一人类可读名称 |
trigger_type |
String |
对于 Discord 通知,必须为 "discord" |
config.discord_url.type |
String |
密钥类型("Plain"、"Environment" 或 "HashicorpCloudVault") |
config.discord_url.value |
String |
密钥值(URL、环境变量名称或 Vault 密钥名称) |
config.message.title |
String |
出现在 Discord 消息中的标题 |
config.message.body |
String |
带有变量替换的消息模板 |
{
"token": {
"type": "HashicorpCloudVault",
"value": "telegram-bot-token"
},
"chat_id": "9876543210",
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
字段 | 类型 | 描述 |
---|---|---|
name |
String |
必需 - 通知的加粗唯一人类可读名称 |
trigger_type |
String |
对于 Telegram 通知,必须为 "telegram" |
config.token.type |
String |
密钥类型("Plain"、"Environment" 或 "HashicorpCloudVault") |
config.token.value |
String |
密钥值(bot Token、环境变量名称或 Vault 密钥名称) |
config.chat_id |
String |
Telegram 聊天 ID |
config.disable_web_preview |
Boolean |
是否禁用 Telegram 消息中的 Web 预览(默认为 false) |
config.message.title |
String |
出现在 Telegram 消息中的标题 |
config.message.body |
String |
带有变量替换的消息模板 |
{
"language": "Bash",
"script_path": "./config/triggers/scripts/custom_notification.sh",
"arguments": ["--verbose"],
"timeout_ms": 1000
}
字段 | 类型 | 描述 |
---|---|---|
name |
String |
必需 - 通知的加粗唯一人类可读名称 |
trigger_type |
String |
对于自定义脚本通知,必须为 "script" |
language |
String |
脚本的语言 |
script_path |
String |
脚本的路径 |
arguments |
Array[String] |
脚本的参数(可选)。 |
timeout_ms |
Number |
脚本超时对于避免执行期间的无限循环非常重要。如果脚本花费的时间超过超时时间,它将被终止。 |
有关自定义脚本的更多信息,请参阅自定义脚本部分。
安全风险:仅运行你信任并完全理解的脚本。恶意脚本可能会损害你的系统或暴露敏感数据。在执行之前,请务必查看脚本内容并验证其来源。 |
该监控器使用结构化的 JSON 格式,其中嵌套对象用于模板变量。数据被展平为 dot 表示法以用于模板。
变量 | 描述 |
---|---|
monitor.name |
触发的监控器的名称 |
transaction.hash |
事务的哈希 |
functions.[index].signature |
函数签名 |
events.[index].signature |
事件签名 |
####### EVM 变量
变量 | 描述 |
---|---|
transaction.from |
发件人地址 |
transaction.to |
收件人地址 |
transaction.value |
交易金额 |
events.[index].args.[param] |
按名称的事件参数 |
functions.[index].args.[param] |
按名称的函数参数 |
####### Stellar 变量
变量 | 描述 |
---|---|
events.[index].args.[position] |
按位置的事件参数 |
functions.[index].args.[param] |
按名称的函数参数 |
事务相关变量(transaction.from 、transaction.to 、transaction.value )不适用于 Stellar 网络。 |
Slack、Discord、Telegram、电子邮件和 Webhook 支持在其消息正文中使用 Markdown 格式。你可以使用 Markdown 语法来增强你的通知。
{
"email_notification": {
"name": "Formatted Alert",
"trigger_type": "email",
"config": {
"host": "smtp.example.com",
"port": 465,
"username": {"type": "plain", "value": "alerts@example.com"},
"password": {"type": "plain", "value": "password"},
"message": {
"title": "**High Value Transfer Alert**",
"body": "### Transaction Details\n\n* **Amount:** ${events.0.args.value} USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n> Transaction Hash: ${transaction.hash}\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})"
},
"sender": "alerts@example.com",
"recipients": ["recipient@example.com"]
}
}
}
{
"slack_notification": {
"name": "Formatted Alert",
"trigger_type": "slack",
"config": {
"slack_url": {"type": "plain", "value": "https://hooks.slack.com/services/XXX/YYY/ZZZ"},
"message": {
"title": "*🚨 High Value Transfer Alert*",
"body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n>Transaction Hash: `${transaction.hash}`\n\n<https://etherscan.io/tx/${transaction.hash}|View on Explorer>"
}
}
}
}
{
"discord_notification": {
"name": "Formatted Alert",
"trigger_type": "discord",
"config": {
"discord_url": {"type": "plain", "value": "https://discord.com/api/webhooks/XXX/YYY"},
"message": {
"title": "**🚨 High Value Transfer Alert**",
"body": "# Transaction Details\n\n* **Amount:** `${events.0.args.value}` USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n>>> Transaction Hash: `${transaction.hash}`\n\n**[View on Explorer](https://etherscan.io/tx/${transaction.hash})"
}
}
}
}
{
"telegram_notification": {
"name": "Formatted Alert",
"trigger_type": "telegram",
"config": {
"token": {"type": "plain", "value": "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ"},
"chat_id": "9876543210",
"message": {
"title": "*🚨 High Value Transfer Alert*",
"body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n`Transaction Hash: ${transaction.hash}`\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})"
}
}
匹配交易属性。可用字段和表达式语法取决于网络类型 (EVM/Stellar)
```json hljs
{
"transactions": [
{
"status": "Success", // 仅匹配成功的交易
"expression": "value > 1500000000000000000" // 匹配 value 大于 1.5 ETH 的交易
}
]
}
字段 | 类型 | 描述 |
---|---|---|
value |
uint256 |
交易 value,单位为 wei |
from |
address |
发送者地址(不区分大小写比较) |
to |
address |
接收者地址(不区分大小写比较) |
hash |
string |
交易哈希 |
gas_price |
uint256 |
Gas 价格,单位为 wei (传统交易) |
max_fee_per_gas |
uint256 |
EIP-1559 最大 gas 费用 |
max_priority_fee_per_gas |
uint256 |
EIP-1559 优先级费用 |
gas_limit |
uint256 |
交易的 Gas 限制 |
nonce |
uint256 |
发送者 nonce |
input |
string |
十六进制编码的输入数据 (例如,"0xa9059cbb…") |
gas_used |
uint256 |
实际使用的 gas (来自回执) |
transaction_index |
uint64 |
在区块中的位置 |
字段 | 类型 | 描述 |
---|---|---|
hash |
string |
交易哈希 |
ledger |
i64 |
包含该交易的账本序列号 |
value |
i64 |
与第一个相关操作相关联的 value(例如,支付金额)。如果未找到相关操作或 value,则默认为 0。 |
from |
address |
第一个相关操作的源账户地址(例如,付款发送者)。不区分大小写比较。 |
to |
address |
第一个相关操作的目标账户地址(例如,付款接收者或调用的合约)。不区分大小写比较。 |
如果未指定任何条件,则匹配所有交易
对于多种条件类型:
首先检查交易条件
然后,函数或事件条件必须匹配
如果同时指定了交易和(函数或事件),则必须同时匹配
表达式允许对函数参数、事件参数和交易字段进行条件检查。
支持的参数/字段类型和基本操作:
类型 | 描述 | 示例运算符 | 注释 |
---|---|---|---|
Numeric (uint/int variants) |
整数值(例如,42 ,-100 )或小数值(例如,3.14 ,-0.5 )。 |
> , >= , < , ⇐ , == , != |
如果存在小数点,数字必须在小数点前后都有数字(例如,.5 或 5. 不是有效的独立数字)。 |
Address |
区块链地址。 | == , != |
比较(例如,from == '0xABC…' )通常在地址值本身的十六进制字符方面不区分大小写。 |
String |
文本值。可以是单引号(例如,'hello' ),或者在比较的右侧,不带引号(例如,active )。 |
== , != , starts_with , ends_with , contains |
带引号的字符串支持 \' 来转义单引号,\\ 来转义反斜杠。所有字符串比较操作(例如,name == 'Alice' ,description contains 'error' )在评估期间都以不区分大小写的方式执行。有关更多示例和详细信息,请参阅专门的“字符串操作”部分。 |
Boolean |
True 或 false 值。 | == , != |
表示为 true 或 false 。这些关键字的解析不区分大小写(例如,TRUE ,False 在表达式中也有效)。 |
Hex String Literal |
以 0x 或 0X 开头,后跟十六进制字符(0-9,a-f,A-F)的字符串字面量。 |
== , != , starts_with , ends_with , contains |
被视为用于比较的字符串(例如,input_data starts_with '0xa9059cbb' )。对于 0x 之后的十六进制字符,比较区分大小写。 |
Array (EVM/Stellar) |
项目的有序列表。对于 Stellar,通常是配置中的 JSON 字符串(例如,'["a", {"id":1}]' )。对于 EVM,通常从 ABI 参数解码。 |
contains , == , != , [index] |
详细操作,包括索引访问和 contains 的行为,因网络而异。请参阅下面的“复杂类型的操作”。 |
Object/Map (Stellar) |
键值对,通常表示为配置中的 JSON 字符串(例如,'{"key": "value", "id": 123}' )。 |
.key_access , == , != |
支持用于字段访问的点表示法(例如,data.id )。有关详细信息,请参阅“复杂类型的操作”。 |
Vec (Stellar) |
有序列表,其中参数的 value 可以是 CSV 字符串(例如,"foo,bar" )或 JSON 数组字符串(例如,'["foo","bar"]' )。 |
contains , == , != |
contains 和 == 的行为根据 value 是 CSV 还是 JSON 数组字符串而有所不同。有关详细信息,请参阅“复杂类型的操作”。 |
逻辑运算符:
AND - 所有条件都必须为真
OR - 至少一个条件必须为真
() - 用于分组的括号
AND 的优先级高于 OR(即,如果未用括号分组,则 AND 运算在 OR 运算之前计算)
变量命名和访问(条件的左侧):
条件的左侧 (LHS) 指定要评估其 value 的数据字段或参数。
基本名称:
这些是参数或字段的直接名称,例如 amount
、from
、status
或事件参数索引,如 0
、1
(在 Stellar 事件中很常见)。
基本名称可以包含字母数字字符(a-z、A-Z、0-9)和下划线(_
)。
它们可以以字母、下划线或数字开头。以数字开头主要与数字索引的参数相关(例如,Stellar 事件参数)。
重要提示: 变量名称在评估期间区分大小写。表达式中使用的名称必须与源数据(例如,来自 ABI 或区块链数据结构)中字段名称的大小写完全匹配。例如,如果在数据中将字段命名为 TotalValue
,则使用 totalvalue
的表达式将找不到它。
变量名称不能是关键字(例如,true
、AND
、OR
、contains
)。关键字本身的解析不区分大小写。
路径访问器(对于复杂类型):
如果基本参数是复杂类型,如对象、映射或数组,则可以使用访问器访问其内部数据:
键访问: 使用点表示法 (.
) 访问对象或映射的属性。
示例:transaction.value
,user.name
,data.0
(如果 0
是作为字符串的有效键名)。
键通常由字母数字字符和下划线组成。它们通常以字母或下划线开头,但也支持纯数字键(例如,.0
,.123
)用于键可能是表示数字的字符串的类映射结构。
键不能包含连字符 (-
)。
索引访问: 使用括号表示法 ([]
) 按其从零开始的整数索引访问数组的元素。
示例:my_array[0]
,log_entries[3]
。
索引必须是非负整数。
组合访问: 可以组合键和索引访问器来导航嵌套结构。
示例:event.data_array[0].property
(访问 data_array
中第一个对象的 property
字段,该字段是 event
的一部分)。
示例:map.numeric_key_as_string_0[1].name
(访问存储在 map
中键 0
下的数组的第二个元素的 name
属性)。
字符串操作:
有几个运算符可用于匹配模式和比较字符串值。这些对于 EVM 交易 input
数据,使用 kind: "string"
定义的 Stellar 参数或包含文本的任何其他字段特别有用。
string_param starts_with 'prefix'
::
检查字符串参数的 value 是否以指定的 prefix
开头。
示例:transaction.input starts_with '0xa9059cbb'
(检查 ERC20 转账函数选择器)。
string_param ends_with 'suffix'
::
检查字符串参数的 value 是否以指定的 suffix
结尾。
示例:file_name ends_with '.txt'
string_param contains 'substring'
::
检查字符串参数的 value 是否在其中任何位置包含指定的 substring
。
示例:message contains 'error'
string_param == 'exact_string'
::
检查字符串参数的 value 是否与 exact_string
完全相等。
string_param != 'different_string'
::
检查字符串参数的 value 是否与 different_string
不相等。
字符串操作的重要说明:
运算符关键字: 运算符关键字本身(starts_with
、ends_with
、contains
、AND
、OR
、true
、false
、比较符号如 ==
、>
)的解析不区分大小写。例如,CONTAINS
与 contains
的处理方式相同,TRUE
与 true
相同。
字符串比较的不区分大小写评估: 在将字符串数据(例如,来自事件参数、交易字段或函数参数)与表达式中的字面量字符串值进行比较时,所有标准字符串操作在评估期间都执行不区分大小写的比较。
相等 (==
) 和不等 (!=
)
模式匹配 (starts_with
,ends_with
,contains
)
变量名称区分大小写: 重要的是将此与变量名称(条件的左侧,例如,status
)区分开来。变量名称区分大小写,并且必须与源数据(ABI 等)中的字段名称完全匹配。
空白处理: 通常允许在运算符、括号和关键字周围使用灵活的空白以提高可读性。但是,带引号的字符串字面量中的空格很重要并且会被保留。
除了简单的原始类型之外,表达式还可以与更复杂的数据结构(如数组、对象和向量)交互。
数组操作 (kind: "array"
)
当 EVM 参数是一个数组(通常在内部表示或配置为 kind: "array"
,如果手动配置,则其 value 是 JSON 字符串表示形式)时,支持以下操作:
array_param contains 'value'
检查字符串 'value'
是否存在于数组中。
array_param == '["raw_json_array_string"]'
将数组的整个 JSON 字符串表示形式与提供的字符串进行字符串比较
array_param != '["raw_json_array_string"]'
上述操作的否定
array_param[0]
索引访问
对象 (kind: "object"
) / 映射 (kind: "Map"
) 操作
object_param.key == 'value'
检查对象或映射是否具有名为 key
的键,其 value 为 'value'
。
object_param.nested_key.another_key > 100
检查嵌套键 nested_key
中的 another_key
的 value 是否大于 100。
object_param == '{"raw_json_object_string"}'
检查对象或映射是否与提供的 JSON 字符串表示形式匹配。
object_param != '{"raw_json_object_string"}'
上述操作的否定
数组操作 (kind: "array"
)
array_param[index]
访问数组中指定 index
处的元素。
array_param[0] == 'value'
检查数组中的第一个元素是否等于 'value'
。
array_param[1].property == 'value'
检查数组中的第二个元素是否具有名为 property
的属性,其 value 为 'value'
。
array_param contains 'value'
检查数组是否包含字符串 'value'
。
array_param == '["raw_json_array_string"]'
检查数组是否与提供的 JSON 字符串表示形式匹配。
array_param != '["raw_json_array_string"]'
上述操作的否定
向量操作 (kind: "vec"
)
当 Stellar 参数具有 kind: "vec"
时,其 value 可以是 CSV 字符串或 JSON 数组字符串。
vec_param contains 'item'
检查向量是否包含字符串 'item'
。这适用于 CSV 和 JSON 数组字符串。
vec_param == 'raw_string_value'
检查向量是否与提供的原始字符串 value 匹配。这适用于 CSV 和 JSON 数组字符串。
vec_param != 'raw_string_value'
上述操作的否定
事件参数访问 (Stellar)
Stellar 事件参数通常通过其数字索引作为基本变量名称来访问(例如,0
、1
、2
)。如果索引事件参数本身是复杂类型(例如数组或映射,表示为 JSON 字符串),则可以应用相应的访问方法:
如果事件参数 0
(kind: "Map") 是 '{"id": 123, "name": "Test"}'
:
0.id == 123
0.name contains 'est'
(不区分大小写)
如果事件参数 1
(kind: "array") 是 '["alpha", {"val": "beta"}]'
:
1[0] == 'ALPHA'
(不区分大小写)
1[1].val == 'Beta'
(不区分大小写)
1 contains 'beta'
(不区分大小写的深度搜索)
这些示例假定常见的 EVM 事件参数或交易字段。
基本比较
// Numeric
"transaction.value > 1000000000000000000" // Value 大于 1 ETH
"event.amount <= 500"
"block.number == 12345678"
// String (case-insensitive evaluation for '==' and 'contains')
"transaction.to == '0xdeadbeef...'" // 地址检查(地址 value 比较本身不区分大小写)
"event.token_name == 'mytoken'"
"transaction.input contains 'a9059cbb'" // 检查 ERC20 转账选择器
// Boolean
"receipt.status == true" // 或者如果可以直接评估布尔字段,则简单地 "receipt.status"
"event.isFinalized == false"
逻辑运算符
"transaction.value > 1000 AND event.type == 'Deposit'"
"(receipt.status == true OR event.fallback_triggered == true) AND user.is_whitelisted == false"
字符串操作
"transaction.input starts_with '0xa9059cbb'" // 操作不区分大小写
"event.message ends_with 'failed'"
"event.details contains 'critical alert'"
数组操作
假定 event.ids
是 [10, 20, 30]
,event.participants
是 [{"user": "Alice", "role": "admin"}, {"user": "Bob", "role": "editor"}]
。
"event.ids[0] == 10"
"event.ids contains '20'" // 检查字符串 '20'(不区分大小写)
"event.participants contains 'Alice'" // True(深度搜索,不区分大小写)
"event.participants contains 'editor'" // True(深度搜索,不区分大小写)
"event.participants == '[{\"user\": \"Alice\", \"role\": \"admin\"}, {\"user\": \"Bob\", \"role\": \"editor\"}]'" // 原始 JSON 匹配(结构和键区分大小写)
基本比较
// Numeric
"event.params.amount > 10000000" // 访问对象 'params' 中的 'amount' 字段
"ledger.sequence >= 123456"
// String (case-insensitive evaluation for '==' and 'contains')
"event.params.recipient == 'GBD22...'" // 地址检查
"event.type == 'payment_processed'"
// Boolean
"transaction.successful == true"
"event.data.is_verified == false"
逻辑运算符
"event.data.value > 500 AND event.source_account == 'GCA7Z...'"
"(event.type == 'TRANSFER' OR event.type == 'PAYMENT') AND event.params.asset_code == 'XLM'"
字符串操作
"event.contract_id starts_with 'CA23...'"
"event.memo ends_with '_TEST'"
"event.params.description contains 'urgent'"
对象 (kind: "object"
) / 映射 (kind: "Map"
) 操作
假定 event.details
(kind: "Map") 是 '{"id": 123, "user": {"name": "CHarlie", "status": "Active"}, "tags": ["new"]}'
。
"event.details.id == 123"
"event.details.user.name == 'charlie'" // 不区分大小写的字符串比较
"event.details.user.status contains 'act'" // 不区分大小写的 contains
"event.details.tags == '[\"new\"]'" // 'tags' 字段的原始 JSON 字符串匹配
数组操作 (kind: "array"
)
假定 event.items
(kind: "array") 是 '[{"sku": "A1", "qty": 10}, {"sku": "B2", "qty": 5, "notes":"Rush order"}]'
。
"event.items[0].sku == 'a1'"
"event.items[1].qty < 10"
"event.items contains 'A1'" // 深度搜索(不区分大小写)
"event.items contains 'rush order'" // 深度搜索(不区分大小写)
向量操作 (kind: "vec"
)
假定 csv_data
(kind: "vec") 是 "ALPHA,Bravo,Charlie"
,json_array_data
(kind: "vec") 是 '["Delta", {"id": "ECHO"}, "Foxtrot"]'
。
"csv_data contains 'bravo'" // 不区分大小写的 CSV 元素匹配
"csv_data == 'ALPHA,Bravo,Charlie'" // 原始字符串匹配
"json_array_data contains 'delta'" // 不区分大小写的深度搜索(如数组)
"json_array_data contains 'ECHO'" // 不区分大小写的深度搜索(如数组)
事件参数访问(数字索引)
假定事件参数 0
是 12345
(u64),1
(kind: "array") 是 '["Val1", "VAL2"]'
,2
(kind: "Map") 是 '{"keyA": "dataX", "keyB": 789}'
。
"0 > 10000"
"1[0] == 'val1'"
"1 contains 'val2'"
"2.keyA == 'DATAX'"
"2.keyB < 1000"
通过 SEP-48 支持,Stellar 函数现在可以通过名称(例如,amount > 1000 )而不是位置(例如,2 > 1000 )引用参数。事件仍然使用索引参数,直到为事件添加 SEP-48 支持为止。<br>你可以通过 Stellar 合约浏览器工具找到合约规范。例如:<br>Stellar DEX 合约接口 |
自定义过滤器允许你为处理监视器匹配项创建复杂的过滤逻辑。这些过滤器充当额外的验证层,确定匹配项是否应触发触发器的执行。
有关自定义脚本的更多信息,请参阅自定义脚本部分。
安全风险:仅运行你信任并完全理解的脚本。恶意脚本可能会损害你的系统或暴露敏感数据。在执行之前,请务必查看脚本内容并验证其来源。 |
触发器条件配置示例
{
"script_path": "./config/filters/evm_filter_block_number.sh",
"language": "Bash",
"arguments": ["--verbose"],
"timeout_ms": 1000
}
字段 | 类型 | 描述 |
---|---|---|
script_path |
String | 脚本的路径 |
language |
String | 脚本的语言 |
arguments |
Array[String] | 脚本的参数(可选)。 |
timeout_ms |
Number | 脚本的超时非常重要,可以避免执行期间的无限循环。如果脚本花费的时间超过超时时间,它将被终止,并且默认情况下将包含该匹配项。 |
监视器中的网络 slug 必须与有效的网络配置匹配。
触发器 ID 必须与配置的触发器匹配。
EVM 和 Stellar 网络之间的表达式语法和可用变量不同。
可以通过两种方式提供 ABI:
对于 EVM 网络:通过使用标准 Ethereum ABI 格式的监视器配置
对于 Stellar 网络:通过使用 SEP-48 格式的监视器配置,或者如果未提供,则自动从链中获取
监视频率由网络的 cron_schedule
控制。
每个监视器可以同时监视多个网络和地址。
可以暂停监视器,而无需删除其配置。
./openzeppelin-monitor
./openzeppelin-monitor --log-file
./openzeppelin-monitor --metrics
./openzeppelin-monitor --check
cargo make docker-compose-up
## 在 .env 文件中设置 METRICS_ENABLED=true,然后:
docker compose --profile metrics up -d
docker compose logs -f monitor
cargo make docker-compose-down
选项 | 默认值 | 描述 |
---|---|---|
--log-file |
false |
将日志写入文件而不是 stdout |
--log-level |
info |
设置日志级别(trace、debug、info、warn、error) |
--metrics |
false |
在端口 8081 上启用指标服务器 |
--check |
false |
仅验证配置文件 |
--help |
- | 显示所有可用选项 |
validate_network_config.sh
脚本有助于确保你的网络配置已正确设置并可运行。脚本:
测试所有配置的 RPC 端点的运行状况
使用特定于网络的方法验证连接
为每个端点提供清晰的可视反馈
## 测试默认网络目录 (/config/networks/)
./scripts/validate_network_config.sh
## 测试特定配置目录
./scripts/validate_network_config.sh -f /path/to/configs
在设置新网络、部署配置更改之前或在对连接问题进行故障排除时运行此脚本。 |
在启动监视器服务之前,可以使用 --check
选项验证配置文件:
./openzeppelin-monitor --check
此命令将:
解析和验证所有配置文件
检查语法错误
验证监视器、网络和触发器之间的引用
报告任何问题而不启动服务
建议在对任何配置文件进行更改后运行此检查。
可以在两种模式下测试监视器:
此模式处理所有已配置网络上的最新区块。
./openzeppelin-monitor --monitor-path="config/monitors/evm_transfer_usdc.json"
它会执行以下操作:
运行“USDC 代币的大额转账”监视器
以配置中指定的所有网络为目标
仅处理每个网络的最新区块
为找到的每个匹配项向所有关联的通道发送通知
此模式允许你分析特定网络上的特定区块,这对于调试特定交易、验证已知事件的监视器行为以及测试历史数据的监视器性能非常有用。
./openzeppelin-monitor \
--monitor-path="config/monitors/evm_transfer_usdc.json" \
--network=ethereum_mainnet \
--block=12345678
它会执行以下操作:
运行“USDC 代币的大额转账”监视器
仅以指定网络 ( ethereum_mainnet
) 为目标
仅处理指定的区块 ( 12345678
)
为找到的每个匹配项向所有关联的通道发送通知
特定区块模式需要两个参数:<br>- --network :要分析的网络<br> <br>- --block :要处理的区块号 |
将 LOG_MODE
设置为文件会将日志数据持久保存在主机上的 logs/
中。要将其更改为其他目录,请使用 LOG_DATA_DIR
。
将 MONITOR_DATA_DIR
设置为你的主机系统上的特定目录,该目录将在容器重新启动之间持久保存数据。
监视器实现了一个具有丰富上下文和跟踪功能的全面错误处理系统。有关错误处理的详细信息,请参阅错误处理指南。
监视器性能取决于网络拥塞和 RPC 端点可靠性。
查看监视器发出的RPC 调用列表。
max_past_blocks
配置至关重要:
计算方式为:(cron_interval_ms/block_time_ms) + confirmation_blocks + 1
(如果未指定,则默认为此计算)。
1 分钟以太坊 cron 的示例:(60000/12000) + 12 + 1 = 18 个区块
。
过低的设置可能会导致错过区块。
触发器条件根据它们在触发器条件数组中的位置依次执行。正确的执行还取决于系统上可用的文件描述符的数量。为确保最佳性能,建议将打开文件描述符的限制增加到至少 2048 或更高。在基于 Unix 的系统上,你可以通过运行 ulimit -n
检查当前限制,并通过 ulimit -n 2048
暂时 增加它。
由于脚本在启动时加载,因此对脚本文件的任何修改都需要重新启动监视器才能生效。
请参阅有关自定义脚本的性能注意事项此处。
模板变量是依赖于上下文的:
事件触发的通知仅填充事件变量。
函数触发的通知仅填充函数变量。
混合上下文会导致空值。
自定义脚本通知有其他注意事项:
脚本接收监视器匹配数据和参数作为 JSON 输入
脚本必须在其配置的 timeout_ms 内完成,否则它们将被终止
脚本修改需要重新启动监视器才能生效
支持的语言仅限于 Python、JavaScript 和 Bash
如需支持或咨询,请在 Telegram 上联系我们。
有功能请求或想贡献?加入我们在 GitHub 上的社区
本项目根据 GNU Affero General Public License v3.0 获得许可 - 有关详细信息,请参阅 LICENSE 文件。
有关安全问题,请参阅我们的 安全策略。
- 原文链接: docs.openzeppelin.com/mo...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!