BlockVectra

方法选择器字典 + 解码交易视图(method_selectors / transactions_named)

对应文件: - schema/clickhouse/derived/016_method_selectors.sql - scripts/derived/gen_method_selectors.py

This content is sourced from upstream and is currently available in Chinese only.

对应文件:

  • schema/clickhouse/derived/016_method_selectors.sql
  • scripts/derived/gen_method_selectors.py

是什么

{db}.method_selectors:一张手工维护的静态参考表(不是从链上数据派生出来 的),把 4 字节 ABI 函数选择器({db}.transactions.input 的前 4 字节, selector = keccak256(ascii(canonical_signature))[:4])映射到人类可读的 signature / name。种子数据来自公开、知名的 ABI:ERC20 / ERC721 / ERC1155、 WETH9、Uniswap V2 Router / V3 SwapRouter / Universal Router、 Multicall / Multicall3、Permit2、Gnosis Safe、EIP-1967/897 代理、 OpenZeppelin AccessControl / Ownable / Pausable,以及 Arbitrum 的 ArbSys(0x64)/ ArbRetryableTx(0x6E)预编译合约。目前共 118 行, 选择器互不冲突(生成脚本会在冲突时直接报错拒绝生成)。

{db}.transactions_named:在 {db}.transactions 的每一行基础上,附加解码出的 method_id(4 字节选择器)、以及命中 method_selectors 时的 method_name / method_signature。

表结构

CREATE TABLE {db}.method_selectors
(
    selector    FixedString(4),
    signature   String,
    name        String,
    source      LowCardinality(String),   -- 分组标签,如 erc20 / uniswap-v3 / arbsys-precompile
    version     UInt64,
    is_deleted  UInt8
)
ENGINE = ReplacingMergeTree(version, is_deleted)
ORDER BY selector;

method_id 为 NULL 的两种情形(不适用"函数选择器"这个概念):

  • 合约创建交易(to IS NULL)——这时 input 是初始化字节码,不是对已有合约的 调用数据;
  • 空/过短 calldata(length(input) < 4)——纯转账或无参数调用。

method_name / method_signature 为 NULL 表示 method_id 存在但不在当前 目录里(未收录的选择器),不是函数没有名字的空字符串——见下面「LEFT JOIN 的一个坑」。

LEFT JOIN 的一个坑:ClickHouse 默认不用 NULL 补未命中

{db}.transactions_named 的 JOIN 部分在开发过程中踩到一个真实的坑: ClickHouse 的 LEFT JOIN 默认(join_use_nulls = 0)用右表列的默认值 (String 类型是空字符串 '')填未命中的行,而不是 NULL。本地验证时用 method_name IS NOT NULL 统计覆盖率,第一版返回了 decoded = total (100% "覆盖",包括没打上名字的行),一看就不对。

修复:在视图定义末尾加 SETTINGS join_use_nulls = 1,并确认过这个设置会 固化进视图定义本身(用另一个不带该 setting 的查询去 SELECT * FROM view 也生效),不是只在建视图那一次会话里生效。修完后未命中选择器的 method_name 才是真正的 NULL,覆盖率统计才有意义(见下面「本地验证」)。

CORRECTNESS RULE 适用性说明

AGENTS.md 的 CORRECTNESS RULE 针对的是"从原始表 SUM/COUNT 出聚合结果"的派生表 ——ReplacingMergeTree 原始表可能有重复插入(backfill 续跑)和 reorg 墓碑 (is_deleted=1 的高 version 行),insert-trigger 的 MV 如果直接 SUM/COUNT 插入行会重复计数。

method_selectors / transactions_named 都不属于这一类:

  • method_selectors 不对任何链上表做 SUM/COUNT,是我们自己拥有、可幂等重新 播种的参考数据——ReplacingMergeTree(version, is_deleted) + ORDER BY selector 让"追加/修正一行选择器"变成"提高 version 重新 INSERT", ... FINAL WHERE is_deleted = 0 折叠成每个 selector 一行,不需要 MV。
  • transactions_named 是一个非物化的普通 VIEW,每次查询都现算, 底层 FROM {db}.transactions AS t FINAL WHERE t.is_deleted = 0 已经写进视图定义(调用方不需要自己记得加),继承的正是原始表自身的去重/ reorg 语义,view 本身不引入任何新的重复计数风险。

安装与补算

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

用 HTTP 接口的话该文件包含 3 条语句(CREATE TABLE / INSERT / CREATE OR REPLACE VIEW),HTTP 接口不支持一次发多条语句,需要按 CREATE TABLE ...;、INSERT INTO ... VALUES (...);、 CREATE OR REPLACE VIEW ...; 切成 3 次独立请求发送(同 003_backfill.sql 的 HTTP 用法说明)。

没有"补算历史数据"这一步——这不是从 {db}.logs/{db}.transactions 派生出来 的表,method_selectors 的种子数据一次性 INSERT 即可覆盖所有历史和未来交易, transactions_named 是非物化视图,建好即对全表(历史+新增)生效,不需要 003_backfill.sql 那种"MV 建立前的数据要单独补"的步骤。

新增/修改选择器:编辑 scripts/derived/gen_method_selectors.py 里的 SELECTORS 列表,重新生成并替换 016 文件里的 INSERT ... VALUES 数据段, 提交 diff:

python3 -m venv /tmp/venv && /tmp/venv/bin/pip install pycryptodome
/tmp/venv/bin/python scripts/derived/gen_method_selectors.py > /tmp/method_selectors.inserts.sql

(脚本自带 transfer(address,uint256) == 0xa9059cbb 的 keccak self-test, 以及选择器冲突检测——冲突会直接 sys.exit,不会静默生成错误数据。)

本地验证(精确匹配)

