ArbOS L1 计价历史数据集(arbos_l1_pricing / daily_l1_base_fee_stats)
来源:schema/clickhouse/derived/022_arbos_l1_pricing.sql。 面向内部用 SQL 查询 Arbitrum Orbit L1 计价状态、L1 区块对齐与跨链批量发布历史的团队。
来源:schema/clickhouse/derived/022_arbos_l1_pricing.sql。
面向内部用 SQL 查询 Arbitrum Orbit L1 计价状态、L1 区块对齐与跨链批量发布历史的团队。
1. 概述与背景原理
1.1 Arbitrum Nitro / ArbOS 内部交易(startBlock)
在 Arbitrum Orbit 架构中,ArbOS 会在每个 L2 区块的最前端(tx_index = 0)自动注入一笔类型为 0x6a(十进制 106,ArbitrumInternalTx)的系统内部交易。该交易的发送方(from)和接收方(to)均为 ArbOS 预编译合约地址:
- 地址:
0x00000000000000000000000000000000000A4B05(即0x...A4B05,十六进制中字符A4B05谐音ARBOS)。 - 函数选择器:
0x6bf6a42d。
1.2 Nitro 源码签名核验
经直接对照 Offchain Labs 官方 Nitro 核心源码(precompiles/ArbosActs.go 与 arbos/internal_tx.go),确认该交易调用的标准函数签名与参数顺序:
// precompiles/ArbosActs.go
func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee huge, l1BlockNumber, l2BlockNumber, timeLastBlock uint64) error {
return con.CallerNotArbOSError()
}
// arbos/internal_tx.go
func InternalTxStartBlock(chainId, l1BaseFee *big.Int, l1BlockNum uint64, header, lastHeader *types.Header) *types.ArbitrumInternalTx {
l2BlockNum := header.Number.Uint64()
timePassed := header.Time - lastHeader.Time
if l1BaseFee == nil {
l1BaseFee = big.NewInt(0)
}
data, err := util.PackInternalTxDataStartBlock(l1BaseFee, l1BlockNum, l2BlockNum, timePassed)
...
}标准规范 ABI 签名:
startBlock(uint256,uint64,uint64,uint64)Keccak-256 哈希计算:keccak256("startBlock(uint256,uint64,uint64,uint64)")[:4] == 0x6bf6a42d,与链上实测选择器完全一致。
1.3 Calldata 32 字节字排布与解码逻辑
该交易的 input 长度恒为 132 字节(4 字节选择器 + 4 个 32 字节 ABI 编码字):
| 偏移区间(字节) | ABI 类型 | 字段名 | 含义说明 |
|---|---|---|---|
[0..4) (4B) | bytes4 | selector | 固定为 0x6bf6a42d |
[4..36) (32B) | uint256 | l1_base_fee | ArbOS 评估的 L1 父链 calldata base fee(单位:wei) |
[36..68) (32B) | uint64 | l1_block_number | 此时对齐的 L1 父链区块号(与 blocks.l1_block_number 一致) |
[68..100) (32B) | uint64 | l2_block_number | L2 区块号(与 block_number 完全一致) |
[100..132) (32B) | uint64 | time_passed | 距离上一个区块经过的秒数(header.Time - lastHeader.Time) |
ClickHouse SQL 解码实现:
EVM ABI 采用大端序(big-endian),而 ClickHouse 的 reinterpretAsUInt256 / reinterpretAsUInt64 采用小端序(little-endian)。因此在 ClickHouse 中解码 32 字节字必须结合 reverse():
l1_base_fee:reinterpretAsUInt256(reverse(substring(input, 5, 32)))l1_block_number:toUInt64(reinterpretAsUInt256(reverse(substring(input, 37, 32))))time_passed:toUInt64(reinterpretAsUInt256(reverse(substring(input, 101, 32))))
2. 表结构
2.1 arbos_l1_pricing(逐块表)
CREATE TABLE IF NOT EXISTS {db}.arbos_l1_pricing
(
block_number UInt64 CODEC(Delta, ZSTD),
block_timestamp DateTime('UTC') CODEC(Delta, ZSTD),
l1_base_fee UInt256,
l1_block_number UInt64,
time_passed UInt64,
version UInt64,
is_deleted UInt8
)
ENGINE = ReplacingMergeTree(version, is_deleted)
PARTITION BY intDiv(block_number, 5000000)
ORDER BY (block_number);2.2 daily_l1_base_fee_stats(每日收盘聚合表)
CREATE TABLE IF NOT EXISTS {db}.daily_l1_base_fee_stats
(
date Date,
block_count UInt64,
min_l1_base_fee UInt256,
max_l1_base_fee UInt256,
avg_l1_base_fee UInt256,
median_l1_base_fee UInt256,
non_zero_blocks UInt64,
version UInt64 DEFAULT 1,
is_deleted UInt8 DEFAULT 0
)
ENGINE = ReplacingMergeTree(version, is_deleted)
ORDER BY (date);3. CORRECTNESS RULE 正确性与安全机制
3.1 arbos_l1_pricing: 方案 (a)
- 1:1 结构映射:Arbitrum Orbit 上每个区块必定且仅有一笔
tx_index = 0的startBlock交易,源表{db}.transactions与目标表行数严格 1:1。 - 幂等与重组免疫:物化视图
mv_arbos_l1_pricing直接透传源表的version与is_deleted。- 断点续跑导致重复插入时,同版本多行在
FINAL上折叠。 - 发生链重组写下墓碑(
is_deleted = 1、更高版本)时,{db}.arbos_l1_pricing FINAL WHERE is_deleted = 0会精确同步剔除回滚区块。 - 重组后新区块再入库,更高版本的新行在
FINAL上胜出。
- 断点续跑导致重复插入时,同版本多行在
3.2 daily_l1_base_fee_stats: 方案 (b)
- 收盘日刷新机制:聚合表使用可刷新物化视图(
REFRESH EVERY 1 DAY OFFSET 1 HOUR RANDOMIZE FOR 10 MINUTE),仅对已完全收盘的自然日(toDate(block_timestamp) < today())从{db}.arbos_l1_pricing FINAL整体重算。 - 整表原子替换:ClickHouse
TO目标表的可刷新物化视图在每次调度时原子替换目标分区/数据,不采用增量累加,彻底根除重复插入或重组墓碑引发的双重计数风险。
3.3 货币与数度安全性(Money Safety)
- 涉及金额与费用的字段(
l1_base_fee、min_l1_base_fee、max_l1_base_fee、avg_l1_base_fee、median_l1_base_fee)全程采用UInt256整数。 avg_l1_base_fee采用整数整除intDiv(sum(l1_base_fee), toUInt256(count())),严禁任何Float64浮点参与。- 中位数采用精确分位数计算
assumeNotNull(quantileExact(0.5)(l1_base_fee))。
4. 验证与核验结果
4.1 独立 Python ABI 解码对比(5 个样本区块精确核验)
通过独立 Python 环境(eth_abi.abi.decode)直接解码 RPC 原始 input,与 ClickHouse SQL 抽取字段逐字节比对:
区块高度 (block_number) | 内部交易哈希 (tx_hash) | Python 解码 (l1_base_fee, l1_block_number, time_passed) | ClickHouse SQL 解码 (l1_base_fee, l1_block_number, time_passed) | 匹配结果 |
|---|---|---|---|---|
72039000 | 0x2e09dd46b35273270a4187f36ccae74d3d2c719494c4172f1a65a9a97c917c0f | (0, 26052892, 1) | (0, 26052892, 1) | EXACT MATCH |
72039322 | 0xae958959b4635c7903036ba2c876bdbdeee3dc748c8a32e2dc0ff3a3f1cf8ba9 | (899928553, 26052831, 0) | (899928553, 26052831, 0) | EXACT MATCH |
72039324 | 0x9ba2b4639a83f8755eaf3f142e8c0ed49147c5c00f8cc52c24593d67c28e4980 | (777241581, 26052834, 0) | (777241581, 26052834, 0) | EXACT MATCH |
72039327 | 0xe5194dd81baef05f23d0ce0f3d2119090ab7cf89ca319ad894767ec71306fb8a | (900392014, 26052844, 0) | (900392014, 26052844, 0) | EXACT MATCH |
72039331 | 0x7280e21d536a0e68586510d9fc81446d7c4ab615dd03d5badcaa12633450a942 | (413370186, 26052858, 0) | (413370186, 26052858, 0) | EXACT MATCH |
所有 5 笔交易的 from 与 to 均为 0x00000000000000000000000000000000000a4b05,类型均为 0x6a(106)。
4.2 ArbGasInfo 预编译合约交叉核验
- 预编译地址:
0x000000000000000000000000000000000000006c(ArbGasInfo) - 核验方法:
getL1BaseFeeEstimate(),函数选择器0xf5d6ded7。 - 调用结果:在节点链头高度(区块 72151045)发起
eth_call:- 返回结果:
0x0000000000000000000000000000000000000000000000000000000000000000(0 wei)。 - 对应该区块
startBlock解码的l1_base_fee亦为0。两者完全吻合。
- 返回结果:
4.3 本地回填全量统计与 Python 独立求和比对
在本地 Docker ClickHouse(区块 [72039000, 72040999],共 2000 个块)执行全量比对:
- 总块数 vs 行数:2000 块生成 2000 行
arbos_l1_pricing(100% 覆盖,0 丢失,0 膨胀)。 l2_block_number == block_number一致率:2000 / 2000(100%)。blocks.l1_block_number == l1_block_number一致率:2000 / 2000(100%)。- 日度聚合指标比对:
指标项 ClickHouse SQL 计算结果 Python 独立累加/计算结果 匹配核对 block_count20002000精确一致 min_l1_base_fee00精确一致 max_l1_base_fee900392014900392014精确一致 sum(l1_base_fee)68126707036812670703精确一致 avg_l1_base_fee34063353406335精确一致 median_l1_base_fee00精确一致 non_zero_blocks1010精确一致
4.4 生产库只读抽样调研
在生产 ClickHouse 库对区间 [72000000, 72010000)(10,000 个连续区块)执行只读查询:
- 对应区块内
type = 106 AND tx_index = 0 AND selector = '6BF6A42D'交易数量严格为10000笔(每个区块严格一笔)。 - 其中非零费率区块共
56个(占比 0.56%)。
5. 安装与补算
5.1 部署方式
将 {db} 替换为目标链数据库名(如 robinhood)后执行:
sed 's/{db}/robinhood/g' schema/clickhouse/derived/022_arbos_l1_pricing.sql \
| clickhouse-client --multiquery若使用 HTTP 接口(因不支持多语句批处理),请按分号将文件拆分为 6 条独立 SQL 语句依次提交:
CREATE TABLE IF NOT EXISTS {db}.arbos_l1_pricing ...CREATE MATERIALIZED VIEW IF NOT EXISTS {db}.mv_arbos_l1_pricing ...INSERT INTO {db}.arbos_l1_pricing SELECT ...(补算历史区块)CREATE TABLE IF NOT EXISTS {db}.daily_l1_base_fee_stats ...CREATE MATERIALIZED VIEW IF NOT EXISTS {db}.mv_daily_l1_base_fee_stats ...INSERT INTO {db}.daily_l1_base_fee_stats SELECT ...(补算已收盘历史日)
5.2 状态监测与刷新
- 每日聚合视图状态查看:
SELECT view, status, last_success_time, exception FROM system.view_refreshes WHERE database = 'robinhood' AND view = 'mv_daily_l1_base_fee_stats'; - 手动触发刷新:
SYSTEM REFRESH VIEW robinhood.mv_daily_l1_base_fee_stats;
6. 查询示例
6.1 查询最近非零 L1 费率变动与衰减过程
SELECT
block_number,
block_timestamp,
l1_base_fee,
l1_block_number,
time_passed
FROM robinhood.arbos_l1_pricing FINAL
WHERE is_deleted = 0
AND l1_base_fee > 0
ORDER BY block_number DESC
LIMIT 20;6.2 查询某段时间每日 L1 Base Fee 趋势
SELECT
date,
block_count,
min_l1_base_fee,
max_l1_base_fee,
avg_l1_base_fee,
median_l1_base_fee,
non_zero_blocks
FROM robinhood.daily_l1_base_fee_stats FINAL
WHERE is_deleted = 0
ORDER BY date DESC;