-
Notifications
You must be signed in to change notification settings - Fork 27
异构链数据验证服务开发手册
HCDVS(Hetero-Chain Data Verifier Service,异构链数据验证服务)是 AntChain Bridge 的可信验证层。它验证异构链数据是否真实、完整、可被对应链的共识规则和账本证明规则接受。BBC 负责把链上数据读出来,HCDVS 负责判断这些数据能不能被信任。
这份手册同样围绕下面的跨链方向展开:
eth.from.domain -> Relayer1 -> Relayer2 -> fisco.to.domain
这个方向中,源链是 Ethereum2,所以源链消息验证主要由 Ethereum2 HCDVS 完成;目标链是 FISCO BCOS,所以投递写链由 FISCO BCOS BBC 完成。若方向反过来,源链变成 FISCO BCOS,消息验证就由 FISCO BCOS HCDVS 完成。理解这一点后,双 Relayer 场景就不会被误解成“两个 Relayer 互相信任”,真正的信任来自 PTC、TpBTA 和 HCDVS 验证结果。
PTC 是调用者,HCDVS 是验证执行者。Committee PTC 的每个节点在背书前都会调用对应 product 的 HCDVS:
-
product=ethereum2时,调用 Ethereum2 HCDVS。 -
product=fiscobcos时,调用 FISCO BCOS HCDVS。
PTC 聚合多个节点的验证结果,形成 TpBTA 或跨链消息背书。四节点 Committee PTC 下,本文默认 threshold > 2,即至少 3 个节点验证通过。
flowchart LR
R["Relayer"] --> P["Committee PTC"]
P --> N1["Committee Node 1"]
P --> N2["Committee Node 2"]
P --> N3["Committee Node 3"]
P --> N4["Committee Node 4"]
N1 --> EH["Ethereum2 HCDVS"]
N2 --> EH
N3 --> EH
N4 --> EH
EH --> ER["VerifyResult"]
P --> B["Embedded BCDNS"]
B --> T["TpBTA / PTC Trust Root"]
Relayer 可以把消息从 Relayer1 传到 Relayer2,但目标链是否接受消息,取决于 PTC 背书和链上 PTC Hub 中的 TpBTA。HCDVS 验证通过是可信消息链路的核心条件。
BTA(Blockchain Trust Anchor,区块链信任锚)描述一条链的初始可信身份。注册 TpBTA 时,PTC 会先验证 BTA 中描述的初始状态是否可信。
subject identity 是 BTA 中最关键的链特定字段,由各插件自定义序列化格式:
- Ethereum2:通常包含
eth2ChainConfig、current sync committee、next sync committee 等。 - FISCO BCOS:通常包含 AM 合约地址、sealer list 或可推导共识身份的信息。
subject identity 不应写成通用 JSON 模板后跨链复用。Ethereum2 和 FISCO BCOS 的可信身份完全不同,HCDVS 只会按对应 product 的格式解析。
BTA 还会携带 AM 合约身份。HCDVS 必须检查共识状态中的 AM 合约地址和 BTA 中的 AM 身份一致,否则攻击者可能把其他合约日志伪装成跨链消息。
ConsensusState 是统一的共识状态封装。它本身不要求所有链使用同一种区块头结构,而是把链特定数据放入 stateData、consensusNodeInfo 和 endorsements。
Ethereum2 中:
-
height表示 Beacon slot。 -
hash表示 Beacon block root。 -
parentHash表示父 Beacon block root。 -
stateData包含 Beacon header、execution payload header、light client update 等。 -
endorsements包含 sync aggregate 和 signature slot。
如果某个 slot missed,BBC 可能返回空 root 状态,HCDVS 应识别该状态不能承载跨链消息证明。消息验证不能从 missed slot 通过。
FISCO BCOS 中:
-
height表示区块高度。 -
hash表示区块 hash。 -
parentHash表示父区块 hash。 -
stateData包含 transactions root、receipts root、state root 等。 -
consensusNodeInfo包含 sealer list。 -
endorsements包含区块签名集合。
CrossChainMessage 表示 BBC 从源链读出的跨链消息。核心字段包括:
-
message:AM 原文。 -
type:消息类型,通常是 AUTH_MSG。 -
provableData:证明这条消息存在于源链账本中的数据。
ledgerData 保存包含 AM 消息原文的账本数据。Ethereum2 通常保存 AM log 相关 JSON;FISCO BCOS 通常保存 transaction receipt JSON。
proof 保存账本存在性证明。Ethereum2 侧一般是 receipt proof;FISCO BCOS 侧一般包含 receiptHash、txReceiptProof 和 receiptsRoot。
这些字段用于把消息和链上交易、区块、时间关联起来。HCDVS 不能只校验 message 字节相等,还必须确认它确实来自对应交易和区块。
注册 TpBTA 时,PTC 使用 verifyAnchorConsensusState 验证第一份 anchor state。该状态一旦通过,会成为后续共识状态推进的起点。
Ethereum2 需要验证:
- BTA 中的 sync committee 大小与链配置一致。
- anchor state 中 AM 合约地址与 BTA AM 身份一致。
- sync committee 签名能验证 Beacon 状态。
- 如 anchor state 位于 period 边界,正确处理 next sync committee。
FISCO BCOS 需要验证:
- BTA 中 AM 合约地址与状态中的 AM 身份一致。
-
stateData中关键区块状态字段完整。 -
endorsements中签名来自 sealer list。 - 有效签名数量满足多数阈值。
Anchor 服务后续推进源链可信状态时,PTC 使用父状态验证子状态。通过后,子状态可以成为新的可信状态。
Ethereum2 的难点是 sync committee period 切换。如果子状态位于当前 period 最后 slot,HCDVS 需要验证 light client update,并把 next sync committee 切换为 current sync committee。
如果 next sync committee 没有正确进入 consensusNodeInfo,后续状态可能出现 sync committee signature is invalid 或 missing next sync committee for endorsements。
FISCO 需要检查子区块 parentHash 是否等于父状态 hash,再用父状态中的 sealer list 验证子区块签名。
Ethereum2 验证消息时,需要证明 AM log 存在于交易 receipt,receipt proof 计算出的 root 等于 execution payload header 中的 receipts root,且 log topic、logger 地址、log data 都和 AM 消息一致。
FISCO 验证消息时,需要证明 transaction receipt 存在于区块 receipts root 下,再从 receipt 的 log entries 中找到 SendAuthMessage 事件,并确认事件消息和 CrossChainMessage.message 一致。
Ethereum2 从 ledgerData 中解析 AM log,再用合约 ABI 提取非 indexed 参数中的 AM 原文。
FISCO 从 transaction receipt JSON 的 logEntries 中查找 SendAuthMessage 事件,解析出事件里的消息字节。
Beacon header 提供 slot、root、parent root 等共识层信息。HCDVS 会校验 ConsensusState.height/hash/parentHash 与 Beacon header 一致。
sync committee 是验证 Beacon 状态签名的核心。BTA subject identity 中保存初始 current sync committee;后续状态推进时可能维护 next sync committee。
light client update 用于在 period 边界更新 sync committee。没有正确处理它,链在 period 切换后会出现签名验证失败。
跨链消息存在性最终要落到 execution payload header 的 receipts root。receipt proof 计算得到的 root 必须等于这个字段。
- 从 BTA subject identity 解析 Ethereum2 链配置和 current sync committee。
- 从 anchor state 的
stateData解析 Ethereum2 共识状态。 - 比对 AM 合约地址。
- 从
endorsements解析 sync aggregate。 - 验证共识状态签名和区块数据。
- 必要时更新 next/current sync committee。
验证成功后,Ethereum2 HCDVS 会把更新后的 sync committee 信息写入 anchorState.consensusNodeInfo。这一步不是可选项,否则后续 verifyConsensusState 没有可信 committee 输入。
// 从 BTA 中解析 Ethereum2 初始可信身份;这是验证 sync committee 的来源。
var subject = EthSubjectIdentity.fromJson(new String(bta.getSubjectIdentity()));
// 解析 BBC 放入 stateData 的 Ethereum2 专有共识状态数据。
// schema/spec 必须来自同一个 eth2ChainConfig,否则反序列化和验证都会错位。
var data = EthConsensusStateData.fromJson(new String(anchorState.getStateData()), schema, spec);
// 校验 AM 合约身份,防止其他合约事件被伪装成跨链系统消息。
if (!amInBta.equals(data.getAmContract())) {
return VerifyResult.fail("invalid am contract");
}
// 解析 sync aggregate;committee size 必须和链配置一致。
var endorsements = EthConsensusEndorsements.fromJson(new String(anchorState.getEndorsements()), committeeSize);
// 执行 Ethereum2 共识状态验证,包括 sync committee 签名和 light client 相关校验。
data.validate(subject.getCurrentSyncCommittee(), endorsements, subject.getEth2ChainConfig());
// 把更新后的 sync committee 状态写回共识状态,供后续 verifyConsensusState 继承。
anchorState.setConsensusNodeInfo(subject.toJson().getBytes());
// 所有检查通过后,PTC 节点才可以继续产生背书。
return VerifyResult.success();- 从父状态
consensusNodeInfo读取 current/next sync committee。 - 解析父状态和子状态的 Ethereum2 数据。
- 校验 AM 合约地址不变。
- 校验子状态 slot、root、parent root。
- 如遇 period 边界,校验 light client update。
- 选择正确的 sync committee 验证 endorsements。
- 更新子状态
consensusNodeInfo。
// 父状态已经被验证过,因此其中保存的 sync committee 状态是当前可信输入。
var subject = EthSubjectIdentity.fromJson(new String(parentState.getConsensusNodeInfo()));
// 解析待验证状态;schema/spec 必须和父状态来自同一条 Ethereum2 链配置。
var curr = EthConsensusStateData.fromJson(new String(stateToVerify.getStateData()), schema, spec);
// 解析父状态,用于检查 AM 合约、父子 root 和 period 边界。
var parent = EthConsensusStateData.fromJson(new String(parentState.getStateData()), schema, spec);
// 同一条源链的 AM 合约不应在连续状态中无故变化。
if (!parent.getAmContract().equals(curr.getAmContract())) {
return VerifyResult.fail("am contract not equal");
}
// 验证 Beacon 链父子链接关系,子状态 parentRoot 必须等于父状态 root。
if (!curr.getBeaconBlockHeader().getParentRoot().equals(parent.getBeaconBlockHeader().getRoot())) {
return VerifyResult.fail("invalid parent hash");
}
// 使用选定的 sync committee 验证 block endorsement;period 边界时可能使用 next committee。
curr.validateBlock(committeeForBlockVerification, endorsements, subject.getEth2ChainConfig());
// 把更新后的 committee 信息写入子状态,作为下一轮状态验证的可信输入。
stateToVerify.setConsensusNodeInfo(subject.toJson().getBytes());- 解码 message 中的 receipt proof。
- 从当前共识状态读取 execution payload receipts root。
- 验证 receipt proof 计算出的 root。
- 从 ledgerData 中解析 AM log。
- 校验 log index、topic、logger 地址和 log data。
- 返回成功。
Ethereum2 消息验证不是只检查 AM 原文是否相等,还要检查 receipt proof、log topic、logger 地址与 execution payload receipts root 的整条链路。
// 解码 BBC 构造的 receipt proof;证明格式必须和 BBC 侧完全一致。
var proof = EthReceiptProof.decodeFromJson(new String(message.getProvableData().getProof()));
// 解析当前可信共识状态,读取 execution payload header。
var data = EthConsensusStateData.fromJson(new String(currState.getStateData()), schema, spec);
// 通过 receipt proof 重新计算 receipts root。
var rootCalc = proof.validateAndGetRoot();
// 从可信 Beacon 状态中的 execution payload header 取期望 receipts root。
var rootExpect = data.getExecutionPayloadHeader().getReceiptsRoot();
// 两个 root 不一致,说明 receipt 不属于当前可信区块。
if (rootCalc.compareTo(rootExpect) != 0) {
return VerifyResult.fail("receipt root not equal");
}
// 解析账本数据中的 AM log,后续会与 proof 里的 log 逐项比对。
var ledgerLog = EthAuthMessageLog.decodeFromJson(new String(message.getProvableData().getLedgerData()));
// 确保 proof 中的日志发出者就是当前共识状态记录的 AM 合约地址。
if (!msgLogInProof.getLogger().equals(data.getAmContract())) {
return VerifyResult.fail("logger not am contract");
}sealer list 是 FISCO 共识节点集合。HCDVS 用它验证区块签名是否来自合法节点。
四节点标准链下,不能只验证一个签名。应按多数阈值验证,例如四节点至少需要 3 个有效签名。
FISCO 的跨链消息存在性依赖 transaction receipt 和 Merkle proof。HCDVS 需要确认 proof 中的 receipts root 与共识状态中的 receipts root 一致。
- 从 BTA subject identity 解析 AM 合约地址。
- 校验 AM 地址与 BTA AM 身份一致。
- 解析 anchor state 的
stateData。 - 解析
endorsements和consensusNodeInfo。 - 计算最低签名数量。
- 验证签名有效数量。
// FISCO BTA subject identity 使用 JSON 表达链特定身份。
JSONObject subject = JSON.parseObject(new String(bta.getSubjectIdentity()));
// 取出 AM 合约地址;后续所有跨链消息都必须来自该 AM 合约。
String amContract = subject.getString("amContract");
// 检查 subject identity 中的 AM 地址与 BTA 标准字段中的 AM 身份一致。
if (!checkAmContract(decode(amContract), bta.getAmId())) {
return VerifyResult.fail("AM合约地址不匹配");
}
// 解析 BBC 写入的区块状态数据,例如 transactionsRoot、receiptsRoot、stateRoot。
JSONObject stateData = JSON.parseObject(new String(anchorState.getStateData()));
// 取区块签名集合;为空时不能继续做可信验证。
JSONArray signatures = endorsements.getJSONArray("signatures");
// 取共识节点集合;签名必须来自这些合法 sealer。
JSONArray sealers = consensusNodeInfo.getJSONArray("sealerList");
// 按 sealer 数量计算最低有效签名数,四节点通常至少需要 3 个。
int min = calculateMinRequiredSignatures(sealers.size());
// 逐个验证签名是否由合法 sealer 产生,并统计有效签名数量。
int valid = verifySignatures(signatures, sealers, anchorState.getHashHex());- 解析父子状态数据。
- 检查父子状态字段完整。
- 校验子区块
parentHash等于父区块hash。 - 从父状态读取 sealer list。
- 用父状态 sealer list 验证子状态签名。
// 父状态必须能解析成 FISCO 区块状态数据。
JSONObject parentData = JSON.parseObject(new String(parentState.getStateData()));
// 子状态也必须能解析成 FISCO 区块状态数据。
JSONObject childData = JSON.parseObject(new String(stateToVerify.getStateData()));
// 取子状态中的 parentHash,用于验证链式关系。
String childParentHash = HexUtil.encodeHexStr(stateToVerify.getParentHash());
// 子状态必须接在父状态之后,否则状态推进不可信。
if (!childParentHash.equalsIgnoreCase(parentState.getHashHex())) {
return VerifyResult.fail("区块链接关系验证失败");
}
// 父状态的 sealer list 是验证子区块签名的可信输入。
JSONArray sealers = parentConsensusNodeInfo.getJSONArray("sealerList");
// 子状态必须携带区块签名集合。
JSONArray signatures = childEndorsements.getJSONArray("signatures");
// 验证子区块签名是否来自合法 sealer,且有效数量满足多数阈值。
int valid = verifySignatures(signatures, sealers, stateToVerify.getHashHex());- 检查消息带有
provableData。 - 校验消息高度与当前共识状态高度一致。
- 从共识状态读取 receipts root。
- 从
ledgerData解析 transaction receipt。 - 从
proof解析 receipt proof。 - 验证 proof root 与状态 receipts root 一致。
- 查找
SendAuthMessage事件。 - 校验事件消息与
CrossChainMessage.message一致。
FISCO BCOS 的 ledgerData 和 proof 必须来自同一笔交易回执和同一区块。只要其中一个字段复用了旧数据,收据根或交易哈希校验就会失败。
// 没有证明数据就不能做可信验证,即使 AM 原文看起来正确也不能通过。
if (message.getProvableData() == null) {
return VerifyResult.fail("跨链消息没有可证明数据");
}
// 消息必须属于当前共识状态对应的区块高度。
if (messageHeight != currState.getHeight().longValue()) {
return VerifyResult.fail("消息高度与当前状态高度不匹配");
}
// 从可信状态中取 receiptsRoot,作为 proof 验证目标。
String receiptsRootInState = stateData.getString("receiptsRoot");
// 解析 BBC 放入 ledgerData 的 FISCO transaction receipt。
JSONObject receiptObj = JSON.parseObject(new String(message.getProvableData().getLedgerData()));
// 解析 BBC 放入 proof 的 receiptHash、txReceiptProof、receiptsRoot。
JSONObject proofObj = JSON.parseObject(new String(message.getProvableData().getProof()));
// proof 声称的 receiptsRoot 必须等于区块状态中的 receiptsRoot。
if (!proofReceiptsRoot.equalsIgnoreCase(receiptsRootInState)) {
return VerifyResult.fail("收据根不匹配");
}
// 在交易回执日志中查找 AM 合约发出的 SendAuthMessage 事件。
AuthMessageEventResult event = findAuthMessageEvent(receiptObj.getJSONArray("logEntries"));
// 事件中的 AM 原文必须与 CrossChainMessage.message 完全一致。
if (!Arrays.equals(event.getMessage(), message.getMessage())) {
return VerifyResult.fail("事件中的消息内容与跨链消息不一致");
}FISCO 的 parseMessageFromLedgerData 会把 ledgerData 当作 transaction receipt JSON 解析,然后查找 SendAuthMessage 事件,返回事件里的消息原文。
// BBC 把 FISCO transaction receipt 序列化后放入 ledgerData。
String receiptJson = new String(ledgerData);
// 按同一格式反序列化为 JSON,便于读取日志。
JSONObject receiptObj = JSON.parseObject(receiptJson);
// 获取交易回执中的所有 log entries。
JSONArray logEntries = receiptObj.getJSONArray("logEntries");
// 查找并解析 SendAuthMessage 事件。
AuthMessageEventResult event = findAuthMessageEvent(logEntries);
// 找到事件后返回 AM 原文;该值应与 CrossChainMessage.message 一致。
if (event != null) {
return event.getMessage();
}
// 没有找到事件时返回空字节,上层验证通常会因此失败或报错。
return new byte[0];在 eth.from.domain -> Relayer1 -> Relayer2 -> fisco.to.domain 中,Relayer1 和 Relayer2 只是负责传输和调度。可信性来自 PTC 背书、HCDVS 验证和链上 PTC Hub 中保存的可信材料。
TpBTA 上传到 Embedded BCDNS 后,其他 Relayer 可以按源域名查询。目标链验证消息时,需要知道源链 BTA 已经被可信 PTC 验证过。
四节点 Committee PTC 中,四个节点都可以背书。threshold > 2 表示至少三个节点验证通过。只启动一两个节点不符合本文路线。
sequenceDiagram
participant R1 as "Relayer1"
participant P as "Committee PTC"
participant EH as "Ethereum2 HCDVS"
participant B as "Embedded BCDNS"
participant R2 as "Relayer2"
participant FB as "FISCO BBC"
participant F as "FISCO BCOS"
R1->>P: 提交 Ethereum2 ConsensusState 和 CrossChainMessage
P->>EH: verifyConsensusState
P->>EH: verifyCrossChainMessage
EH-->>P: VerifyResult.success
P->>B: 查询或发布 TpBTA
R1->>R2: 传递已验证消息
R2->>FB: relayAuthMessage
FB->>F: 调用 AM/SDP/业务合约
若从 fisco.to.domain 发往 eth.from.domain,PTC 调用的是 FISCO BCOS HCDVS,验证 FISCO 区块签名、receipt root、Merkle proof 和 SendAuthMessage 事件;目标链投递则由 Ethereum2 BBC 完成。
优先检查 eth2ChainConfig 是否来自当前 Beacon API,BTA subject identity 是否用当前链当前 slot 生成,slot 是否靠近 sync committee period 边界。私链重建后继续使用旧配置是最常见原因。
说明 receipt proof 计算出的 root 和 execution payload header 中的 receipts root 不一致。重点检查消息的 ledgerData、proof、slot、交易哈希是否来自同一个区块。
说明状态位于 period 边界,但缺少可验证的 light client update。应等待可用 update,或避开边界 slot 构造初始 BTA;如果 Beacon 返回 Fulu 而插件期望 Deneb,应重建链。
检查 consensusNodeInfo 中 sealer list 是否完整,endorsements 中签名是否来自当前区块,四节点链是否至少有多数有效签名。不要通过降低验证阈值绕过。
检查 proof.receiptsRoot、stateData.receiptsRoot、transaction receipt 是否来自同一高度同一区块。若使用缓存或复用旧消息,很容易出现 root 不匹配。
确认扫描的合约地址是 AM 合约,不是 SDP 或业务合约。还要确认业务调用确实触发了 SDP 发送跨链消息,而不是只更新了本地状态。
常见原因是 Committee Node 的插件目录、classpath 或 native library 路径与本地单测不同。FISCO BCOS 尤其要确认 WedPR native library 能被 Committee Node JVM 加载。
BBC 负责写入 stateData、ledgerData、proof;HCDVS 负责读取这些 bytes。任何字段名、编码、大小写、十六进制前缀不一致,都会导致验证失败。新增链插件时,应把 BBC/HCDVS 的序列化契约写成单测。
先确认 PTC 注册 BTA 成功,再确认 TpBTA 已上传到 BCDNS,最后确认目标链 PTC Hub 中能查询到对应源域名的 TpBTA。双 Relayer 场景中,不要把“Relayer 能通信”误认为“目标链已经信任源链”。