0%

StudyRecord-Bsc详解-bsc_relayer同步跨链数据包

引言:

bsc_relayer跨链同步主要涉及两个系统合约,分别为

  • TendermintLightClient.sol: 同步区块头的操作时会调用到
  • CrossChain.sol: 同步跨链数据包的操作会调用到

上篇blog我们搞定了同步区块头的问题,本篇继续解决同步跨链数据包的问题。
目的:

  • 本地搭建起BSC和BC两条链

  • 启动bsc_relayer,能成功与两条链建立连接

  • 在BC上创建新的验证者

  • bsc_relayer要能同步数据成功,在BSC对应合约BSCValidatorSet里,能查询到新增的验证者

BC权益质押管理:

参考币安智能链白皮书,可知BSC的权益质押模块是在BC上实现的,再通过跨链的方式,将质押等信息同步回BSC上

理想情况下,这样的权益质押和奖励发放逻辑应该包含在区块链中,并在产生新区块时自动执行。与币安链一样采用Tendermint共识库的Cosmos Hub就是这样工作的。

自设计之日起,BC就一直在准备启用PoS。另一方面,BSC想要尽可能地与以太坊保持兼容,在其上直接实现PoSA是一个巨大的挑战和压力。特别是考虑到以太坊本身可能在短时间(或更长时间)内迁移到PoS共识协议时,尤其如此。为了保持以太坊的兼容性和复用BC的基础架构,我们在BC上完成了BSC的权益质押逻辑

  1. 质押代币是 BNB,这是因为它是两个区块链上的原生代币。

  2. 在BC上记录BSC的权益质押和委托行为。

  3. BSC 验证人集由它的权益质押和委托逻辑来决定,在BC上构建一个BSC的权益质押模块,并通过跨链通信在每天UTC 00:00:00 由BC传送到BSC。

  4. BC上的奖励分配发生在每天UTC 00:00时刻。

本篇我们主要解决的是,与权益质押管理相关的跨链数据包的同步问题。

同步跨链数据包调用图:

20220926164613
如图所示,bsc-relayer会构造tx,调用系统合约CrossChain,通过默克尔树校验后,再调用其他合约(如BSCValidatorSet.sol)完成具体跨链数据包的处理与同步。

现在先来看看系统合约/跨链合约CrossChain.sol的源码及其实现

系统合约CrossChain:

简介:

负责执行bsc-relayer发送过来的“同步跨链数据包”的交易,进行跨链包预处理,通过emit事件发送跨链包到BC.
主要处理逻辑是:

  1. 调用预编译合约iavlMerkleProofValidate对跨链数据包进行校验

  2. 根据不同的channelId,内部调用对应的系统合约,进行具体的数据包处理

简言之,主要做的工作是跨链数据包的校验路由工作。

初始化函数:

contracts/CrossChain.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 初始化函数
// @dev 从System.sol读取配置变量,在registeredContractChannelMap映射中,建立channelId=>系统合约的映射
function init() external onlyNotInit {
// TokenManager Contract | 跨链代币管理(绑定/解绑)合约 | 用于`BC`和`BSC`两边代币的绑定/解绑
// BIND_CHANNELID: 1
channelHandlerContractMap[BIND_CHANNELID] = TOKEN_MANAGER_ADDR;
isRelayRewardFromSystemReward[BIND_CHANNELID] = false;
registeredContractChannelMap[TOKEN_MANAGER_ADDR][BIND_CHANNELID] = true;

// TokenHub Contract | 跨链代币转移合约 | 用于`BC`与`BSC`的跨链代币转移
// TRANSFER_IN_CHANNELID: 2
channelHandlerContractMap[TRANSFER_IN_CHANNELID] = TOKEN_HUB_ADDR;
isRelayRewardFromSystemReward[TRANSFER_IN_CHANNELID] = false;
registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_IN_CHANNELID] = true;

// TRANSFER_OUT_CHANNELID: 3
channelHandlerContractMap[TRANSFER_OUT_CHANNELID] = TOKEN_HUB_ADDR;
isRelayRewardFromSystemReward[TRANSFER_OUT_CHANNELID] = false;
registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_OUT_CHANNELID] = true;

// BSCValidatorSet Contract | 智能链验证者集合合约 | 用于 BC 更新 bsc-validator 验证者节点地址列表(查询or更新)
// STAKING_CHANNELID: 8
channelHandlerContractMap[STAKING_CHANNELID] = VALIDATOR_CONTRACT_ADDR;
isRelayRewardFromSystemReward[STAKING_CHANNELID] = true;
registeredContractChannelMap[VALIDATOR_CONTRACT_ADDR][STAKING_CHANNELID] = true;

// GovHub Contract | 治理管理合约 | 处理来自`BC`上的链上治理数据包
// GOV_CHANNELID: 9
channelHandlerContractMap[GOV_CHANNELID] = GOV_HUB_ADDR;
isRelayRewardFromSystemReward[GOV_CHANNELID] = true;
registeredContractChannelMap[GOV_HUB_ADDR][GOV_CHANNELID] = true;

// Liveness Slash Contract | 惩罚合约 |用于惩罚违规操作的 bsc-validator
// SLASH_CHANNELID: 11
channelHandlerContractMap[SLASH_CHANNELID] = SLASH_CONTRACT_ADDR;
isRelayRewardFromSystemReward[SLASH_CHANNELID] = true;
registeredContractChannelMap[SLASH_CONTRACT_ADDR][SLASH_CHANNELID] = true;

batchSizeForOracle = INIT_BATCH_SIZE;

oracleSequence = - 1;
previousTxHeight = 0;
txCounter = 0;

alreadyInit = true;
}

在初始化函数里,主要做的工作是从System.sol读取配置变量,在registeredContractChannelMap映射中,建立channelId=>系统合约地址的映射。
有以下映射:

ContractName ContractNameCN Role ChannelID
TokenManager Contract 跨链代币管理(绑定/解绑)合约 用于BCBSC两边代币的绑定/解绑 1
TokenHub Contract 跨链代币转移合约 用于BCBSC的跨链代币转移 2,3
BSCValidatorSet Contract 智能链验证者集合合约 用于 BC 更新 bsc-validator 验证者节点地址列表(查询or更新) 8
GovHub Contract 治理管理合约 处理来自BC上的链上治理数据包 9
Liveness Slash Contract 惩罚合约 用于惩罚违规操作的 bsc-validator 11

处理跨链数据包函数:

contracts/CrossChain.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 处理跨链数据包
// modifier检验:
// onlyRelayer 仅允许中继器调用
// sequenceInOrder 检验sequence序列号是否按顺序
// blockSynced 在轻客户端合约中,该区块是否已经同步
// channelSupported 该通道是否已经注册,相当于channelId白名单机制
// @param payload 编码过的跨链数据包
// @param proof
// @param height 区块高度
// @param packageSequence 跨链数据包序列号
// @param channelId 通道ID
function handlePackage(bytes calldata payload, bytes calldata proof, uint64 height, uint64 packageSequence, uint8 channelId) onlyInit onlyRelayer
sequenceInOrder(packageSequence, channelId) blockSynced(height) channelSupported(channelId) external {
bytes memory payloadLocal = payload;
// fix error: stack too deep, try removing local variables
bytes memory proofLocal = proof;
// fix error: stack too deep, try removing local variables
// 默克尔树根校验
require(MerkleProof.validateMerkleProof(ILightClient(LIGHT_CLIENT_ADDR).getAppHash(height), STORE_NAME, generateKey(packageSequence, channelId), payloadLocal, proofLocal), "invalid merkle proof");
// 同步该区块的中继器地址
address payable headerRelayer = ILightClient(LIGHT_CLIENT_ADDR).getSubmitter(height);

uint8 channelIdLocal = channelId;
// fix error: stack too deep, try removing local variables
// 解码跨链数据包
(bool success, uint8 packageType, uint256 relayFee, bytes memory msgBytes) = decodePayloadHeader(payloadLocal);
if (!success) {
emit unsupportedPackage(packageSequence, channelIdLocal, payloadLocal);
return;
}
emit receivedPackage(packageType, packageSequence, channelIdLocal);
if (packageType == SYN_PACKAGE) {
// 根据channelId获取对应的系统合约地址
address handlerContract = channelHandlerContractMap[channelIdLocal];
// 调用具体的系统合约处理跨链数据包
try IApplication(handlerContract).handleSynPackage(channelIdLocal, msgBytes) returns (bytes memory responsePayload) {
if (responsePayload.length != 0) {
sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(ACK_PACKAGE, 0, responsePayload));
channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1;
}
} catch Error(string memory reason) {
sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(FAIL_ACK_PACKAGE, 0, msgBytes));
channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1;
emit unexpectedRevertInPackageHandler(handlerContract, reason);
} catch (bytes memory lowLevelData) {
sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(FAIL_ACK_PACKAGE, 0, msgBytes));
channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1;
emit unexpectedFailureAssertionInPackageHandler(handlerContract, lowLevelData);
}
} else if (packageType == ACK_PACKAGE) {
address handlerContract = channelHandlerContractMap[channelIdLocal];
try IApplication(handlerContract).handleAckPackage(channelIdLocal, msgBytes) {
} catch Error(string memory reason) {
emit unexpectedRevertInPackageHandler(handlerContract, reason);
} catch (bytes memory lowLevelData) {
emit unexpectedFailureAssertionInPackageHandler(handlerContract, lowLevelData);
}
} else if (packageType == FAIL_ACK_PACKAGE) {
address handlerContract = channelHandlerContractMap[channelIdLocal];
try IApplication(handlerContract).handleFailAckPackage(channelIdLocal, msgBytes) {
} catch Error(string memory reason) {
emit unexpectedRevertInPackageHandler(handlerContract, reason);
} catch (bytes memory lowLevelData) {
emit unexpectedFailureAssertionInPackageHandler(handlerContract, lowLevelData);
}
}
IRelayerIncentivize(INCENTIVIZE_ADDR).addReward(headerRelayer, msg.sender, relayFee, isRelayRewardFromSystemReward[channelIdLocal] || packageType != SYN_PACKAGE);
}