拓扑:本地 Docker ClickHouse(http://127.0.0.1:8123),真实节点 RPC 回填, scratch db msel_wt40,区块区间 [72,040,000, 72,042,000)(2000 个块, 回填当天链头约 72,044,420)。

chain-indexer backfill --sink clickhouse --from 72040000 --to 72042000
  → blocks=2000 txs=12073 logs=55722
验证项结果
method_selectors FINAL 行数 / 去重后行数118 / 118(无重复 selector)
transfer(address,uint256) 选择器hex(selector) = 'A9059CBB',name = 'transfer',与题面给定值精确匹配
transactions_named 行数 vs transactions FINAL WHERE is_deleted=0 行数12073 = 12073(精确相等,视图未产生任何行膨胀/丢失)
独立 RPC 交叉验证(eth_getTransactionByHash,非 ClickHouse 路径)3 笔命中 method_name='execute' 的交易,直接对节点查询 input 字段前 4 字节,均为 0x3593564c,与 ClickHouse 侧解码结果精确一致:0x4223ce2a2c55082365c83702e2192a08f1901cf771ef0a5a069910a575835cef0x102eb59d7050f22c5ecdafae9bfb9a627a76adda7f0c2f7aa2a27ff577cb283d0x9939bd8d5d6f97242ec669d6508388b3c6b217e789bf7db73e2277c2cd3ce2d3
本地样本覆盖率(method_id IS NOT NULL 中能解出名字的占比)2814 / 11426 = 24.63%

修复 join_use_nulls 问题之前,同一条覆盖率查询错误地返回 100%(见上文 「LEFT JOIN 的一个坑」)——记录在这里是为了说明这项验证真的跑出来会失败的 中间状态,不是凑数。

生产环境研究:按交易数排名前 50 的 method id

只读查询,SETTINGS max_threads=2, max_execution_time=90,通过 ssh [email protected] 读生产 ClickHouse(未做任何写入)。范围: {db}.transactions WHERE is_deleted=0 AND to IS NOT NULL AND length(input)>=4 (全部历史,不分区间),共 124,890,974 笔"可解码"交易。

总体覆盖率:27,836,932 / 124,890,974 = 22.29%(用当前 118 个种子选择器 匹配,全表口径,不只是 Top 50)。

Top 50(selector / 目录里的 name,未收录留空 / 交易数):

#selectorname(本目录)交易数
16bf6a42d(未收录)15,946,248
24d819a2a(未收录)11,710,362
3095ea7b3approve10,352,248
43593564cexecute7,343,743
500000000(未收录)5,402,242
604e45aaf(未收录)4,332,060
7b6621842(未收录)4,067,707
8cce7ec13(未收录)3,604,439
939ecce49(未收录)3,105,851
10b344ecec(未收录)2,930,043
115ae401dcmulticall2,390,989
123e0f9c3c(未收录)2,030,433
13765e827f(未收录)1,679,944
14c1120e3d(未收录)1,542,552
15ac9650d8multicall1,511,937
165965a4ff(未收录)1,459,814
170a2b8f36(未收录)1,426,547
18b6f9de95swapExactETHForTokensSupportingFeeOnTransferTokens1,328,297
1924856bc3execute1,304,851
20a9059cbbtransfer1,230,161
21e6cb474f(未收录)1,192,050
2218a54a74(未收录)960,324
23a84b47b4(未收录)946,473
24791ac947swapExactTokensForETHSupportingFeeOnTransferTokens864,195
25161ac21f(未收录)786,752
26f2c42696(未收录)769,184
2700000008(未收录)742,942
285fc3c09f(未收录)736,948
2987517c45(未收录)658,349
3083701d69(未收录)544,083
31f3f77a0e(未收录)531,916
3201000bd7(未收录)512,077
330000003a(未收录)483,162
34c7d693bf(未收录)479,395
3519409a60(未收录)467,585
36b3938887(未收录)447,600
3795c3abcf(未收录)434,839
3800000005(未收录)411,473
39aededc1a(未收录)392,263
406ab3d6ed(未收录)367,649
412e1a7d4dwithdraw339,582
42a59ac6dd(未收录)308,261
43b1521cf6(未收录)303,489
44a0328eb2(未收录)301,499
45036f80a5(未收录)296,232
460273a58c(未收录)288,378
4708c1284c(未收录)283,187
48c5d78cea(未收录)265,526
497299095e(未收录)264,629
502213bc0b(未收录)258,846

Top 50 里目前收录了 9 个(approve / execute ×2 / multicall ×2 / transfer / withdraw / 两个 SupportingFeeOnTransferTokens 变体), Top 50 范围内的加权覆盖率 26,666,003 / 100,339,356 = 26.58%。

已知缺口(未收录、留作后续)

排名第 1、2 的选择器(6bf6a42d、4d819a2a)合计接近 2800 万笔交易, 显著高于任何一个已收录方法,值得优先补齐。查过公开的 4byte.directory, 分别返回过 startBlock(uint256,uint64,uint64,uint64) 和一个 DEX 聚合器风格的 swap((...)[],address,uint256,uint256,uint256) 元组签名候选——但 4byte.directory 是众包数据库,条目未经这条链验证(没有拿对应 to 地址的已验证 合约源码/ABI 交叉核对过参数形状),本 PR 不把它们当"知名 ABI"塞进种子表, 避免在参考表里放不确定的名字。这两个、以及 00000000 / 00000008 / 0000003a / 00000005 这类高位全零的选择器(形状上更像是某个自定义合约的 低数值函数选择器,也可能是 Robinhood 相关合约的自有方法),都建议作为独立的 后续任务:拿到对应 to 地址在生产库里的合约字节码/已验证源码后再收录, 不在本 PR 范围内。

On this page