引言:
目的:
本地搭建起BSC和BC两条链
启动bsc_relayer,能成功与两条链建立连接
bsc_relayer要能同步数据成功:拉取BC的数据,同步到BSC上,能成功修改bsc对应合约里的数据
bsc-relayer中继器:
简介:
bsc-relayer 是一个独立进程,主要有两个功能:
跨链同步主要涉及两个系统合约,分别为
与两条链的连接方式:
bsc-relayer 与 BC、BSC 均通过 RPC 进行通信。
bsc-relayer <-----> BC
:
bsc-relayer 通过发送不同的请求信息获取 BC 上的数据,例如 abci_info
、block
等。
bsc-relayer <-----> BSC
:
bsc-relayer 直接使用了 Etheruem 提供的 RPC 模块,可直接调用发送数据或者请求,其中最常用的是eth_sendRawTransaction
进行交易的发送。
RPC 的配置信息记录在 config/config.json
文件里:
涉及到的系统合约:
可在bsc-relayer 里的const.go
中看到:
executor/const.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var ( prefixForCrossChainPackageKey = []byte {0x00 } prefixForSequenceKey = []byte {0xf0 } PureHeaderSyncChannelID relayercommon.CrossChainChannelID = -1 tendermintLightClientContractAddr = common.HexToAddress("0x0000000000000000000000000000000000001003" ) relayerIncentivizeContractAddr = common.HexToAddress("0x0000000000000000000000000000000000001005" ) relayerHubContractAddr = common.HexToAddress("0x0000000000000000000000000000000000001006" ) crossChainContractAddr = common.HexToAddress("0x0000000000000000000000000000000000002000" ) )
只涉及到4个系统合约:
sequence
ContractName
ContractNameCN
Role
1
TendermintLightClient Contract
Tendermint轻客户端合约
负责执行 bsc-relayer
中继器发送过来的“同步块头”的交易
2
RelayerIncentivize Contract
中继器奖励合约
用于 bsc-relayer
claim reward。而 bsc-validator 的 reward 为打包出块的tx
的 gas
3
RelayerHub Contract
中继器管理合约
用于记录 bsc-relayer
的注册信息, RelayerHub 管理bsc-relayer
的权限.想要运行bsc-relayer
的人必须调用合约来存入一些 BNB 以获得授权。
4
CrossChain Contract
跨链包处理合约
负责执行bsc-relayer
发送过来的“同步跨链数据包”的交易,进行跨链包预处理,通过emit
事件发送跨链包到BC
.包预处理包括序列验证和默克尔证明验证
现在先看TendermintLightClient.sol
轻客户端的源码及其实现
轻客户端的实现:
官方文档 里有介绍。BSC用智能合约的形式,实现了一个轻客户端。
跨链互操作性的目的是使一个区块链能够充当另一个区块链的轻客户端。
由于信标链使用经典的拜占庭容错共识算法,轻客户端验证既便宜又容易:我们所要做的就是检查最新区块上的验证者签名
,并验证状态的默克尔证明
。
在 Tendermint 中,验证者在处理区块之前就区块达成一致。
这意味着该块的签名和状态根直到下一个块才包含在内。因此,每个块都包含一个名为 LastCommit
的字段,其中包含负责提交前一个块的投票,以及块头中的一个名为 AppHash
的字段,它指的是应用程序处理前一个块的交易后的 Merkle 根哈希
。
因此,如果我们想从高度 H
验证AppHash
,我们需要来自 LastCommit
在高度 H+1
的签名。(请记住,此 AppHash 仅包含直到并包括块 H-1 的所有交易的结果
)
与工作量证明不同,轻客户端协议不需要下载和检查区块链中的所有标头 - 客户端始终可以直接跳转到可用的最新标头,只要验证器集没有太大变化 。如果验证者集发生变化,客户端需要跟踪这些变化,这需要为每个发生重大变化的块下载标头。在这里,我们将假设验证器集是恒定的,并推迟处理验证器集更改。
以太坊平台支持 golang 实现的无状态预编译合约
和solidity实现的普通合约
。
与普通合约相比,预编译合约效率更高,gas 成本更低,但它们是无状态的。但是,链上轻客户端必须是有状态的。所以这里我们将尝试一种混合方式:
预编译实现合约(无状态计算,如签名验证)
普通合约(存储验证器集和可信appHash)
基于solidity的智能合约,我们比较熟悉,所以接下来我们先了解一下预编译合约。
预编译合约:
介绍:
预编译合约是 EVM 中用于提供更复杂库函数(通常用于加密、散列等复杂操作)的一种折衷方法,这些函数不适合编写操作码。
它们适用于简单但经常调用的合约,或逻辑上固定但计算量很大的合约。
预编译合约是在使用节点客户端(如geth)代码实现的,因为它们不需要 EVM,所以运行速度很快。 与使用直接在 EVM 中运行的函数相比,它对开发人员来说成本也更低。
简言之,预编译合约特点如下:
常用于加密、散列、验证、数据计算等操作
不运行在evm中,在geth客户端里实现
无状态、运行速度快、成本低
BSC 中的预编译合约定义如下:
core/vm/contracts.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var PrecompiledContractsIstanbul = map [common.Address]PrecompiledContract{ common.BytesToAddress([]byte {1 }): &ecrecover{}, common.BytesToAddress([]byte {2 }): &sha256hash{}, common.BytesToAddress([]byte {3 }): &ripemd160hash{}, common.BytesToAddress([]byte {4 }): &dataCopy{}, common.BytesToAddress([]byte {5 }): &bigModExp{}, common.BytesToAddress([]byte {6 }): &bn256AddIstanbul{}, common.BytesToAddress([]byte {7 }): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte {8 }): &bn256PairingIstanbul{}, common.BytesToAddress([]byte {9 }): &blake2F{}, common.BytesToAddress([]byte {100 }): &tmHeaderValidate{}, common.BytesToAddress([]byte {101 }): &iavlMerkleProofValidate{}, }
前面的都是在Etheruem中已经实现的预编译合约了,BSC 新增了两个:
接下来主要介绍预编译合约tmHeaderValidate
和系统合约TendermintLightClient
系统合约TendermintLightClient:
数据结构:
TendermintLightClient.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 contract TendermintLightClient is ILightClient , System , IParamSubscriber { struct ConsensusState { uint64 preValidatorSetChangeHeight; bytes32 appHash; bytes32 curValidatorSetHash; bytes nextValidatorSet; } mapping (uint64 => ConsensusState ) public lightClientConsensusStates; mapping (uint64 => address payable) public submitters; uint64 public initialHeight; uint64 public latestHeight; bytes32 public chainID; bytes constant public INIT_CONSENSUS_STATE_BYTES = hex"42696e616e63652d436861696e2d4e696c650000000000000000000000000000000000000000000229eca254b3859bffefaf85f4c95da9fbd26527766b784272789c30ec56b380b6eb96442aaab207bc59978ba3dd477690f5c5872334fc39e627723daa97e441e88ba4515150ec3182bc82593df36f8abb25a619187fcfab7e552b94e64ed2deed000000e8d4a51000" ; uint256 constant public INIT_REWARD_FOR_VALIDATOR_SER_CHANGE = 1e16 ; uint256 public rewardForValidatorSetChange; event initConsensusState (uint64 initHeight, bytes32 appHash); event syncConsensusState (uint64 height, uint64 preValidatorSetChangeHeight, bytes32 appHash, bool validatorChanged); event paramChange (string key, bytes value); constructor ( ) public {} .... }
需要关注的是 INIT_CONSENSUS_STATE_BYTES
初始化共识状态变量,该变量可在创始系统合约仓库bsc-genesis-contract 中的generate-tendermintlightclient.js
中设置,这个值要设置得对,后面的区块头才能顺利通过验证并同步。
在INIT_CONSENSUS_STATE_BYTES
这变量上消耗了很多时间
初始化函数:
TendermintLightClient.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 function init ( ) external onlyNotInit { uint256 pointer; uint256 length; (pointer, length) = Memory .fromBytes (INIT_CONSENSUS_STATE_BYTES ); assembly { sstore (chainID_slot, mload (pointer)) } ConsensusState memory cs; uint64 height; (cs, height) = decodeConsensusState (pointer, length, false ); cs.preValidatorSetChangeHeight = 0 ; lightClientConsensusStates[height] = cs; initialHeight = height; latestHeight = height; alreadyInit = true ; rewardForValidatorSetChange = INIT_REWARD_FOR_VALIDATOR_SER_CHANGE ; emit initConsensusState (initialHeight, cs.appHash ); }
初始化函数中主要是将初始化共识状态变量INIT_CONSENSUS_STATE_BYTES
中的值通过内联汇编的方式写入storage
中,该函数以及后面的函数会大量操作指针变量uint256 pointer
和长度变量uint256 length
来实现存储/读取数据的操作。
同步区块头函数:
TendermintLightClient.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 69 70 71 72 73 74 75 76 77 78 79 80 function syncTendermintHeader (bytes calldata header, uint64 height ) external onlyRelayer returns (bool) { require (submitters[height] == address (0x0 ), "can't sync duplicated header" ); require (height > initialHeight, "can't sync header before initialHeight" ); uint64 preValidatorSetChangeHeight = latestHeight; ConsensusState memory cs = lightClientConsensusStates[preValidatorSetChangeHeight]; for (; preValidatorSetChangeHeight >= height && preValidatorSetChangeHeight >= initialHeight;) { preValidatorSetChangeHeight = cs.preValidatorSetChangeHeight ; cs = lightClientConsensusStates[preValidatorSetChangeHeight]; } if (cs.nextValidatorSet .length == 0 ) { preValidatorSetChangeHeight = cs.preValidatorSetChangeHeight ; cs.nextValidatorSet = lightClientConsensusStates[preValidatorSetChangeHeight].nextValidatorSet ; require (cs.nextValidatorSet .length != 0 , "failed to load validator set data" ); } uint256 length = 136 + cs.nextValidatorSet .length ; bytes memory input = new bytes (length + header.length ); uint256 ptr = Memory .dataPtr (input); require (encodeConsensusState (cs, preValidatorSetChangeHeight, ptr, length), "failed to serialize consensus state" ); uint256 src; ptr = ptr + length; (src, length) = Memory .fromBytes (header); Memory .copy (src, ptr, length); length = input.length + 32 ; bytes32[128 ] memory result; assembly { if iszero (staticcall(not(0 ), 0x64 , input, length, result, 4096 ) ) { revert (0 , 0 ) } } assembly { length := mload (add (result, 0 )) } bool validatorChanged = false ; if ((length & (0x01 << 248 )) != 0x00 ) { validatorChanged = true ; ISystemReward (SYSTEM_REWARD_ADDR ).claimRewards (msg.sender , rewardForValidatorSetChange); } length = length & 0xffffffffffffffff ; assembly { ptr := add (result, 32 ) } uint64 actualHeaderHeight; (cs, actualHeaderHeight) = decodeConsensusState (ptr, length, !validatorChanged); require (actualHeaderHeight == height, "header height doesn't equal to the specified height" ); submitters[height] = msg.sender ; cs.preValidatorSetChangeHeight = preValidatorSetChangeHeight; lightClientConsensusStates[height] = cs; if (height > latestHeight) { latestHeight = height; } emit syncConsensusState (height, preValidatorSetChangeHeight, cs.appHash , validatorChanged); return true ; }
该函数需要关注的是 调用了预编译合约tmHeaderValidate
进行区块头合法性的验证
1 2 3 4 5 6 7 8 9 assembly { if iszero (staticcall(not(0 ), 0x64 , input, length, result, 4096 ) ) { revert (0 , 0 ) } }
调用关系如下:
一开始就卡在调用预编译合约这一步,验证一直没法通过。
预编译合约tmHeaderValidate:
先在geth中找到预编译合约的代码文件: core/vm/contracts_lightclient.go
,通过测试文件core/vm/contracts_lightclient_test.go
可以帮助我们测试、快速了解。
测试代码:
core/vm/contracts_lightclient_test.go 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 func TestTmHeaderValidateAndMerkleProofValidate (t *testing.T) { consensusStateBytes, err := hex.DecodeString("..." ) require.NoError(t, err) cs, err := lightclient.DecodeConsensusState(consensusStateBytes) require.NoError(t, err) headerBytes, err := hex.DecodeString("...." ) require.NoError(t, err) parameterInput := make ([]byte , 32 +len (consensusStateBytes)+len (headerBytes)) binary.BigEndian.PutUint64(parameterInput[24 :32 ], uint64 (len (consensusStateBytes))) copy (parameterInput[32 :32 +len (consensusStateBytes)], consensusStateBytes) copy (parameterInput[32 +len (consensusStateBytes):], headerBytes) totalLengthPrefix := make ([]byte , 32 ) binary.BigEndian.PutUint64(totalLengthPrefix[0 :8 ], 0 ) binary.BigEndian.PutUint64(totalLengthPrefix[8 :16 ], 0 ) binary.BigEndian.PutUint64(totalLengthPrefix[16 :24 ], 0 ) binary.BigEndian.PutUint64(totalLengthPrefix[24 :], uint64 (len (parameterInput))) input := append (totalLengthPrefix, parameterInput...) var tmHeaderValidateContract tmHeaderValidate syncedConsensusStateBytes, err := tmHeaderValidateContract.Run(input) require.NoError(t, err) syncedConsensusState, err := lightclient.DecodeConsensusState(syncedConsensusStateBytes[32 :]) require.NoError(t, err) require.Equal(t, testHeight+1 , syncedConsensusState.Height) require.Equal(t, cs.ChainID, syncedConsensusState.ChainID) .... }
共识状态数据结构:
core/vm/lightclient/types.go 1 2 3 4 5 6 7 8 9 consensusState := ConsensusState{ ChainID: chainID, Height: height, AppHash: appHash, CurValidatorSetHash: curValidatorSetHash, NextValidatorSet: &tmtypes.ValidatorSet{ Validators: validatorSet, }, }
先看测试代码前面的部分,主要步骤如下:
轻客户端本身保存着当前的活跃验证者集合,当收到新的区块后,
首先对新区块头做基本的检查: 例如chainId
是否相等
判断新区块头是否获得了3分之2的投票
区块头中的验证者集合与轻客户端本身存储的验证者集合做比较,若相同,则通过+2/3的投票,通过验证,若不同,更新轻客户端的活跃验证者集合,再次验证;
当轻客户端的安全性不足时(如高度不连续、bft超过1/3等等),与全节点进行交互验证。
上面部分摘自《区块链架构与实现-cosmos详解-9.1.1轻客户端原理概述》
共识状态数据结构:
先把解码后的数据打印出来观察一下。
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 === RUN TestTmHeaderValidateAndMerkleProofValidateMy [*****] tmHeaderValidate Run Func [*****] Step 1 [*****] Step 2 [*****] cs: &{715 2 [41 236 162 84 179 133 155 255 239 175 133 244 201 93 169 251 210 101 39 118 107 120 66 114 120 156 48 236 86 179 128 182] [235 150 68 42 170 178 7 188 89 151 139 163 221 71 118 144 245 197 135 35 52 252 57 230 39 114 61 170 151 228 65 232] ValidatorSet{ Proposer: Validator{35B42F545A2E3575E48A95B47C9E9540074756BD PubKeyEd25519{8BA4515150EC3182BC82593DF36F8ABB25A619187FCFAB7E552B94E64ED2DEED} VP:1000000000000 A:0} Validators: Validator{35B42F545A2E3575E48A95B47C9E9540074756BD PubKeyEd25519{8BA4515150EC3182BC82593DF36F8ABB25A619187FCFAB7E552B94E64ED2DEED} VP:1000000000000 A:0} }} [*****] header: SignedHeader{ Header{ Version: {10 0} ChainID: 715 Height: 5335 Time: 2022-09-20 10:00:41.504366656 +0000 UTC NumTxs: 0 TotalTxs: 0 LastBlockID: 0EE58A3014F929D14E9A82AB04CDBF2097B1915A0CF8C23EB8143B900777357F:1:A84AF4EA95C5 LastCommit: B6D621C0189F2DFD4767E85C57B61813804DECF7A32F08E73666F53616E5FDDF Data: Validators: C506C171480200F8EDE028065D07D02CA8DA35C94463326A6E31FBD6CF82388C NextValidators: C506C171480200F8EDE028065D07D02CA8DA35C94463326A6E31FBD6CF82388C App: 0E067C5259944D79F6110455B387C9EF941C9D10A8552B5ACDD3DEF69868DE25 Consensus: 294D8FBD0B94B767A7EBA9840F299A3586DA7FE6B5DEAD3B7EECBA193C400F93 Results: Evidence: Proposer: 931B028E3A2B6C045D0E4579470E9E50847029A2 } Commit{ BlockID: 20404785119D52196F6BA954C57AF0F0FF19E680921A7F7E8A4A8CD485195972:1:6B3354315C9D Precommits: Vote{0:931B028E3A2B 5335/00/2(Precommit) 20404785119D 654C58D09A92 @ 2022-09-20T10:00:42.512185556Z} } }
基本检查:
先对新区块头的数据进行基本检查,例如chainId
是否相等。
一开始就是卡在这一步,generate-tendermintlightclient.js
中默认的INIT_CONSENSUS_STATE_BYTES
里的chainId
为Binance-Chain-Nile
,而我同步过来新区块头里的chainId
为715
core/vm/contracts_lightclient_encode_test.go 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 func TestTmHeaderValidateAndMerkleProofValidateTemp (t *testing.T) { consensusStateBytesStr := "3731350000000000000000000000000000000000000000000000000000000000000000000000000229eca254b3859bffefaf85f4c95da9fbd26527766b784272789c30ec56b380b6eb96442aaab207bc59978ba3dd477690f5c5872334fc39e627723daa97e441e88ba4515150ec3182bc82593df36f8abb25a619187fcfab7e552b94e64ed2deed000000e8d4a51000" consensusStateBytes, err := hex.DecodeString(consensusStateBytesStr) require.NoError(t, err) cs, err := lightclient.DecodeConsensusState(consensusStateBytes) require.NoError(t, err) fmt.Printf("cs: %v \n" , cs) cs.ChainID = "715" cs.Height = 2 consensusStateBytes, err = cs.EncodeConsensusState() newConsensusStateBytesStr := hex.EncodeToString(consensusStateBytes) fmt.Println("consensusStateBytesHexStr: " , newConsensusStateBytesStr) newConsensusStateBytes, err := hex.DecodeString(newConsensusStateBytesStr) newcs, err := lightclient.DecodeConsensusState(newConsensusStateBytes) require.NoError(t, err) fmt.Printf("newcs: %v \n" , newcs) }
我尝试去修改其中的链id,再编码回去,chainId
校验可以通过,但会出现新的报错:投票权重不足
1 2 Error: Received unexpected error: Invalid commit -- insufficient old voting power: got 0, needed 666666666667
投票权重检查:
接着上面的报错,同步过来的新区块的提案者地址为
1 Proposer: 931B028E3A2B6C045D0E4579470E9E50847029A2
但初始的共识状态里的验证者集合没有该地址,所以投票权重检查不通过
1 2 3 Validators: Validator{35B42F545A2E3575E48A95B47C9E9540074756BD PubKeyEd25519{8BA4515150EC3182BC82593DF36F8ABB25A619187FCFAB7E552B94E64ED2DEED} VP:1000000000000 A:0} }
至此,我们有两个思路:
基于generate-tendermintlightclient.js
中默认的INIT_CONSENSUS_STATE_BYTES
,我们去魔改/生成一个对的初始共识状态十六进制字节数组字符串
直接获取bc上某高度个共识状态数据,再编码成十六进制字节数组字符串
思路1:
比较棘手的是PubKeyEd25519
公钥数据,没法通过BC的cli、rpc api直接获取。
1 2 3 Validators: Validator{35B42F545A2E3575E48A95B47C9E9540074756BD PubKeyEd25519{8BA4515150EC3182BC82593DF36F8ABB25A619187FCFAB7E552B94E64ED2DEED} VP:1000000000000 A:0} }
参考文章COSMOS ED25519简析 ,我们可以在bnc-tendermint 中调用函数,自己生成。打开BC的配置文件/config/priv_validator_key.json
/config/priv_validator_key.json 1 2 3 4 5 6 7 8 9 10 11 { "address" : "36231A72BBC68CBD82D0EEC690EE8540B0B049AF" , "pub_key" : { "type" : "tendermint/PubKeyEd25519" , "value" : "AXtsaPfWGEp+oDrq/PKMEvTImmb4HS87juIdVykoSNg=" } , "priv_key" : { "type" : "tendermint/PrivKeyEd25519" , "value" : "cijqz2BN0KPc1qETWND0Dr7Zk4g1TYTV+MH6S04YuRcBe2xo99YYSn6gOur88owS9MiaZvgdLzuO4h1XKShI2A==" } }
修改并运行测试脚本:
privval/file_test.go 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 func TestUnmarshalValidatorKeyTemp (t *testing.T) { serialized := fmt.Sprintf(`{ "address": "931B028E3A2B6C045D0E4579470E9E50847029A2", "pub_key": { "type": "tendermint/PubKeyEd25519", "value": "+dC/2i1cfw/ZcHZvsxt8dDXxbl1EnClGeckR4o4vUUo=" }, "priv_key": { "type": "tendermint/PrivKeyEd25519", "value": "B8z+AJ9+wtAoeMWShtlZ1BVewYp9UtD+LRMXoE8ITGX50L/aLVx/D9lwdm+zG3x0NfFuXUScKUZ5yRHiji9RSg==" } }` ) fmt.Println(serialized) val := FilePVKey{} err := cdc.UnmarshalJSON([]byte (serialized), &val) fmt.Println(err) fmt.Println(val.Address) fmt.Println((val.PubKey.(ed25519.PubKeyEd25519)).String()) }
输出测试结果,成功打印出PubKeyEd25519
字符串
1 2 3 4 === RUN TestUnmarshalValidatorKeyTemp PubKeyEd25519{F9D0BFDA2D5C7F0FD970766FB31B7C7435F16E5D449C294679C911E28E2F514A} --- PASS: TestUnmarshalValidatorKeyTemp (0.00s) PASS
思路1要改的内容太多,容易出错,应采取思路2的更为直接、准确。
思路2:
直接获取BC上某高度上共识状态数据,再编码成十六进制字节数组字符串
币安是先有BC链,再有BSC链,它们主网在TendermintLightClient.sol
轻客户端合约里的初始高度设置成一个很大的值110186855
。
可猜测:
BC链是先于BSC链,持续运行着
选取了一个BC链上一个特定的高度里的共识状态数据作为轻客户端合约里的初始共识状态数据
然后运行起BSC链、bsc-relayer
中继器连接两条链,读取到BC特定高度的数据后,会同步BC后面的共识状态到轻客户端合约里
在bsc-relayer 中新增测试脚本:
executor/blockinfo_test.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func TestGetBlockInfo (t *testing.T) { initFlags() var height int64 height = 50 bbcNetworkType := viper.GetInt(flagBBCNetworkType) bbcExecutor, _ := NewBBCExecutor(cfg, types.ChainNetwork(bbcNetworkType)) cs, err := bbcExecutor.GetInitConsensusState(height) if err != nil { fmt.Println(err) } common.Logger.Infof("cs: %v\n" , cs) fmt.Printf("cs: %v \n" , cs) consensusStateBytes, err := cs.EncodeConsensusState() newConsensusStateBytesStr := hex.EncodeToString(consensusStateBytes) fmt.Println("consensusStateBytesHexStr: " , newConsensusStateBytesStr) }
运行测试脚本:
1 2 cd /bsc-relayer/executorgo test -v
主要是使用了bsc-relayer中的bbcExecutor.GetInitConsensusState(height)
函数,成功获取BC上高度为50的共识状态数据,并编码成十六进制字符串。
1 2 3 consensusStateBytesHexStr: 373135000000000000000000000000000000000000000000000000000000000000000000000000323cebfa274480fa027136f6f9049aa0b6a92b4b3843ac5bce32d2d19af15d7a6a6e42927ceb8681e615b60bc77cb1ff3449b93bc9017b6c68f7d6184a7ea03aeafcf28c12f4c89a66f81d2f3b8ea51000 --- PASS: TestGetBlockInfo (0.00s) PASS
至此,我们就获得了一个准确的初始化共识状态数据,可作为TendermintLightClient.sol
轻客户端合约的初始化共识状态数据。
现在我们需要将BC、BSC、bsc-relayer三者结合起来完整验证一下,验证bsc-relayer 是否能够同步新区块头的到TendermintLightClient.sol
轻客户端合约里,并成功修改到轻客户端合约里的数据。
完整验证:
运行BC链:
修改一下BC链配置文件里的同步区块间隔,原本是一天一次,现在要调整得快一些
1 2 3 [base] # Interval blocks of breathe block, if breatheBlockInterval is 0, breathe block will be created every day. breatheBlockInterval = 100
然后初始化并运行起BC链
获取初始化共识状态数据
使用bsc-relayer
上的测试脚本获取某特定高度的初始化共识状态数据的十六进制字节数组字符串
executor/blockinfo_test.go 1 2 3 4 5 === RUN TestGetBlockInfo consensusStateBytesHexStr: 373135000000000000000000000000000000000000000000000000000000000000000000000000323 cebfa274480fa027136f6f9049aa0b6a92b4b3843ac5bce32d2d19af15d7a6a6e42927ceb8681e615b60bc77cb1ff3449b93bc9017b6c68f7d6184a7ea03aeafcf28c12f4c89a66f81d2f3b8ea51000 --- PASS: TestGetBlockInfo (0.00 s) PASS ok github.com/binance-chain/bsc-relayer/executor 0.080 s
重新生成BSC的genesis.json:
在创始系统合约仓库bsc-genesis-contract 中的generate-tendermintlightclient.js
中设置initConsensusStateBytes
generate-tendermintlightclient.js 1 2 3 program.option ("--initConsensusStateBytes <initConsensusStateBytes>" , "init consensusState bytes, hex encoding, no prefix with 0x" , "373135000000000000000000000000000000000000000000000000000000000000000000000000323cebfa274480fa027136f6f7986147b96aa4adb7c421b909049aa0b6a92b4b3843ac5bce32d2d19af15d7a6a6e42927ceb8681e615b60bc77cb1ff3449b93bc9017b6c68f7d6184a7ea03aeafcf28c12f4c89a66f81d2f3b8ee21d57292848d8000000e8d4a51000" );
生成genesis.json
,初始化并运行起BSC链
运行bsc-relayer:
初始化并运行起bsc-relayer
中继器
查看结果:
用本地搭建的blockscout
区块链浏览器查看结果
bsc-relayer
中继器发起到TendermintLightClient.sol
轻客户端合约的tx交易成功
Remix IDE
读取TendermintLightClient.sol
轻客户端合约的变量,修改成功。
跨链同步主要涉及两个系统合约,分别为
TendermintLightClient.sol
: 同步块头的操作时会调用到
CrossChain.sol
: 同步跨链数据包的操作会调用到
至此,我们就搞定了TendermintLightClient.sol
轻客户端合约中区块头的同步
问题,后面继续研究同步跨链数据包
的问题。
小结:
涉及到的github代码仓库较多,简单梳理一下:
Refs: