引言:
还是围绕验证者管理模块进行,上次完成了添加新的验证者,这次围绕验证者的奖励进行验证/学习/测试。
目的:
验证者能够查询自己的收入
验证者能够获取到自己的挖矿收益
BC权益质押:
参考白皮书,可知币安链在在BC上完成了BSC的权益质押逻辑
:
质押代币是 BNB,这是因为它是两个区块链上的原生代币。
在BC上记录BSC的权益质押和委托行为。
BSC 验证人集由它的权益质押和委托逻辑来决定,在BC上构建一个BSC的权益质押模块,并通过跨链通信在每天UTC 00:00:00 由BC传送到 BSC 。
BC上的奖励分配发生在每天UTC 00:00时刻。
奖励:
验证人集更新和奖励分配都发生在每天的UTC 00:00 。这是为了节省频繁更新和区块奖励分配的成本。频繁分配奖励的代价可能是巨大的,因为区块奖励是在BSC上收取的,并在BC上分发给BSC验证人和委托人
。(请注意,BC出块奖励仅分发给BC验证人。)
为了确保分配是公平的,这里引入了一种延后分配的算法:
区块奖励不会立即发送给验证人,而是计算并积累在智能合约中;
当BSC收到验证人集更新消息时,它将触发跨链转账,将奖励转账给验证人的托管地址。 托管地址是由系统控制,因此在向委派者承诺的分配完成之前,奖金是不能用的。
为了使同步更简单,并分配时间以防出现罚没,第T天的奖励将在第T + 2 天分配。 在委托人收到奖励后,剩下的收益将被转移到验证人自己的奖励地址。
奖励分配流程:
主网Tx例子:
缩小block区间,在bscan上获取一个tx,其中Transaction Receipt Event Logs
里有与BSCValidatorSet.sol
(0x0000000000000000000000000000000000001000
)的交互记录,通过这个tx 在tenderly查看,方便自己理解系统合约间的内部调用关系。
CrossChain.sol
——> BSCValidatorSet.sol
——>TokenHub.sol
tx-> BSCValidatorSet.sol:
区块奖励不会立即发送给验证人,而是计算并积累在智能合约中;
验证者的奖励/收益来源于区块内的交易手续费(gas fee),奖励会先累计在BSCValidatorSet.sol
合约中。
bsc-relayer->BSC:
流程图:
每天0点时,bsc-relayer
会将区块头和跨链包数据同步到BSC上。
bsc-relayer
调用CrossChain.sol
(0x0000000000000000000000000000000000002000
)跨链合约
再内部调用BSCValidatorSet.sol
(0x0000000000000000000000000000000000001000
)的updateValidatorSet
函数
再内部调用TokenHub.sol
(0x0000000000000000000000000000000000001004
)的batchTransferOutBNB
函数
再内部调用CrossChain.sol
(0x0000000000000000000000000000000000002000
)跨链合约的sendSynPackage
函数
BSCValidatorSet.sol:
BSC收到验证人集更新消息,BSCValidatorSet合约中updateValidatorSet
函数中会执行以下的步骤:
step 0: force all maintaining validators to exit Temporary Maintenance
validators exit maintenance 验证者退出维护状态
clear all maintainInfo 清除所有维护信息
get unjailed validators from validatorSet 从验证者集合中获取未被监禁的验证者
step1:do calculate distribution, do not make it as an internal function for saving gas. 计算分配
验证者的收入 > 0.1bnb, 则进行跨链转账,小于则进行直接转账
step2:执行跨链转账:
将跨链转账总额转到TokenHub.sol
合约中,调用batchTransferOutBNB
函数,会传几个参数:
crossTotal: 跨链转账总额,总共多少BNB 见主网tx例子:大概2000个BNB左右
crossAddrs数组: 验证者的BBCFeeAddress,BC收款地址
crossAmounts数组:验证者的收入
crossRefundAddrs数组: 也是验证者的BBCFeeAddress,BC收款地址
step3:执行直接转账: 收入少的话则,直接转账: 将验证者的收入转到验证者的收款地址中
step4: do dusk transfer 应该是把合约里剩余的零钱转走
step5: do update validator set state 更新验证者集合状态
step6: clean slash contract 清空惩罚slash合约状态
TokenHub.sol:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function batchTransferOutBNB (address[] calldata recipientAddrs, uint256[] calldata amounts, address[] calldata refundAddrs, uint64 expireTime ) external override onlyInit payable returns (bool) { ... ... rewardForRelayer = msg.value .sub (totalAmount); TransferOutSynPackage memory transOutSynPkg = TransferOutSynPackage ({ bep2TokenSymbol : BEP2 _TOKEN_SYMBOL_FOR_BNB, contractAddr : address (0x00 ), amounts : convertedAmounts, recipients : recipientAddrs, refundAddrs : refundAddrs, expireTime : expireTime }); ICrossChain (CROSS_CHAIN_CONTRACT_ADDR ).sendSynPackage (TRANSFER_OUT_CHANNELID , encodeTransferOutSynPackage (transOutSynPkg), rewardForRelayer.div (TEN_DECIMALS )); emit transferOutSuccess (address (0x0 ), msg.sender , totalAmount, rewardForRelayer); return true ; }
主要是先进行各种值的检验、构造同步包、调用CrossChain
合约的sendSynPackage
函数发送同步包
oracle-relayer -> BC:
oracle-relayer:
作用: 拉取 BSC 的跨链数据包,并针对 BC 的预言(prophecy)进行声明(claim);
介绍:
BC上的oracle模块是与 gov类似的通用模块,用于处理预言和声明。
预言意味着验证者希望就某些事情达成共识,例如跨链转移。
声明由验证者提出,声明的内容是跨链转移。
validator在哪提出声明(claim)?
当大多数验证者(如 70%)在预言上声明相同的东西时,获胜的声明将被执行。因为 oracle 模块是一个普通模块,其他依赖于 oracle 模块的模块将注册声明类型和相关的钩子检查和处理宣称。
每个声明类型都有一个序列,oracle 模块应该按序列处理预言和声明。当一个预言执行成功时,声明类型的序列将加一。
Oracle模块流程:
Oracle 模块从验证者接收到声明消息,如果序列不是当前序列,则声明消息将被拒绝。
如果序列有效,则声明类型的钩子将检查声明消息,如果声明消息无效,则返回
如果声明消息有效并且是第一个声明,则将创建相关的预言。如果声明消息不是第一个声明,则将其添加到已存在的预言中。
如果声明相同内容的验证者的权力达到 70% 之类的阈值,则预言将被标记为成功,钩子将执行获胜的声明。并且索赔类型的顺序将增加。
如果验证者没有机会达成共识,则预言将被标记为失败,预言将被删除,验证者应重新开始。
BC上是如何创建预言?
Oracle-relayer源码:
oracle-relayer
中主要两个文件是
observer观察者:
主要做的工作就是 同步bsc区块/跨链包的数据
逐个区块高度读取BSC上的区块数据
将区块数据、区块内的跨链包数据存储在数据库中
1 2 3 4 5 2022-10-19 00:19:10 INFO Fetch fetch block, height=130180 2022-10-19 00:19:10 INFO Fetch fetch block, height=130181 2022-10-19 00:19:10 INFO Fetch fetch block, height=130182 2022-10-19 00:19:10 INFO Fetch fetch block, height=130183 2022-10-19 00:19:10 INFO Fetch fetch block, height=130184
relayer中继器:
主要的作用就是,按序发送发送跨链数据包到BC链,对预言进行声明
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 81 82 83 84 85 86 87 func (r *Relayer) process(chainId uint16 ) error { sequence, err := r.BBCExecutor.GetCurrentSequence(chainId) if err != nil { util.Logger.Errorf("get current sequence error: chainId=%d, err=%s" , chainId, err.Error()) return err } util.Logger.Infof("current sequence, chain_id=%d, seq=%d" , chainId, sequence) claimLogs := make ([]*model.CrossChainPackageLog, 0 ) err = r.DB.Where("oracle_sequence = ? and chain_id = ? and status = ?" , sequence, chainId, model.PackageStatusConfirmed).Order("tx_index asc" ).Find(&claimLogs).Error if err != nil { util.Logger.Errorf("query claim log error: err=%s" , err.Error()) return err } fmt.Printf("[*] claimLogs: %v\n" , claimLogs) fmt.Printf("[*] len claimLogs: %d\n" , len (claimLogs)) if len (claimLogs) == 0 { return fmt.Errorf("no packages found" ) } prophecy, err := r.BBCExecutor.GetProphecy(chainId, sequence) fmt.Printf("[*] prophecy: %v\n" , prophecy) if err != nil { util.Logger.Errorf("get prophecy error: err=%s" , err.Error()) return err } validatorAddress := r.BBCExecutor.GetAddress() fmt.Printf("[*] validatorAddress: %s\n" , validatorAddress) if prophecy != nil && prophecy.ValidatorClaims != nil && prophecy.ValidatorClaims[validatorAddress.String()] != "" { return fmt.Errorf("already claimed" ) } packages := make (msg.Packages, 0 , len (claimLogs)) for _, claimLog := range claimLogs { fmt.Printf("[*] claimLog: %v\n" , claimLog) payload, err := hex.DecodeString(claimLog.PayLoad) if err != nil { return fmt.Errorf("decode payload error, payload=%s" , claimLog.PayLoad) } pack := msg.Package{ ChannelId: types.IbcChannelID(claimLog.ChannelId), Sequence: claimLog.PackageSequence, Payload: payload, } packages = append (packages, pack) } encodedPackages, err := rlp.EncodeToBytes(packages) if err != nil { return fmt.Errorf("encode packages error, err=%s" , err.Error()) } util.Logger.Infof("claim, chain_id=%d, seq=%d, payload=%s" , chainId, sequence, hex.EncodeToString(encodedPackages)) txHash, err := r.BBCExecutor.Claim(chainId, uint64 (sequence), encodedPackages) if err != nil { util.Logger.Errorf("claim error: err=%s" , err.Error()) return err } err = r.DB.Model(model.CrossChainPackageLog{}).Where("oracle_sequence = ? and chain_id = ?" , sequence, chainId).Update(map [string ]interface {}{ "status" : model.PackageStatusClaimed, "claim_tx_hash" : txHash, "update_time" : time.Now().Unix(), }).Error if err != nil { util.Logger.Errorf("update CrossChainPackageLog error, err=%s" , err.Error()) } return err }
完整测试/验证步骤:
模拟多个验证者:
先模拟BSC上正常的情况:
有2个验证者节点,两个节点要在区块链浏览器上能看到,块要由他们轮流出
浏览器查看出块情况:
目前是轮流出块的状态
模拟交易:
在BSCValidatorSet
合约中,查看验证者的收入:
验证者的收入来自gasfee,多次转账并提高gasPrice,使得矿工的收入大于0.1BNB
,这样才满足跨链转账的前提。
1 2 eth.sendTransaction({from: "0x9FC0c18d285C66dD993B8fF43C2560481A2D8d04" , to: "0xC1b025e7406461E06185dE04253267C61E3990F6" , value: web3.toWei(1, "ether" ), gasPrice: web3.toWei(20000, "gwei" )})
添加验证者:
先在BC上添加2个验证者,等bsc-relayer
中继器将新的验证者集合同步到BSC上。
此时会触发BSCValidatorSet
合约中奖励发放的逻辑,接下来看执行TOKEN_HUB
合约里的batchTransferOutBNB
函数交易是否正常、跨链转账是否能够正常进行。
bsc-relayer停了再运行后,发的同步跨链包的sequence
不对,序列号没有按顺序且过大
中继器发跨链包的sequence是怎么获取的?
中继器不断的BatchRelayCrossChainPackages
后,sequence
补齐了新增的验证者地址,同步成功:
查询BSCValidatorSet合约:
原本验证者集合合约里还有一些BNB,现在应该是执行到跨链转账的阶段了
本地tx
启动oracle-relayer:
现在是要将BSC上获取的挖矿奖励,跨链到BC上,需要使用到oracle-relayer
进行同步
拉取 BSC 的跨链数据包,并针对 BC 的预言(prophecy)进行声明(claim);
1 2 ./build/relayer --bbc-network 1 --config-type local --config-path config/config.json
报错1: 环境设置不当
1 2 3 4 5 6 2022-10-17 00:22:11 ERROR process claim error: err={"codespace" :1,"code" :7,"abci_code" :65543,"message" :"tbnb1fvpct0j9qt76skhgj362t8dll0ycztm2u2f0xu" } 2022-10-17 00:22:12 INFO Fetch fetch block, height=90353 2022-10-17 00:22:12 ERROR Fetch fetch block error, err=get block info error, height=90353, err=not found 2022-10-17 00:22:12 INFO process current sequence, chain_id=714, seq =0 2022-10-17 00:22:12 INFO process claim, chain_id=714, seq =0, payload=f8c5f8c30380b8bf000000000000000000000000000000000000000000000000000000000000061a80f89ca0424e420000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000ca840bd696208410a79319ea9439de103a1bf89743829e1fb94261133a4d3719869461e25456b50d2ff65b48be75d0d435279d3f9e21ea9439de103a1bf89743829e1fb94261133a4d3719869461e25456b50d2ff65b48be75d0d435279d3f9e2184634660e5 2022-10-17 00:22:12 ERROR process claim error: err={"codespace" :1,"code" :7,"abci_code" :65543,"message" :"tbnb1fvpct0j9qt76skhgj362t8dll0ycztm2u2f0xu" }
设置为生产环境,bc链的地址前缀才为bnb
、测试环境的地址前缀为tbnb
报错2: 交易验签失败
1 2 2022-10-19 01:04:47 INFO process claim, chain_id=714, seq=0, payload=f8c5f8c30380b8bf000000000000000000000000000000000000000000000000000000000000061a80f89ca0424e420000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000ca840bd696208410a79319ea9439de103a1bf89743829e1fb94261133a4d3719869461e25456b50d2ff65b48be75d0d435279d3f9e21ea9439de103a1bf89743829e1fb94261133a4d3719869461e25456b50d2ff65b48be75d0d435279d3f9e2184634660e5 2022-10-19 01:04:47 ERROR process claim error: err=claim error, code=65540, log={"codespace":1,"code":4,"abci_code":65540,"message":"signature verification failed"}
signature verification failed
签名验证失败
auth模块中的AnteHandler
会提前对消息进行验证
refs:
node\common\tx\ante.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func processSig (txHash string , sig auth.StdSignature, pubKey crypto.PubKey, signBytes []byte ) ( res sdk.Result) { if sigCache.getSig(txHash) { log.Debug("Tx hits sig cache" , "txHash" , txHash) return } if !pubKey.VerifyBytes(signBytes, sig.Signature) { return sdk.ErrUnauthorized("signature verification failed" ).Result() } sigCache.addSig(txHash) return }
修改BC的node源码,增加一些print,方便debug。在processSig
函数中验证不通过,提示签名验证失败
1 2 3 4 5 6 7 8 9 D[2022-10-20|03:37:18.115] Scheduled timeout module=consensus dur=993.499345ms height=4396 round=0 step=RoundStepNewHeight I[2022-10-20|03:37:18.378] ABCIQuery module=rpc path=/store/sc/key data=F102CA00 result="key:\"\\361\\002\\312\\000\" height:4394 " I[2022-10-20|03:37:18.378] WSJSONRPC module=rpc-server protocol=websocket remote=192.168.2.24:54078 method=abci_query I[2022-10-20|03:37:18.386] ABCIQuery module=rpc path=/store/oracle/key data=3731343A303A30 result="key:\"714:0:0\" height:4394 " I[2022-10-20|03:37:18.386] WSJSONRPC module=rpc-server protocol=websocket remote=192.168.2.24:54078 method=abci_query I[2022-10-20|03:37:18.392] ABCIQuery module=rpc path=/account/bnb186t4z2pdu02khlfn5skqxpd36fwsa393mgu4hq data= result="value:\"K\\334L'\\nP\\n\\024>\\227Q(-\\343\\325k\\3753\\244,\\003\\005\\261\\322]\\016\\304\\261\\022\\014\\n\\003BNB\\020\\200\\334\\341\\251\\2427\\032&\\353Z\\351\\207!\\003v\\321\\027\\037\\3414y\\211\\2225\\037\\304+\\3328\\351\\220\\\"\\346\\211p\\021}\\223?@x\\300\\337\\032\\315\\234 \\002(\\001\" " I[2022-10-20|03:37:18.392] WSJSONRPC module=rpc-server protocol=websocket remote=192.168.2.24:54078 method=abci_query I[2022-10-20|03:37:18.393] Rejected bad transaction module=mempool tx=398EFE3CBE0BCCEDA5D9A74B7F627AA4639339604AE5119C04CD7362E7180EE4 res="&{CheckTx:code:65540 log:\"{\\\"codespace\\\":1,\\\"code\\\":4,\\\"abci_code\\\":65540,\\\"message\\\":\\\"signature verification failed\\\"}\" events:<> }" err=null I[2022-10-20|03:37:18.394] WSJSONRPC module=rpc-server protocol=websocket remote=192.168.2.24:54078 method=broadcast_tx_commit
https://github.com/tendermint/go-crypto/blob/master/pub_key.go
1 2 3 4 5 6 7 8 9 10 func (pubKey PubKeyEd25519) VerifyBytes(msg []byte , sig_ Signature) bool { sig, ok := sig_.(SignatureEd25519) if !ok { return false } pubKeyBytes := [32 ]byte (pubKey) sigBytes := [64 ]byte (sig) return ed25519.Verify(&pubKeyBytes, msg, &sigBytes) }
https://github.com/tendermint/ed25519/blob/master/ed25519.go
1 2 func Verify (publicKey *[PublicKeySize]byte , message []byte , sig *[SignatureSize]byte ) bool {}
验证sig 是否由公钥生成有效的消息签名
1 sig, err := privKey.Sign(msg)
sig是用私钥对msg进行签名
多次尝试后,没debug成功,先注释掉对应代码,跳过验签部分。
注释掉验签代码块:
注释掉签名验证部分的函数,编译新的node,运行oracle-relayer
,会报新的错误:
1 2022-10-21 02:01:32 ERROR process claim error: err=claim error, code=721903, log ={"codespace" :11,"code" :1007,"abci_code" :721903,"message" :"claim must be made by actively bonded validator" }
报错提示:验证者必须是活跃状态的,换成BC初始账户,继续运行oracle-relayer
出现新的报错:
1 2 3 4 5 process package failed, channel=3, sequence=0, error=ERROR: Codespace: 1 Code: 10 Message: " < 400000BNB" module=oracle
定位到
bnc-cosmos-sdk\x\oracle\handler.go 1 2 _, _, sdkErr := oracleKeeper.BkKeeper.SubtractCoins(ctx, sdk.PegAccount, fee)
应该是会从托管账户里扣除手续费
bnc-cosmos-sdk\types\cross_chain.go 1 2 3 4 5 var ( PegAccount = AccAddress(crypto.AddressHash([]byte ("BinanceChainPegAccount" ))) )
找到托管账户地址,给托管账户充值BNB:
1 bnbcli send --chain-id=715 --from=bnb1macd49chays0zqak9xedjzcxupkv4s87s7w5ze --amount="8000000000000:BNB" --to=bnb1v8vkkymvhe2sf7gd2092ujc6hweta38xadu2pj --sequence=4
成功声明预言值:
再次运行oracle-relayer
,这次claim 成功了。。
1 2 3 4 5 6 7 8 9 10 11 12 13 2022-10-21 03:40:03 INFO process current sequence, chain_id=714, seq =1 2022-10-21 03:40:03 INFO Fetch fetch block, height=27541 [*] claimLogs: [0xc00127f220] [*] len claimLogs: 1 [*] prophecy: <nil> [*] validatorAddress: bva1macd49chays0zqak9xedjzcxupkv4s87sz0yua [*] claimLog: &{2 714 1 0 11 000000000000000000000000000000000000000000000000000000000000000000e094c1b025e7406461e06185de04253267c61e3990f68204348202ca8463512988 0 1 0x7ed1aecbe7e2290acdd11f054e27eb511a27ae71e9ca5d648d9d24869ab33263 0x93f73ad8e3228c20ad34899f20e6d2c35b653e7e42510f282e2b5a86747589bb 1076 15 1666320721 1666320721} 2022-10-21 03:40:03 INFO process claim, chain_id=714, seq =1, payload=f848f8460b80b842000000000000000000000000000000000000000000000000000000000000000000e094c1b025e7406461e06185de04253267c61e3990f68204348202ca8463512988 2022-10-21 03:40:04 INFO Claim claim success, tx_hash=57D765A0D15825577434B29B6195D56DA147E188A4978FABF8AF27409529E444 2022-10-21 03:40:04 INFO process current sequence, chain_id=714, seq =1 2022-10-21 03:40:04 INFO Fetch fetch block, height=27645 [*] claimLogs: [] [*] len claimLogs: 0
声明成功,数据库状态修改为 已声明,值为2
检查验证者的余额:
未执行oracle-relayer之前
1 2 3 4 [root@localhost ~] {"type" :"bnbchain/Account" ,"value" :{"base" :{"address" :"bnb186t4z2pdu02khlfn5skqxpd36fwsa393mgu4hq" ,"coins" :[{"denom" :"BNB" ,"amount" :"1902906976744" }],"public_key" :{"type" :"tendermint/PubKeySecp256k1" ,"value" :"A3bRFx/hNHmJkjUfxCvaOOmQIuaJcBF9kz9AeMDfGs2c" },"account_number" :"2" ,"sequence" :"1" },"name" :"" ,"frozen" :null,"locked" :null,"flags" :"0" }} [root@localhost ~] {"type" :"bnbchain/Account" ,"value" :{"base" :{"address" :"bnb1v64323yrzekauf89qhsfrvyhe7xsvsck00jwpv" ,"coins" :[{"denom" :"BNB" ,"amount" :"3803093023256" }],"public_key" :{"type" :"tendermint/PubKeySecp256k1" ,"value" :"ApAG3Q9tZFtGw4vYvn72Q14QJHWxVg6uw3R/dKbMVTle" },"account_number" :"3" ,"sequence" :"1" },"name" :"" ,"frozen" :null,"locked" :null,"flags" :"0" }}
1 2 3 4 [root@localhost ~] {"type" :"bnbchain/Account" ,"value" :{"base" :{"address" :"bnb186t4z2pdu02khlfn5skqxpd36fwsa393mgu4hq" ,"coins" :[{"denom" :"BNB" ,"amount" :"1902906976744" }],"public_key" :{"type" :"tendermint/PubKeySecp256k1" ,"value" :"A3bRFx/hNHmJkjUfxCvaOOmQIuaJcBF9kz9AeMDfGs2c" },"account_number" :"2" ,"sequence" :"1" },"name" :"" ,"frozen" :null,"locked" :null,"flags" :"0" }} [root@localhost ~] {"type" :"bnbchain/Account" ,"value" :{"base" :{"address" :"bnb1v64323yrzekauf89qhsfrvyhe7xsvsck00jwpv" ,"coins" :[{"denom" :"BNB" ,"amount" :"3803115052627" }],"public_key" :{"type" :"tendermint/PubKeySecp256k1" ,"value" :"ApAG3Q9tZFtGw4vYvn72Q14QJHWxVg6uw3R/dKbMVTle" },"account_number" :"3" ,"sequence" :"1" },"name" :"" ,"frozen" :null,"locked" :null,"flags" :"0" }}
账户2余额由38030.93023256
变为38031.15052627
,余额增加了,bnb奖励应该是跨链成功了。
小结:
每个validator都要运行oracle-relayer
应该是每个validator都要运行beaconchain
anteHandler验证签名处还没搞定
bnc-cosmos-sdk\x\oracle\handler.go
里处理声明后,在BC上奖励分配的代码还没厘清楚
Refs: