BlockVectra

事件签名库 + 解码日志视图(`event_signatures` / `logs_named`)

面向直接写 SQL 的内部团队:给定 logs.topic0,查出这是哪个事件、参数长什么样, 并对生产环境里最常见的一批事件提供开箱即用的解码视图。

面向直接写 SQL 的内部团队:给定 logs.topic0,查出这是哪个事件、参数长什么样, 并对生产环境里最常见的一批事件提供开箱即用的解码视图。

1. 是什么

  • {db}.event_signatures:topic0 -> ABI 的静态参考表(45 行),覆盖 ERC20 / ERC721 / ERC1155、WETH、Uniswap V2 / V3 / V4、Ownable、AccessControl、 Pausable、EIP-1967 可升级代理事件、Arbitrum ArbSys / ArbRetryableTx 预编译合约事件、Chainlink AnswerUpdated/NewRound、Arbitrum 代币桥常见事件。
  • {db}.logs_named:logs 关联 event_signatures 的只读视图,直接得到 event_name/event_signature/event_source,未命中的事件这三列为空。
  • 8 个 v_decoded_* 解码视图:对生产环境 top 50 topic0 里"库里有、且非动态长度字段、解码简单"的事件,把 topic1..3/data 拆成具名字段(地址、有符号/无符号整数等)。Transfer 已经有专门的 erc20_transfers/erc721_transfers(见 001_erc20_transfers.sql / 002_erc721_transfers.sql),这里不重复造。

安装文件:schema/clickhouse/derived/015_event_signatures.sql。 生成脚本:scripts/derived/gen_event_signatures.py。

2. 安装与补算

sed 's/{db}/robinhood/g' schema/clickhouse/derived/015_event_signatures.sql \
  | clickhouse-client --multiquery

依赖 {db}.logs 已存在;不依赖 {db}.traces(另行接入中)。表结构和视图定义 都是 CREATE ... IF NOT EXISTS,种子数据的 INSERT 前也带了一条 TRUNCATE TABLE {db}.event_signatures(见文件内注释)——整份文件重复执行 (含 --multiquery 中途失败后重跑)安全,不会在 event_signatures 里堆出 重复的 topic0 行(那会让 logs_named 对命中的 topic0 精确翻倍计数)。

补算 / 更新事件目录:event_signatures 是静态种子表,不是从 logs 增量派生的物化视图——它和某个区块范围完全无关,因此常规派生表那条 "ReplacingMergeTree 原始表可能有重复插入和重组墓碑,INSERT 触发的 MV 直接 SUM/COUNT 会重复计数"的正确性规则在这里不适用:没有要去重的东西,也没有 要传播的重组。表引擎选的是普通 MergeTree ORDER BY topic0(不是 ReplacingMergeTree,它本身不做去重),补算流程就是重新跑一遍安装命令 (TRUNCATE 已经内置在 015 文件里,不需要手动补):

# 改 scripts/derived/gen_event_signatures.py 的 EVENTS 列表、重新生成 INSERT
# 并粘回 015_event_signatures.sql 之后,重跑同一条安装命令即可(幂等):
sed 's/{db}/robinhood/g' schema/clickhouse/derived/015_event_signatures.sql \
  | clickhouse-client --multiquery

新增/修改事件签名时,改 scripts/derived/gen_event_signatures.py 里的 EVENTS 列表,重新跑脚本,把新的 INSERT 粘回 015_event_signatures.sql(脚本自带 --check 做哈希自检,见下)。

logs_named 和全部 v_decoded_* 都是普通视图(不是物化视图),每次查询 都直接读 {db}.logs FINAL ... AND is_deleted = 0(event_signatures 是 MergeTree 不支持 FINAL,也不需要——见下方设计说明)。好处是彻底绕开 派生表正确性规则:没有存储状态,也就没有 backfill 重复插入的重复计数问题, 也没有过期未反映重组的问题,代价是每次查询都要付 FINAL 扫描成本——适合 临时查询/研究场景,不建议作为高 QPS 热路径(同类讨论见 docs/perf/query-indexes.md)。

