本文介绍了OpenZeppelin Monitor的自定义脚本功能,该功能允许用户实现自定义的过滤器脚本和通知处理脚本,从而实现更精细的监控匹配和个性化的通知处理。文章详细说明了如何使用 Bash、Python 和 JavaScript 创建脚本,以及脚本的输入输出要求。此外,还提供了性能方面的考虑,例如文件描述符限制、脚本超时和资源使用情况。
OpenZeppelin Monitor 允许你实现自定义脚本,以对监控匹配项进行额外的过滤和自定义通知处理。
安全风险: 仅运行你信任并完全理解的脚本。恶意脚本可能会损害你的系统或暴露敏感数据。在执行之前,请务必检查脚本内容并验证其来源。 |
自定义过滤脚本允许你对监控检测到的匹配项应用额外的条件。 这有助于你根据特定于你的用例的标准来优化收到的警报。
Bash
Python
JavaScript
{
"args": ["--verbose"],
"monitor_match": {
"EVM": {
"matched_on": {
"events": [],
"functions": [\
{\
"expression": null,\
"signature": "transfer(address,uint256)"\
}\
],
"transactions": [\
{\
"expression": null,\
"status": "Success"\
}\
]
},
"matched_on_args": {
"events": null,
"functions": [\
{\
"args": [\
{\
"indexed": false,\
"kind": "address",\
"name": "to",\
"value": "0x94d953b148d4d7143028f397de3a65a1800f97b3"\
},\
{\
"indexed": false,\
"kind": "uint256",\
"name": "value",\
"value": "434924400"\
}\
],\
"hex_signature": "a9059cbb",\
"signature": "transfer(address,uint256)"\
}\
]
},
"monitor": {
"addresses": [\
{\
"contract_spec": null,\
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"\
}\
],
"match_conditions": {
"events": [\
{\
"expression": "value > 10000000000",\
"signature": "Transfer(address,address,uint256)"\
}\
],
"functions": [\
{\
"expression": null,\
"signature": "transfer(address,uint256)"\
}\
],
"transactions": [\
{\
"expression": null,\
"status": "Success"\
}\
]
},
"name": "Large Transfer of USDC Token",
"networks": ["ethereum_mainnet"],
"paused": false,
"trigger_conditions": [\
{\
"arguments": ["--verbose"],\
"language": "Bash",\
"script_path": "./config/filters/evm_filter_block_number.sh",\
"timeout_ms": 1000\
}\
],
"triggers": ["evm_large_transfer_usdc_script"]
},
"receipt": {
"blockHash": "0x...",
"blockNumber": "0x...",
"contractAddress": null,
"cumulativeGasUsed": "0x...",
"effectiveGasPrice": "0x...",
"from": "0x...",
"gasUsed": "0xb068",
"status": "0x1",
"to": "0x...",
"transactionHash": "0x...",
"transactionIndex": "0x1fc",
"type": "0x2"
},
"logs": [\
{\
"address": "0xd1f2586790a5bd6da1e443441df53af6ec213d83",\
"topics": [\
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\
"0x00000000000000000000000060af8cf92e5aa9ead4a592d657cd6debecfbc616",\
"0x000000000000000000000000d1f2586790a5bd6da1e443441df53af6ec213d83"\
],\
"data": "0x00000000000000000000000000000000000000000000106015728793d21f77ac",\
"blockNumber": "0x1451aca",\
"transactionHash": "0xa39d1b9b3edda74414bd6ffaf6596f8ea12cf0012fd9a930f71ed69df6ff34d0",\
"transactionIndex": "0x0",\
"blockHash": "0x9432868b7fc57e85f0435ca3047f6a76add86f804b3c1af85647520061e30f80",\
"logIndex": "0x2",\
"removed": false\
},\
],
"transaction": {
"accessList": [],
"blockHash": "0x...",
"blockNumber": "0x1506545",
"chainId": "0x1",
"from": "0x...",
"gas": "0x7a120",
"gasPrice": "0x...",
"hash": "0x...",
"maxFeePerGas": "0x...",
"maxPriorityFeePerGas": "0x...",
"nonce": "0x14779f",
"to": "0x...",
"transactionIndex": "0x...",
"type": "0x2",
"value": "0x0"
}
}
}
}
{
"args": ["--verbose"],
"monitor_match": {
"Stellar": {
"monitor": {
"name": "Large Swap By Dex",
"networks": ["stellar_mainnet"],
"paused": false,
"addresses": [\
{\
"address": "GCXYK...",\
"contract_spec": null\
}\
],
"match_conditions": {
"functions": [\
{\
"signature": "swap(Address,U32,U32,U128,U128)",\
"expression": "out_min > 1000000000"\
}\
],
"events": [],
"transactions": []
},
"trigger_conditions": [\
{\
"arguments": ["--verbose"],\
"language": "Bash",\
"script_path": "./config/filters/stellar_filter_block_number.sh",\
"timeout_ms": 1000\
}\
],
"triggers": ["stellar_large_transfer_usdc_script"]
},
"transaction": {
"status": "SUCCESS",
"txHash": "2b5a0c...",
"applicationOrder": 3,
"feeBump": false,
"envelopeXdr": "AAAAAA...",
"envelopeJson": {
"type": "ENVELOPE_TYPE_TX",
"tx": {/* transaction details */}
},
"resultXdr": "AAAAAA...",
"resultJson": {/* result details */},
"resultMetaXdr": "AAAAAA...",
"resultMetaJson": {/* metadata details */},
"diagnosticEventsXdr": ["AAAAAA..."],
"diagnosticEventsJson": [{/* event details */}],
"ledger": 123456,
"createdAt": 1679644800,
"decoded": {
"envelope": {/* decoded envelope */},
"result": {/* decoded result */},
"meta": {/* decoded metadata */}
}
},
"ledger": {
"hash": "abc1...",
"sequence": 123456,
"ledgerCloseTime": "2024-03-20T10:00:00Z",
"headerXdr": "AAAAAA...",
"headerJson": {/* header details */},
"metadataXdr": "AAAAAA...",
"metadataJSON": {/* metadata details */}
},
"matched_on": {
"functions": [\
{\
"signature": "swap(Address,U32,U32,U128,U128)",\
"expression": "out_min > 1000000000"\
}\
],
"events": [],
"transactions": []
},
"matched_on_args": {
"functions": [],
"events": null
}
}
}
}
你的脚本应打印一个布尔值,指示是否应过滤匹配项。
如果应过滤掉匹配项(不触发警报),则打印 true
。
如果应处理匹配项(触发警报),则打印 false
。
仅考虑最后打印的行进行评估。
##!/bin/bash
main() {
# Read JSON input from stdin
# 从标准输入读取 JSON 输入
input_json=$(cat)
# Parse arguments from the input JSON and initialize verbose flag
# 从输入 JSON 解析参数并初始化 verbose 标志
verbose=false
args=$(echo "$input_json" | jq -r '.args[]? // empty')
if [ ! -z "$args" ]; then
while IFS= read -r arg; do
if [ "$arg" = "--verbose" ]; then
verbose=true
echo "Verbose mode enabled"
# 启用详细模式
fi
done <<< "$args"
fi
# Extract the monitor match data from the input
# 从输入中提取监控匹配数据
monitor_data=$(echo "$input_json" | jq -r '.monitor_match')
if [ "$verbose" = true ]; then
echo "Input JSON received:"
# 收到的输入 JSON
fi
# Extract blockNumber from the EVM receipt or transaction
# 从 EVM 收据或交易中提取 blockNumber
block_number_hex=$(echo "$monitor_data" | jq -r '.EVM.transaction.blockNumber' || echo "")
# Validate that block_number_hex is not empty
# 验证 block_number_hex 是否为空
if [ -z "$block_number_hex" ]; then
echo "Invalid JSON or missing blockNumber"
# JSON 无效或缺少 blockNumber
echo "false"
exit 1
fi
# Remove 0x prefix if present and clean the string
# 如果存在,删除 0x 前缀并清理字符串
block_number_hex=$(echo "$block_number_hex" | tr -d '\n' | tr -d ' ')
block_number_hex=${block_number_hex#0x}
if [ "$verbose" = true ]; then
echo "Extracted block number (hex): $block_number_hex"
# 提取的块号(十六进制)
fi
# Convert hex to decimal with error checking
# 将十六进制转换为十进制,并进行错误检查
if ! block_number=$(printf "%d" $((16#${block_number_hex})) 2>/dev/null); then
echo "Failed to convert hex to decimal"
# 无法将十六进制转换为十进制
echo "false"
exit 1
fi
if [ "$verbose" = true ]; then
echo "Converted block number (decimal): $block_number"
# 转换后的块号(十进制)
fi
# Check if even or odd using modulo
# 使用模数检查是偶数还是奇数
is_even=$((block_number % 2))
if [ $is_even -eq 0 ]; then
echo "Block number $block_number is even"
# 块号 $block_number 是偶数
echo "Verbose mode: $verbose"
# 详细模式:$verbose
echo "true"
exit 0
else
echo "Block number $block_number is odd"
# 块号 $block_number 是奇数
echo "Verbose mode: $verbose"
# 详细模式:$verbose
echo "false"
exit 0
fi
}
## Call main function
# 调用 main 函数
main
##!/bin/bash
try {
let inputData = '';
// Read from stdin
// 从标准输入读取
process.stdin.on('data', chunk => {
inputData += chunk;
});
process.stdin.on('end', () => {
const data = JSON.parse(inputData);
const monitorMatch = data.monitor_match;
const args = data.args;
// Extract block_number
// 提取 block_number
let blockNumber = null;
if (monitorMatch.EVM) {
const hexBlock = monitorMatch.EVM.transaction?.blockNumber;
if (hexBlock) {
// Convert hex string to integer
// 将十六进制字符串转换为整数
blockNumber = parseInt(hexBlock, 16);
}
}
if (blockNumber === null) {
console.log('false');
return;
}
const result = blockNumber % 2 === 0;
console.log(`Block number ${blockNumber} is ${result ? 'even' : 'odd'}`);
# 块号 ${blockNumber} 是 ${result ? '偶数' : '奇数'}
console.log(result.toString());
});
} catch (e) {
console.log(`Error processing input: ${e}`);
# 处理输入时出错:${e}
console.log('false');
}
##!/bin/bash
import sys
import json
def main():
try:
# Read input from stdin
# 从标准输入读取输入
input_data = sys.stdin.read()
if not input_data:
print("No input JSON provided", flush=True)
# 未提供输入 JSON
return False
# Parse input JSON
# 解析输入 JSON
try:
data = json.loads(input_data)
monitor_match = data['monitor_match']
args = data['args']
except json.JSONDecodeError as e:
print(f"Invalid JSON input: {e}", flush=True)
# JSON 输入无效:{e}
return False
# Extract block_number
# 提取 block_number
block_number = None
if "EVM" in monitor_match:
hex_block = monitor_match['EVM']['transaction'].get('blockNumber')
if hex_block:
# Convert hex string to integer
# 将十六进制字符串转换为整数
block_number = int(hex_block, 16)
if block_number is None:
print("Block number is None")
# 块号为 None
return False
result = block_number % 2 == 0
print(f"Block number {block_number} is {'even' if result else 'odd'}", flush=True)
# 块号 {block_number} 是 {'偶数' if result else '奇数'}
return result
except Exception as e:
print(f"Error processing input: {e}", flush=True)
# 处理输入时出错:{e}
return False
if __name__ == "__main__":
result = main()
# Print the final boolean result
# 打印最终的布尔值结果
print(str(result).lower(), flush=True)
此示例脚本根据 EVM 交易的块号进行过滤:
对于偶数编号的块中的交易,返回 true
(过滤掉)
对于奇数编号的块中的交易,返回 false
(允许)
接受 --verbose
标志以进行详细日志记录
在examples/config/filters
目录 中浏览其他示例。
按照配置指南将你的自定义过滤脚本与监控集成。
触发条件根据它们在触发条件数组中的位置按顺序执行。 每个过滤器必须返回 false 才能包含匹配项,并且仅在成功执行后才会被考虑。 |
自定义通知脚本允许你定义在满足特定条件时如何传递警报。 这可以包括将警报发送到不同的通道或以特定方式格式化通知。
Bash
Python
JavaScript
非零退出代码表示发生错误
错误消息应写入 stderr
零退出代码表示执行成功
##!/bin/bash
main() {
# Read JSON input from stdin
# 从标准输入读取 JSON 输入
input_json=$(cat)
# Parse arguments from the input JSON and initialize verbose flag
# 从输入 JSON 解析参数并初始化 verbose 标志
verbose=false
args=$(echo "$input_json" | jq -r '.args[]? // empty')
if [ ! -z "$args" ]; then
while IFS= read -r arg; do
if [ "$arg" = "--verbose" ]; then
verbose=true
echo "Verbose mode enabled"
# 启用详细模式
fi
done <<< "$args"
fi
# Extract the monitor match data from the input
# 从输入中提取监控匹配数据
monitor_data=$(echo "$input_json" | jq -r '.monitor_match')
# Validate input
# 验证输入
if [ -z "$input_json" ]; then
echo "No input JSON provided"
# 未提供输入 JSON
exit 1
fi
# Validate JSON structure
# 验证 JSON 结构
if ! echo "$input_json" | jq . >/dev/null 2>&1; then
echo "Invalid JSON input"
# JSON 输入无效
exit 1
fi
if [ "$verbose" = true ]; then
echo "Input JSON received:"
# 收到的输入 JSON
echo "$input_json" | jq '.'
echo "Monitor match data:"
# 监控匹配数据
echo "$monitor_data" | jq '.'
fi
# Process args if they exist
# 如果参数存在,则处理参数
args_data=$(echo "$input_json" | jq -r '.args')
if [ "$args_data" != "null" ]; then
echo "Args: $args_data"
# 参数:$args_data
fi
# If we made it here, everything worked
# 如果我们到达了这里,一切正常
echo "Verbose mode: $verbose"
# 详细模式:$verbose
# return a non zero exit code and an error message
# 返回非零退出代码和错误消息
echo "Error: This is a test error" >&2
# 错误:这是一个测试错误
exit 1
}
## Call main function
# 调用 main 函数
main
##!/bin/bash
try {
let inputData = '';
// Read from stdin
// 从标准输入读取
process.stdin.on('data', chunk => {
inputData += chunk;
});
process.stdin.on('end', () => {
// Parse input JSON
# 解析输入 JSON
const data = JSON.parse(inputData);
const monitorMatch = data.monitor_match;
const args = data.args;
// Log args if they exist
# 如果参数存在,则记录参数
if (args && args.length > 0) {
console.log(`Args: ${JSON.stringify(args)}`);
# 参数:${JSON.stringify(args)}
}
// Validate monitor match data
# 验证监控匹配数据
if (!monitorMatch) {
console.log("No monitor match data provided");
# 未提供监控匹配数据
return;
}
});
} catch (e) {
console.log(`Error processing input: ${e}`);
# 处理输入时出错:${e}
}
##!/bin/bash
import sys
import json
def main():
try:
# Read input from stdin
# 从标准输入读取输入
input_data = sys.stdin.read()
if not input_data:
print("No input JSON provided", flush=True)
# 未提供输入 JSON
# Parse input JSON
# 解析输入 JSON
try:
data = json.loads(input_data)
monitor_match = data['monitor_match']
args = data['args']
if args:
print(f"Args: {args}")
# 参数:{args}
except json.JSONDecodeError as e:
print(f"Invalid JSON input: {e}", flush=True)
# JSON 输入无效:{e}
except Exception as e:
print(f"Error processing input: {e}", flush=True)
# 处理输入时出错:{e}
if __name__ == "__main__":
main()
这些示例演示了如何:
处理输入 JSON 数据
处理用于调试的 verbose 模式
通过 stderr
返回错误消息
设置适当的退出代码
在 examples/config/triggers/scripts
目录 中浏览其他示例。
按照配置指南将你的自定义通知脚本与触发器集成。
文件描述符限制: 每次脚本执行都需要用于 stdin
、stdout
和 stderr
的文件描述符
确保你的系统允许至少 2,048 个打开的文件描述符
使用 ulimit -n
检查你在基于 Unix 的系统上的当前限制
使用 ulimit -n 2048
暂时增加限制
对于永久性更改,请修改 /etc/security/limits.conf
或适用于你的系统的等效文件
脚本超时: 在你的触发条件中配置适当的超时值,以防止长时间运行的脚本阻止管道
timeout_ms
参数控制脚本在被终止之前可以运行的时间
资源使用: 复杂的脚本可能会消耗大量的 CPU 或内存资源
考虑优化脚本中资源密集型操作
在高流量期间监控系统性能
脚本重新加载: 由于脚本在启动时加载,因此对脚本文件的任何修改都需要重新启动监控才能生效
- 原文链接: docs.openzeppelin.com/mo...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!