Canonical Backfill + Realtime Merge 与 Reorg 提交协议¶
30 秒版(开场)¶
Backfill 和 realtime 不能按 block number 最后写入者覆盖,而要把不可变 observation 与可变 canonical assignment 分离。每个块按
chain_id + block_hash保留 parent lineage 和 source;候选 head 必须先由链特有 fork-choice/canonical 证据选出,再证明能沿 parent hash 连续走到当前 canonical 祖先。父链连续只是必要条件,不会自动证明它是主链。reorg 先按高度 倒序发布 orphan,再按高度正序 apply 新分支;双通道在重叠区要证明双方观察到同一组 canonical hashes,最大高度相等不代表没有洞。
3 分钟版(精讲深度)¶
核心数据模型:
block_observation(chain_id, hash):height、parent hash、payload/receipt root、provider/source、raw location,不可变。canonical_assignment(chain_id, height):当前 hash、assignment version、finality status,可变但保留变更历史。consumer_watermark:canonical hash + height + assignment version,不能只存 number。reorg_event:common ancestor、orphaned hashes、applied hashes、reason/source evidence。
Realtime 负责低延迟提示,backfill 负责完整性;两者都只能先写 evidence,再由单一 canonicalizer 改 ownership。
10 分钟版(合并算法)¶
flowchart LR
Backfill["archive/backfill"] --> Evidence["immutable evidence by hash"]
Realtime["head/subscription + polling"] --> Evidence
Evidence --> Candidate["candidate head"]
Candidate --> Walk["walk parent hashes to canonical ancestor"]
Walk --> Guard{"continuous and above finalized?"}
Guard -->|no| Hold["gap/conflict queue"]
Guard -->|yes| Commit["atomic canonical assignment + outbox"]
Commit --> Orphan["orphan descending"]
Orphan --> Apply["apply ascending"]
Apply --> Projection["idempotent projections"]
Adopt 的安全步骤¶
- 读取候选
head_hash对应 evidence,不能从“高度最新”反推 hash。 - 沿
parent_hash向后走;每一步验证parent.height + 1 == child.height。 - 遇到当前 canonical 同 hash 的块,得到 common ancestor。
- 若路径缺 parent,进入 gap recovery;不能跳过后直接 adopt。
- 使用链特有 fork choice、共识证明或受信任 canonical source 判断候选权威性;不能只选高度 最大、工作量字段最大或 provider 多数。
- 若候选分支要替换协议 finalized checkpoint 或更低历史,fail closed 并升级链/provider 事件。
- 在同一数据库事务内更新 canonical assignments、assignment version 和 outbox。
- consumer 按 orphan descending 回滚派生状态,再按 applied ascending 重放。
必须分开保存两个水位:
- protocol finality checkpoint:例如 Ethereum
finalized或 BFT commit,来自协议证据; - credit/risk watermark:例如 PoW 的 N confirmations、金额分层或运营策略,只表示业务接受的 概率风险,不应命名为 protocol finality。
两者都不能被一个全链固定 N 抽象掉。若协议发生真正的 finality violation,应进入人工/安全 事件流程;若只是业务阈值内的概率 reorg,则按已声明的冲正和风险流程处理。
Backfill/realtime handoff¶
正确 handoff 不是比较 backfill_max_height == realtime_min_height:
- realtime 先启动并 durable buffer;
- backfill 向前推进到超过目标交接点;
- 对
[H-k, H]每个高度比较 canonical hash,并核对 tx/receipt/event count 或 root/checksum; - gap scanner 证明区间连续,decoder/projection watermark 也赶上;
- 原子转移 range ownership,保留一段 overlap 继续审计。
两个 source 都看到高度 1000,但一个漏了 998,最大值仍相等。watermark 必须带“连续到哪里”的证明,而不是 MAX(height)。
多 provider 分歧¶
同高度不同 hash 是合法 evidence,不能立即以多数票删除少数分支。先验证 parent lineage、协议 finality、节点同步/chain ID 和独立来源。多数一致可作为读路径信号,但不等于共识证明;尤其不能用三个同一上游的 provider 冒充独立 quorum。
一致读取¶
跨多次 RPC 读取一个状态快照时应绑定 block hash。EIP-1898 允许支持的方法用 blockHash,并可用 requireCanonical 要求节点拒绝非 canonical block;只固定 block number 在调用间发生 reorg 时可能读到不一致状态。
可运行示例¶
go test -race ./examples/senior/chainmerge/...
示例证明 overlap hash 覆盖、gap fail closed、reorg rollback/replay 顺序、finalized 不可改写和 orphan evidence 保留。它是内存状态机;生产需要事务、outbox、持久化 raw evidence 和链特有 finality adapter。
生产场景¶
- WebSocket 断线后从持久化的 canonical 扫块水位回补,订阅只负责提示。
- Archive provider 限流时可暂停 backfill,不影响 realtime evidence;切勿悄悄跳过 receipt。
- Decoder bug 修复只重建 facts/projection,不改 raw block 和 canonical history。
- 深 reorg 时按受影响 asset/merchant 冻结派生可用余额,完成 reversal 与对账后恢复。
排查与工具¶
指标:source contiguous watermark、head lag、unknown parent、same-height disagreement、reorg depth、finalized conflict、orphan/apply backlog、projection assignment version、overlap mismatch。每次 handoff 保存区间、hash 清单/checksum 和参与 source 作为证据。
架构取舍¶
单 writer canonicalizer 最容易证明顺序;吞吐可按 chain/network 分片,而不能让同一 chain 的多个 writer 无 CAS 抢高度。大型 projection 可异步消费 outbox,但 canonical commit 与事件发布必须原子关联。
深挖问答¶
- 为什么 block number 不能当主键? → 同高度可因 reorg 有多个 hash;number 是位置,不是不可变身份。
- parent 缺了能先处理后补吗? → evidence 可先缓冲,canonical/projection 不能越洞提交。
- 怎样确认 backfill 已追上? → 连续区间 + canonical hash overlap + 内容对账,不是比较最大高度。
- reorg 为什么先 orphan 倒序? → 先撤销子状态再撤父状态,随后新分支从祖先正序重放。
- finalized 冲突怎么办? → 视作协议/provider/配置安全事件,停止自动覆盖并保全证据。
- parent lineage 连续是否足以选主链? → 否;多个合法分支都可能连续,仍需链特有 fork choice、共识证明或 canonical authority。
反模式与事故¶
UPSERT blocks ON CONFLICT(height) DO UPDATE,把旧分支证据覆盖掉。- Backfill 跑完后才启动 realtime,交接窗口丢块。
- 仅保存
last_processed_height,重启时不知道该高度 hash 是否仍 canonical。 - 多 provider 取多数后直接入账,未验证它们是否同一上游或同步落后。
- consumer 只处理 added event,不支持 orphan/reversal。