3. 验证

3.1 Keccak-256 自检(脚本内置)

scripts/derived/gen_event_signatures.py 用纯 Python(标准库,无第三方依赖)实现 Keccak-256(Ethereum/Solidity 变体,域分隔字节 0x01,不是 NIST SHA3-256 的 0x06)。开发时额外用 pycryptodome(Crypto.Hash.keccak)做过独立交叉验证 (空串、"abc"、以及全部 45 个签名文本逐一比对,全部字节级一致);生成的 SQL 只依赖脚本自带的纯 Python 实现,不依赖环境里有没有装 pycryptodome。

$ python3 scripts/derived/gen_event_signatures.py --check
[self-check] OK       Transfer(address,address,uint256) -> ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
[self-check] OK       Approval(address,address,uint256) -> 8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
[self-check] OK       TransferSingle(address,address,address,uint256,uint256) -> c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62
[self-check] OK       OwnershipTransferred(address,address) -> 8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0
self-check passed, 45 unique topic0 rows

3.2 视图安装 + 解码结果验证(本地 ClickHouse,真实链上数据)

在本地 Docker ClickHouse(chain-indexer-ch)建了一个唯一命名的临时库 wt39_event_sigs(验证完已 DROP DATABASE),从共享的 robinhood 库拷贝了已回填的真实数据(48,388 行 logs,区块 [72039000, 72050000) 区间),依次执行 001/002/015,全部对象创建成功:

  • logs_named 总行数 = logs FINAL WHERE is_deleted=0 行数:48388 = 48388, 精确匹配(LEFT JOIN 不丢行、不重复行)。
  • 8 个 v_decoded_* 视图在这批真实数据里都命中了真实行(v_decoded_approval 3431 行、v_decoded_uniswap_v3_swap 2451 行、v_decoded_uniswap_v4_swap 2773 行、v_decoded_uniswap_v4_modify_liquidity 797 行、 v_decoded_uniswap_v2_swap/v_decoded_uniswap_v2_sync 各 121/123 行、 v_decoded_uniswap_v3_burn/v_decoded_uniswap_v3_collect 各 126 行)。

3.3 独立计算交叉验证(Python 直接解码原始字节)

对同一行,分别用 Python(int.from_bytes(..., "big", signed=...),不经过本 仓库任何代码)和 SQL 解码视图各算一遍,逐字段比对:

Uniswap V3 Swap(tx_hash=8621f101e74be06bfbd60de5e2644c751081a7a35d145952fe2ff21e599a4fe5, log_index=10,signed int256/int24 字段):

字段Python 独立解码SQL 视图输出一致
amount0199097754662422392199097754662422392✅
amount1-531770233-531770233✅
sqrtPriceX9640946475565405773807151124094647556540577380715112✅
liquidity235735405797971435235735405797971435✅
tick-197418-197418✅

Uniswap V4 ModifyLiquidity(tx_hash=faff1aa38b7f32f727a3d76859aff000269cbb3a3b6dd1cb19b5950f498bee3e, log_index=1,负的 tickLower/liquidityDelta):

字段Python 独立解码SQL 视图输出一致
tickLower-882000-882000✅
tickUpper882000882000✅
liquidityDelta-241892425908747-241892425908747✅
salt0000…22ba720000…22ba72✅

ERC20 Approval(tx_hash=9235dad82dc66752efe5c78bd0ffbd10da97dcfd9966b88d82992cf636116125, log_index=9):owner=5803ab82b0fecca8f5c17b9e69e0c4d2c130efef, spender=2b820aafa5e9edb0337f9cb9089168b073987be1,value=0——两边一致。

三组样本涵盖无符号大整数、有符号 int256、有符号 int24(含负 tick)三类解码 路径,全部逐字节精确匹配。

4. 生产环境研究:Top 50 topic0 覆盖率

只读查询(SETTINGS max_threads=2, max_execution_time=90,未写入、未触碰服务/节点):

SELECT hex(topic0) AS topic0_hex, count() AS cnt
FROM robinhood.logs
WHERE topic0 IS NOT NULL
GROUP BY topic0_hex
ORDER BY cnt DESC
LIMIT 50;

结果:event_signatures 命中 top 50 里的 9 个 topic0(Transfer、 Approval、Uniswap V2 Swap/Sync、Uniswap V3 Swap/Burn/Collect、 Uniswap V4 Swap/ModifyLiquidity),按日志行数覆盖率:

覆盖 = 301,115,372 / 408,520,774 ≈ 73.71%(top-50 范围内,9/50 行命中)

Top 10 明细(cnt 为生产环境真实计数):

排名topic0事件cnt命中库
1ddf252ad…3b3efTransfer (ERC20/ERC721)215,372,408✅
2c42079f9…bcca67Swap (UniswapV3)32,490,902✅
337e7f0db…cd3802未知(见下)32,433,734❌
48c5be1e5…c3b925Approval (ERC20/ERC721)29,470,599✅
58619026a…9f02a未知(见下)11,189,859❌
6205442d6…82b5c92未知(见下)11,183,953❌
740e9cecb…7112fSwap (UniswapV4)9,652,119✅
893485dcd…7ccdc324未知7,422,814❌
91c411e9a…fbbad1Sync (UniswapV2)6,205,574✅
10d78ad95f…40159d822Swap (UniswapV2)6,197,098✅

未命中的原因(已用真实样本核实,不是猜测):这些高频事件不在通用公开 ABI 库的范围内,是 Robinhood Chain 自己的代币化股票 / 交易基础设施合约事件。样本 证据(只读查询,地址/哈希均为链上真实值):

  • 205442d6… 与 8619026a…:同一笔交易里成对出现(地址 0x65050a9b7e5075a2ba5ced7b1b64ee66262c40dc, tx_hash=0x1fdd72a6c23718146c39eb2bd00c7b37ad33fc82b843959f8b292a00a0f9bccf, 区块 9750000),计数几乎完全相等(11,189,859 vs 11,183,953),形态像一对 自定义"请求/结果"事件,不是任何标准 ABI。
  • 37e7f0db…:地址 0x322f0929c4625ed5bad873c95208d54e1c003b2d, tx_hash=0xcb73093ff539ac56d8173ae3c35d8c131f4a33777f0a3cd3bce2512486e8c8ec, 区块 9750012,topic1/topic2 有值、topic3 无、data 64 字节——形态类似 自定义 Transfer-like 事件但签名文本不同,同样是自定义合约。
  • 93485dcd…:地址 0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f, tx_hash=0x7c2fc0c65ab422ecf1d1037db62f823740bc479c975fa19ef8e00da1552f5813, 区块 9750001,无 indexed 参数、data 224 字节。

这部分超出"知名公开 ABI 库"的任务范围,留作后续(如果需要覆盖 Robinhood 自有 合约事件,需要拿到对应合约源码/ABI,不能靠猜签名字符串)。

5. 已知局限

  • v_decoded_approval 用 topic3 IS NULL 区分 ERC20/ERC721 形状;生产环境 实测还存在极少数(约 3000 万分之一) 完全没有 topic 的行复用了同一个 topic0(不是合法的 ERC20/ERC721 Approval 形状)。解码视图对这类行直接不 产出(fail-closed,不去猜测解码),不是 bug。
  • Uniswap V4 的事件签名(Swap/ModifyLiquidity/Initialize/Donate)来自 PoolManager 单例设计的公开文档;Swap/ModifyLiquidity 已经用生产环境真实 日志的 topic 个数 + data 字节长度核对过形状(见 §4 表格),可信度高。 Initialize/Donate 未在 top 50 里出现,形状未经真实数据核实,如果之后要 解码它们,先重复一遍 §4 的"形状核对"步骤。
  • ArbSys/ArbRetryableTx/Chainlink/Arbitrum 桥事件目前只进了 event_signatures(签名可信,来自公开的预编译合约 ABI),未出现在 top 50, 没有做解码视图,也没有用生产数据验证过形状。

本页目录