BlockVectra

价格预言机数据集(oracle_feeds / oracle_prices)

Robinhood Chain(chain id 4663)上确实存在链上价格预言机,用于给约 60 多个代币化股票 / 加密资产 / 稳定币提供参考价。经独立验证,其形态是经典 Chainlink FluxAggregator (AggregatorV3Interface + AnswerUpdated/…

结论

Robinhood Chain(chain id 4663)上确实存在链上价格预言机,用于给约 60 多个代币化股票 / 加密资产 / 稳定币提供参考价。经独立验证,其形态是经典 Chainlink FluxAggregator (AggregatorV3Interface + AnswerUpdated/NewRound 事件),不是 OCR2/OCR3(未发现 NewTransmission),也没有发现 Pyth / Redstone / Chronicle 的痕迹。

本次交付:{db}.oracle_feeds(feed 注册表,脚本填充)+ {db}.oracle_prices (从 {db}.logs 派生的 MV)。

研究方法与证据

1. 事件签名先用节点自身计算,不凭记忆

用生产节点的 web3_sha3 RPC(对 Transfer(address,address,uint256) 先做了一次已知值 校验,结果与 001_erc20_transfers.sql 里硬编码的 topic0 完全一致,确认方法可信), 现算出以下候选事件的 topic0:

候选协议事件签名topic0(web3_sha3 实算)链上是否出现
Chainlink Flux/OCR1AnswerUpdated(int256,uint256,uint256)0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f是,62 个地址
Chainlink Flux/OCR1NewRound(uint256,address,uint256)0x0109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271是(与上表同一批地址伴生)
Chainlink OCR2/OCR3NewTransmission(uint32,int192,address,int192[],bytes,bytes32)0xf6a97944f31ea060dfde0566e4167c1a1082551e64b60ecb14d599a9d023d451否
PythPriceFeedUpdate(bytes32,uint64,int64,uint64)0xd06a6b7f4918494b3719217d1802786c1f5112a6c1d88fe2cfec00b4584f6aec否
ChronicleLogPushed(bytes32,bytes)0x9d041a8cc415c772acd95a12cd9f5be2c46c6172974094f821ecdd24a37975c3否

“否”是在对生产 {db}.logs(417,492,832 行,区块 1..72,044,367,17,891 个不同 topic0)按 topic0 IN (...) 的全表扫描下得到的零匹配,不是抽样结果。Redstone 按其设计本来就不产生持久化链上事件(价格数据在 calldata 里现付现验),因此这种“拉取式” 预言机原则上不会留下可供本方法发现的日志痕迹,本次未见其特征,但也不能仅凭日志排除其 存在——如后续需要,需改为扫描交易 calldata 特征。

2. 用真实合约交叉验证,不只信文档 / 搜索结果

公开文档(docs.chain.link/data-feeds/tokenized-equity-feeds/robinhood)声称 Robinhood Chain 采用 Chainlink 标准 AggregatorV3Interface(latestRoundData()),但页面本身没有 给出可核实的合约地址;一次网络搜索摘要给出了 2 个候选地址(AAPL/NVDA),在写入任何 结论前先用节点 eth_call 独立验证,而不是直接采信搜索结果(该搜索结果本身还提示 "Robinhood Chain" 生态存在钓鱼站点,对不熟悉的区块浏览器域名一律未访问):

资产代理合约(consumer 用的地址)decimals()description()latestRoundData().updatedAt
AAPL0x6B22A786bAa607d76728168703a39Ea9C99f2cD08Robinhood AAPL / USD2026-09-24T19:55:25Z(新鲜)
NVDA0x379EC4f7C378F34a1B47E4F3cbeBCbAC3E8E9F158RHNVDA / USD2026-09-25T06:22:16Z(新鲜)

两个合约都有真实字节码(19,142 hex 字符 / ~9,571 字节),且都实现了标准 AggregatorV3Interface(decimals()/description()/latestRoundData() 均返回合理值), 证实预言机确实存在且在正常工作,而非文档臆造。

代理合约的 aggregator()(Chainlink EACAggregatorProxy 标准 getter)进一步给出了真正 产生数据的底层聚合器地址:AAPL -> 0xbb11a21267cfdb63d4935d99a499133dd1744acb,NVDA -> 0xc9d16e4f2569b9e3ea0468fd85844953713dc2a2——这两个地址正是上面 topic0 扫描发现的 62 个地址中的两个。

3. 从"发现两个"到"发现全部":直接对全链日志做 topic0+shape 扫描

不依赖搜索引擎能给出多少个例子,而是直接对 {db}.logs 按 topic0 = AnswerUpdated 且 topic1/topic2 非空、topic3 为空、data 恰好 32 字节 做去重 地址统计,一次拿到全部 62 个 feed 聚合器地址(这就是 scripts/oracle/discover_feeds.py discover 子命令做的事)。再对这 62 个地址逐一 eth_call decimals()/description(),61 个成功解析,1 个(0xa0a3e2149b4a7125ad521a20f2531962433959b7, 历史上只出现过 1 次 AnswerUpdated,区块 902149)在 description()/latestRoundData() 上都 execution reverted,判定为已废弃/自毁的旧 feed,按 fail-closed 原则不写入 oracle_feeds(宁可缺记录,不猜一个描述/decimals)。

61 个已确认的 feed 覆盖:

  • 代币化股票(约 40 个,description() 多为 Robinhood <TICKER> / USD 或 RH<TICKER> / USD):AAPL、NVDA、TSLA(RHTSLA)、MSFT(RHMSFT)、GOOGL、AMZN、META、 COIN、PLTR、BABA、TSM、ORCL、MSTR、GME、DELL、QQQ、SPY(RHSPY)、AMD(RHAMD)、 INTC(RHINTC)、CRCL、CRWV、IONQ、RGTI、RKLB、SPCX、CLSK、NBIS、ASML、EWY、SLV、 USO(RHUSO)、SGOV 等。
  • 加密资产:ETH、BTC、WBTC、CBBTC、LINK、LBTC、weETH、wstETH、EURC。
  • 稳定币 / 汇率型 feed(decimals=18,语义是汇率而非美元价): weETH/eETH、wstETH/stETH、Syrup* 系列(USDC/USDT/USDG exchange rate)、USDT/USD、 USDC/USD、USDG/USD、USDE/USD、USDS/USD。
  • 另有 3 个 ... Managed ETH / USD(BOWD ×2、NEXORA、RHOD)——最后更新时间在 2026 年 3 月左右,明显已停更。

完整、权威的清单以 {db}.oracle_feeds 表内容为准(见下方安装步骤),本文档不重复维护 一份会过期的地址表。

数据质量说明(重要,务必读完再用这个数据集)

对 62 个 feed 按"最后一条 AnswerUpdated 所在区块是否落在链头附近(tip − 500,000 区块内,约最近 14 小时)"分类:

  • 31 个 feed 的事件流持续产生到链头附近(含 NVDA)。
  • 31 个 feed 的事件流在区块 ~15,636,111 ~ 17,262,918(对应 2026 年 7 月中下旬) 之间永久停止,此后再也没有任何日志——但通过 eth_call latestRoundData() 直接读取 同一个地址(含 AAPL),返回的却是当天(2026-09-24)的新鲜数据,roundId 远大于历史事件条数(AAPL:日志里只有 272 条 AnswerUpdated,但当前 roundId 已到 668)。也确认了这 31 个地址在停止发日志之后完全没有任何其它 topic0 的日志(不是换了 个新事件签名,是彻底不再 LOG)。

结论:这批 feed 在某个时间点之后的更新方式变了(大概率是底层聚合器实现升级/替换为 更省 gas、不写事件的版本,地址本身没变),导致 oracle_prices 对这 31 个 feed 的 "最新一行" 明显滞后于链上真实状态,不能当作"当前价格"使用。这个数据集对:

  • 历史回放 / 某个时间点当时的价格:所有 62 个 feed 在"仍在发日志"的区间内都可信 (见下方验证)。
  • "现在的价格":只有那 31 个仍在持续发日志的 feed 可以直接用最新一行;另外 31 个 必须对代理合约 eth_call latestRoundData()(不是本表)。这一点会在 {db}.oracle_feeds 之外单独提示,不在本次交付范围内自动修复(需要另开工作项调查 这 31 个聚合器为何停止发日志,是否有替代事件、或是否需要改用状态快照而非事件流)。

表结构

{db}.oracle_feeds(注册表,脚本填充,非 MV)

address        FixedString(20)   -- 聚合器合约地址(不是代理地址)
description    String            -- eth_call description()
decimals       UInt8             -- eth_call decimals()
discovered_at  DateTime('UTC')
version        UInt64            -- ReplacingMergeTree 版本,重跑发现脚本会推进

由 scripts/oracle/discover_feeds.py 两步填充:discover 子命令从 {db}.logs 里找出全部候选地址,resolve 子命令对每个地址 eth_call decimals()/description() 并 upsert。解析失败(如已废弃合约)的地址不写入 (fail-closed,不猜默认值)。

{db}.oracle_prices(MV,来自 {db}.logs)

feed             FixedString(20)
description      Nullable(String)   -- 来自 oracle_feeds JOIN,未注册的 feed 为 NULL
decimals         Nullable(UInt8)    -- 同上,NULL 而不是猜一个 8
round_id         UInt256
answer           Int256             -- 原始整数,未按 decimals 换算,禁止转 float
updated_at       DateTime('UTC')
block_number / block_timestamp / tx_hash / tx_index / log_index
version / is_deleted                -- 从源 log 行原样带过

正确性方案:选 (a) insert-trigger MV,version/is_deleted 从源 log 行原样带过 (与 erc20_transfers 一致),理由:每一条 AnswerUpdated 本身就是一个天然带唯一键 (feed, round_id) 的离散事件,不需要按"已收盘周期"做聚合刷新;oracle_feeds 的 JOIN 只增加描述性列,不改变每条源日志产出的行数,因此 reorg/去重语义与 {db}.logs FINAL 完全一致。

安装与补算

# 0) 替换 {db} 为目标库,例如 robinhood
DB=robinhood

# 1) 建表 + 物化视图(此时 oracle_feeds 还是空的也没关系,先建表结构)
sed "s/{db}/$DB/g" schema/clickhouse/derived/017_oracle_prices.sql | clickhouse-client --multiquery

# 2) 发现 + 解析 feed 注册表(node RPC 走共享节点,默认限速 0.15s/请求,60 个地址约 20 秒)
python3 scripts/oracle/discover_feeds.py discover --db "$DB" \
    --ch-url "$CH_URL" --ch-user "$CH_USER" --ch-password "$CH_PASSWORD" \
    > /tmp/oracle_candidates.txt

python3 scripts/oracle/discover_feeds.py resolve --db "$DB" \
    --rpc-url "$NODE_RPC_URL" \
    --addresses-file /tmp/oracle_candidates.txt \
    --ch-url "$CH_URL" --ch-user "$CH_USER" --ch-password "$CH_PASSWORD"

# 3) 回填历史日志(MV 只处理建视图之后的新数据);oracle_feeds 更新后也可重跑本步骤,
#    历史行的 description/decimals 会从 NULL 变成正确值(ReplacingMergeTree 去重安全)
sed "s/{db}/$DB/g" schema/clickhouse/derived/017_oracle_prices_backfill.sql | clickhouse-client --multiquery

重新发现(例如怀疑有新 feed 上线,或某个 feed 描述变了):重跑第 2、3 步即可,全程幂等。

验证结果(独立计算,精确匹配)

样本:NVDA 聚合器 0xc9d16e4f2569b9e3ea0468fd85844953713dc2a2,区块 72,009,385,round_id = 1100(十六进制 0x44c)。

数据来源round_idanswer(原始整数)updated_at(unix)
{db}.logs 原始行,Python 直接对 topic1/topic2/data 大端解码(独立于 SQL 实现)1100225574720861790317336
017_oracle_prices.sql 里的 SQL 转换逻辑(reinterpretAsInt256(reverse(...)) 等)1100225574720861790317336
对同一地址 eth_call latestRoundData()(生产节点,实时)1100(低 32 位)225574720861790317336

三者逐位精确一致(价格换算:22557472086 / 10^8 = 225.57472086 USD)。

补充:{db}.logs 里符合 AnswerUpdated 形状(topic1/topic2 非空、topic3 为空、 data 长度 32)且能匹配 topic0 的历史行总数为 46,994 条,覆盖 62 个 feed 地址—— 这是回填后 SELECT count() FROM {db}.oracle_prices FINAL WHERE is_deleted=0 (在未来新增行之前)应该精确等于的基线数字,用于验收本表是否完整回填、有无漏行。

生产环境访问记录(供复核)

  • SSH 只读查询:[email protected],clickhouse-client/curl 走 /opt/chain-indexer/clickhouse.env 里的凭据,SETTINGS max_threads=2, max_execution_time=90,全程未写入生产库。
  • 节点 RPC:仅用于 web3_sha3(算 topic0)、eth_getCode、eth_call (decimals()/description()/latestRoundData()/aggregator()/owner()), 单请求间隔 ≥0.15s,未做批量/并发请求。

本页目录