一开始有多个modifier检验:

  • onlyRelayer: 仅允许中继器调用

  • sequenceInOrder: 检验sequence序列号是否按顺序

  • blockSynced: 在轻客户端合约中,该区块是否已经同步

  • channelSupported: 该通道是否已经注册,相当于channelId白名单机制

能够成功同步区块头数据是同步跨链数据包的前提。

其中关键的代码段是:

1
2
3
4
5
6
7
8
9
10
11
if (packageType == SYN_PACKAGE) {
// 根据channelId获取对应的系统合约地址
address handlerContract = channelHandlerContractMap[channelIdLocal];
// 调用具体的系统合约处理跨链数据包
try IApplication(handlerContract).handleSynPackage(channelIdLocal, msgBytes) returns (bytes memory responsePayload) {
if (responsePayload.length != 0) {
sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(ACK_PACKAGE, 0, responsePayload));
channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1;
}
}
....

并不是在跨链合约CrossChain中进行具体的数据包同步逻辑,CrossChain主要起到跨链数据包校验、路由的作用,根据不同的channelId,CrossChain会去调用对应的系统合约,由它们各自进行具体的跨链数据包处理。

完整验证步骤:

  1. 初始化并运行起BC链

  2. 获取新的初始化共识状态数据,生成新的genesis.json,初始化并运行起BSC链

  3. BC链上,新增验证者

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# bnbcli staking bsc-create-validator --chain-id 715 --from bnb186t4z2pdu02khlfn5skqxpd36fwsa393mgu4hq --amount 2100000000000:BNB --moniker jerry_v1 --commission-rate 80000000 --commission-max-rate 95000000 --commission-max-change-rate 3000000 --side-chain-id bsc --side-cons-addr 0x86fDBAE16949433E5338846f75764067219fb221 --side-fee-addr 0x86fDBAE16949433E5338846f75764067219fb221

Password to sign with 'jerry':
Committed at block 919 (tx hash: 7E3B7A1038DD9C55FF3B476A1255C68D3FD17A11A876B66A5BFBDA4C9D83ECDD, response: {Code:0 Data:[] Log:Msg 0: Info: GasWanted:0 GasUsed:0 Events:[{Type: Attributes:[{Key:[100 101 115 116 105 110 97 116 105 111 110 45 118 97 108 105 100 97 116 111 114] Value:[98 118 97 49 56 54 116 52 122 50 112 100 117 48 50 107 104 108 102 110 53 115 107 113 120 112 100 51 54 102 119 115 97 51 57 51 109 53 97 57 102 121] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[109 111 110 105 107 101 114] Value:[106 101 114 114 121 95 118 49] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[105 100 101 110 116 105 116 121] Value:[] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[97 99 116 105 111 110] Value:[115 105 100 101 95 99 114 101 97 116 101 95 118 97 108 105 100 97 116 111 114] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] Codespace: XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0})

[root@localhost ~]# bnbcli staking bsc-create-validator --chain-id 715 --from bnb1v64323yrzekauf89qhsfrvyhe7xsvsck00jwpv --amount 2200000000000:BNB --moniker six_v1 --commission-rate 80000000 --commission-max-rate 95000000 --commission-max-change-rate 3000000 --side-chain-id bsc --side-cons-addr 0xeE8620E0DEF0129c9b7a2F46C8C1C364B0869752 --side-fee-addr 0xeE8620E0DEF0129c9b7a2F46C8C1C364B0869752
Password to sign with 'six':

Committed at block 927 (tx hash: 35D7FB25C9BB6189E0B097A01F4C0839133F85ACBF0CBC24CC432198E9E4D331, response: {Code:0 Data:[] Log:Msg 0: Info: GasWanted:0 GasUsed:0 Events:[{Type: Attributes:[{Key:[100 101 115 116 105 110 97 116 105 111 110 45 118 97 108 105 100 97 116 111 114] Value:[98 118 97 49 118 54 52 51 50 51 121 114 122 101 107 97 117 102 56 57 113 104 115 102 114 118 121 104 101 55 120 115 118 115 99 107 48 110 110 55 108 103] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[109 111 110 105 107 101 114] Value:[115 105 120 95 118 49] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[105 100 101 110 116 105 116 121] Value:[] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[97 99 116 105 111 110] Value:[115 105 100 101 95 99 114 101 97 116 101 95 118 97 108 105 100 97 116 111 114] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] Codespace: XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0})

  1. 在BC上,查询top验证者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@localhost ~]# bnbcli staking  side-top-validators --trust-node --side-chain-id="bsc"
Validator
Fee Address: bnb1v64323yrzekauf89qhsfrvyhe7xsvsck00jwpv
Operator Address: bva1v64323yrzekauf89qhsfrvyhe7xsvsck0nn7lg
Validator Consensus Pubkey:
Jailed: false
Status: Unbonded
Tokens: 2200000000000
Delegator Shares: 2200000000000
Description: {six_v1 }
Bond Height: 0
Unbonding Height: 0
Minimum Unbonding Time: 1970-01-01 00:00:00 +0000 UTC
Commission: {rate: 80000000, maxRate: 95000000, maxChangeRate: 3000000, updateTime: 2022-09-26 08:15:29.716841534 +0000 UTC}
Distribution Addr: bnb1880pqwsmlzt58q57r7u5ycgn8fxnwxvxqppm9k
Side Chain Id: bsc
Consensus Addr on Side Chain: 0xeE8620E0DEF0129c9b7a2F46C8C1C364B0869752
Fee Addr on Side Chain: 0xeE8620E0DEF0129c9b7a2F46C8C1C364B0869752

Validator
Fee Address: bnb186t4z2pdu02khlfn5skqxpd36fwsa393mgu4hq
Operator Address: bva186t4z2pdu02khlfn5skqxpd36fwsa393m5a9fy
Validator Consensus Pubkey:
Jailed: false
Status: Unbonded
Tokens: 2100000000000
Delegator Shares: 2100000000000
Description: {jerry_v1 }
Bond Height: 0
Unbonding Height: 0
Minimum Unbonding Time: 1970-01-01 00:00:00 +0000 UTC
Commission: {rate: 80000000, maxRate: 95000000, maxChangeRate: 3000000, updateTime: 2022-09-26 08:15:21.651313673 +0000 UTC}
Distribution Addr: bnb1v839g444p5hlvk6ghe6ap4p4y7wnl83p5x0qn6
Side Chain Id: bsc
Consensus Addr on Side Chain: 0x86fDBAE16949433E5338846f75764067219fb221
Fee Addr on Side Chain: 0x86fDBAE16949433E5338846f75764067219fb221
  1. 启动bsc-relayer,拉取BC数据,同步到BSC上

  2. 查看区块浏览器的结果:
    20220926172659
    调用CrossChain系统合约的交易均成功

  3. 查询系统合约BSCValidatorSet里的数据

20220926172805
20220926172818
能查到新的验证者数据。

Refs: