事件签名库 + 解码日志视图(`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 可升级代理事件、ArbitrumArbSys/ArbRetryableTx预编译合约事件、ChainlinkAnswerUpdated/NewRound、Arbitrum 代币桥常见事件。{db}.logs_named:logs关联event_signatures的只读视图,直接得到event_name/event_signature/event_source,未命中的事件这三列为空。- 8 个
v_decoded_*解码视图:对生产环境 top 50topic0里"库里有、且非动态长度字段、解码简单"的事件,把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 rows3.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_approval3431 行、v_decoded_uniswap_v3_swap2451 行、v_decoded_uniswap_v4_swap2773 行、v_decoded_uniswap_v4_modify_liquidity797 行、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 视图输出 | 一致 |
|---|---|---|---|
| amount0 | 199097754662422392 | 199097754662422392 | ✅ |
| amount1 | -531770233 | -531770233 | ✅ |
| sqrtPriceX96 | 4094647556540577380715112 | 4094647556540577380715112 | ✅ |
| liquidity | 235735405797971435 | 235735405797971435 | ✅ |
| tick | -197418 | -197418 | ✅ |
Uniswap V4 ModifyLiquidity(tx_hash=faff1aa38b7f32f727a3d76859aff000269cbb3a3b6dd1cb19b5950f498bee3e,
log_index=1,负的 tickLower/liquidityDelta):
| 字段 | Python 独立解码 | SQL 视图输出 | 一致 |
|---|---|---|---|
| tickLower | -882000 | -882000 | ✅ |
| tickUpper | 882000 | 882000 | ✅ |
| liquidityDelta | -241892425908747 | -241892425908747 | ✅ |
| salt | 0000…22ba72 | 0000…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 | 命中库 |
|---|---|---|---|---|
| 1 | ddf252ad…3b3ef | Transfer (ERC20/ERC721) | 215,372,408 | ✅ |
| 2 | c42079f9…bcca67 | Swap (UniswapV3) | 32,490,902 | ✅ |
| 3 | 37e7f0db…cd3802 | 未知(见下) | 32,433,734 | ❌ |
| 4 | 8c5be1e5…c3b925 | Approval (ERC20/ERC721) | 29,470,599 | ✅ |
| 5 | 8619026a…9f02a | 未知(见下) | 11,189,859 | ❌ |
| 6 | 205442d6…82b5c92 | 未知(见下) | 11,183,953 | ❌ |
| 7 | 40e9cecb…7112f | Swap (UniswapV4) | 9,652,119 | ✅ |
| 8 | 93485dcd…7ccdc324 | 未知 | 7,422,814 | ❌ |
| 9 | 1c411e9a…fbbad1 | Sync (UniswapV2) | 6,205,574 | ✅ |
| 10 | d78ad95f…40159d822 | Swap (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无、data64 字节——形态类似 自定义Transfer-like 事件但签名文本不同,同样是自定义合约。93485dcd…:地址0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f,tx_hash=0x7c2fc0c65ab422ecf1d1037db62f823740bc479c975fa19ef8e00da1552f5813, 区块 9750001,无 indexed 参数、data224 字节。
这部分超出"知名公开 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, 没有做解码视图,也没有用生产数据验证过形状。
ERC20 授权数据集(erc20_approvals / erc20_current_allowances)
对应 SQL 文件:schema/clickhouse/derived/018_erc20_approvals.sql。 依赖已安装的 {db}.logs(sql/schema.sql,由底层的 raw loader 写入并维护)。
方法选择器字典 + 解码交易视图(method_selectors / transactions_named)
对应文件: - schema/clickhouse/derived/016_method_selectors.sql - scripts/derived/gen_method_selectors.py