教程区块链区块链技术ch1212.2 事件、日志与链下监听

本页目录

智能合约的函数调用是确定性的,但现实世界的交互是异步的。事件(Event / Log)是以太坊的"消息队列",它是链上合约与链下世界的唯一桥梁。


12.2.1 事件的内部结构

solidity
pragma solidity ^0.8.0;

contract EventDemo {
    event Transfer(address indexed from, address indexed to, uint256 value, string memo);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    // indexed 参数存储在 "topic" 中,允许直接过滤
    // 非 indexed 参数在 "data" 字段,ABI 编码,不用于索引
    
    function transferWithMemo(address to, uint256 value, string calldata memo) external {
        // ... 检查逻辑 ...
        emit Transfer(msg.sender, to, value, memo);
    }
}

事件在 EVM 层面被记录为日志(LOGn 操作码)

字段大小说明
Topics[0]32 bytesEvent selector = keccak256("Transfer(address,address,uint256,string)")
Topics[1]32 bytesfrom(因为 indexed
Topics[2]32 bytesto(因为 indexed
Data变长value, memo 的 ABI 编码,不用于索引

最多 4 个 indexed 参数。Indexed 参数允许直接使用 eth_getLogs 进行日志过滤。非 indexed 参数只能获取后解码才能筛选。


12.2.2 链下监听架构

graph LR
    subgraph Chain[以太坊节点/WebSocket/RPC]
        C[合约 emit Event]
        L[日志存储<br/>Receipt Logs]
    end
    
    subgraph Indexing[索引层]
        S[Subgraph / TheGraph]
        E[自定义 ETL<br/>LevelDB 遍历]
    end
    
    subgraph 消费[应用层]
        A[前端轮询/subscribe]
        N[通知系统]
    end
    
    C --> L --> |"eth_getLogs"| S
    S --> |"GraphQL"| A
    S --> N

三种数据获取策略

策略延迟成本去中心化程度适用场景
直接 RPC 查询12s-1h(视轮询间隔)高(每次请求计费)小规模 DApp
WebSocket 订阅接近实时高(需要持久连接)高频交易/监控
TheGraph 索引12s(通常)中(按查询量计费)90% 生产级 DApp
自建 ETL + RDB12s-5min低(存储主导)全量分析/监控

12.2.3 Topics 过滤的精确检索

typescript
/**
 * 基于 keccak 事件签名和 topics 的日志过滤逻辑
 * 模拟 EIP-20 Transfer 事件的链下监听
 */
function keccak256(input: string): string {
  // 简化:用简单哈希模拟,真实实现需要 sha3-256
  let h = 0;
  for (let i = 0; i < input.length; i++) {
    h = ((h << 5) - h + input.charCodeAt(i)) | 0;
  }
  return "0x" + (h >>> 0).toString(16).padStart(64, '0');
}

// 真实 Event 签名哈希: keccak256("Transfer(address,address,uint256)")
// = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
const TRANSFER_EVENT_SIGNATURE = "Transfer(address,address,uint256)";
const TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";

interface LogFilter {
  fromBlock: number;
  toBlock: number | "latest";
  address: string;      // 合约地址
  topics: (string | null)[]; // topic[0]=事件签名, [1]=from, [2]=to
}

function buildTransferFilter(
  contractAddress: string,
  fromBlock: number,
  from?: string,
  to?: string,
): LogFilter {
  return {
    fromBlock,
    toBlock: "latest",
    address: contractAddress,
    topics: [
      TRANSFER_TOPIC0,
      from ? from.slice(2).padStart(64, '0').toLowerCase() : null,
      to ? to.slice(2).padStart(64, '0').toLowerCase() : null,
    ],
  };
}

// 示例:监听从 Vitalik 地址发出的所有 USDC 转账
const vitalikAddress = "0xd8dA6BF26964aF9D7aEd9e03E53415D3aD4803d1";
const usdcContract = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

const filter = buildTransferFilter(usdcContract, 18000000, vitalikAddress, undefined);
console.log("Filter:", JSON.stringify(filter, null, 2));

// 解析日志数据(非 topics)
interface DecodedTransfer {
  from: string;
  to: string;
  value: bigint;
  blockNumber: number;
  transactionHash: string;
}

function decodeTransferLog(data: string, topics: string[]): DecodedTransfer {
  // 简化:假设 topics[0]=signature, [1]=from, [2]=to
  const padToAddr = (hex: string): string => "0x" + hex.slice(-40);
  const from = padToAddr(topics[1]);
  const to = padToAddr(topics[2]);
  
  // data 中只包含 value (uint256),ABI 编码为 32 字节
  const valueHex = data.slice(2, 66);
  const value = BigInt("0x" + valueHex);
  
  return { from, to, value, blockNumber: 0, transactionHash: "0x" }; // 占位
}

12.2.4 事件驱动的智能合约设计模式

推送 vs 拉取

  • 拉取模式:用户主动调用合约读取状态(如查询余额)。好处:无延迟;坏处:用户需主动交互。
  • 推送模式:合约通过事件触发,链下系统监听并采取行动(如发送通知、更新数据库、触发自动交易)。
flowchart LR
    subgraph Pull[拉取模式]
        U1[用户] --> |"balanceOf()"| S1[合约查询]
        S1 --> U1
    end
    
    subgraph Push[推送模式]
        C[合约] --> |"emit Update"| L[日志]
        L --> |"监听"| I[链下系统]
        I --> |"调用"| S2[外部 API]
    end
    
    style Pull fill:#ffebee
    style Push fill:#e8f5e9

> ← 上一节:12.1 Solidity进阶 | 前往 → 12.3 典型漏洞与攻击 |*

评论

0

评论加载中…

发表评论

0/2000