自定义脚本 - OpenZeppelin 文档

本文介绍了OpenZeppelin Monitor的自定义脚本功能,该功能允许用户实现自定义的过滤器脚本和通知处理脚本,从而实现更精细的监控匹配和个性化的通知处理。文章详细说明了如何使用 Bash、Python 和 JavaScript 创建脚本,以及脚本的输入输出要求。此外,还提供了性能方面的考虑,例如文件描述符限制、脚本超时和资源使用情况。

自定义脚本

OpenZeppelin Monitor 允许你实现自定义脚本,以对监控匹配项进行额外的过滤和自定义通知处理。

安全风险: 仅运行你信任并完全理解的脚本。恶意脚本可能会损害你的系统或暴露敏感数据。在执行之前,请务必检查脚本内容并验证其来源。

自定义过滤脚本

自定义过滤脚本允许你对监控检测到的匹配项应用额外的条件。 这有助于你根据特定于你的用例的标准来优化收到的警报。

实施指南

  1. 使用以下受支持的语言之一创建脚本:
  • Bash

  • Python

  • JavaScript

  1. 你的脚本将收到一个具有以下结构的 JSON 对象:
  • EVM
{
    "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"
        }
      }
    }
}
  • Stellar
{
    "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

  • 仅考虑最后打印的行进行评估。

示例过滤脚本 (Bash)

##!/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

示例过滤脚本 (JavaScript)

##!/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');
}

示例过滤脚本 (Python)

##!/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 才能包含匹配项,并且仅在成功执行后才会被考虑。

自定义通知脚本

自定义通知脚本允许你定义在满足特定条件时如何传递警报。 这可以包括将警报发送到不同的通道或以特定方式格式化通知。

实施指南

  1. 使用以下受支持的语言之一创建脚本:
  • Bash

  • Python

  • JavaScript

  1. 你的脚本将收到与过滤器脚本相同的 JSON 输入格式

脚本输出要求

  • 非零退出代码表示发生错误

  • 错误消息应写入 stderr

  • 零退出代码表示执行成功

示例通知脚本 (Bash)

##!/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

示例通知脚本 (JavaScript)

##!/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}
}

示例通知脚本 (Python)

##!/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()

这些示例演示了如何:

集成

按照配置指南将你的自定义通知脚本与触发器集成。

性能注意事项

  • 文件描述符限制: 每次脚本执行都需要用于 stdinstdoutstderr 的文件描述符

  • 确保你的系统允许至少 2,048 个打开的文件描述符

  • 使用 ulimit -n 检查你在基于 Unix 的系统上的当前限制

  • 使用 ulimit -n 2048 暂时增加限制

  • 对于永久性更改,请修改 /etc/security/limits.conf 或适用于你的系统的等效文件

  • 脚本超时: 在你的触发条件中配置适当的超时值,以防止长时间运行的脚本阻止管道

  • timeout_ms 参数控制脚本在被终止之前可以运行的时间

  • 资源使用: 复杂的脚本可能会消耗大量的 CPU 或内存资源

  • 考虑优化脚本中资源密集型操作

  • 在高流量期间监控系统性能

  • 脚本重新加载: 由于脚本在启动时加载,因此对脚本文件的任何修改都需要重新启动监控才能生效

← RPC 客户端

错误处理 →

  • 原文链接: docs.openzeppelin.com/mo...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。