Bystack是由比原鏈團隊提出的一主多側(cè)鏈架構(gòu)的BaaS平臺。其將區(qū)塊鏈應(yīng)用分為三層架構(gòu):底層賬本層,側(cè)鏈擴展層,業(yè)務(wù)適配層。底層賬本層為Layer1,即為目前比較成熟的采用POW共識的Bytom公鏈。側(cè)鏈擴展層為Layer2,為多側(cè)鏈層,vapor側(cè)鏈即處于Layer2。

Vapor側(cè)鏈采用DPOS和BBFT共識,TPS可以達到數(shù)萬。此處就分析一下連接Bytom主鏈和Vapor側(cè)鏈的跨鏈模型。
主側(cè)鏈協(xié)同工作模型

1、技術(shù)細節(jié)
POW當前因為能源浪費而飽受詬病,而且POW本身在提高TPS的過程中遇到諸多問題,理論上可以把塊變大,可以往塊里面塞更多的交易。TPS是每秒出塊數(shù)*塊里面的交易數(shù)。但是也存在問題:小節(jié)點吃不消存儲這么大的容量的內(nèi)容,會慢慢變成中心化的模式,因為只有大財團和大機構(gòu)才有財力去組建機房設(shè)備,成為能出塊的節(jié)點。同時傳輸也存在問題,網(wǎng)絡(luò)帶寬是有限的,塊的大小與網(wǎng)絡(luò)傳輸?shù)倪呺H是有關(guān)的,不可能無限的去增加塊的大小,網(wǎng)絡(luò)邊際上的人拿不到新塊的信息,也會降低去中心化的程度,這就是為什么POW不能在提高可靠性的情況下,提高TPS的原因。
而BFT雖然去中心化較弱,但其效率和吞吐量高,也不需要大量的共識計算,非常環(huán)保節(jié)能,很符合Bystack側(cè)鏈高TPS的性能需求
(1)跨鏈模型架構(gòu)
在Bystack的主側(cè)鏈協(xié)同工作模型中,包括有主鏈、側(cè)鏈和Federation。主鏈為bytom,采用基于對AI 計算友好型PoW(工作量證明)算法,主要負責(zé)價值錨定,價值傳輸和可信存證。側(cè)鏈為Vapor,采用DPOS+BBFT共識,高TPS滿足垂直領(lǐng)域業(yè)務(wù)。主鏈和側(cè)鏈之間的資產(chǎn)流通主要依靠Federation。
(2)節(jié)點類型
跨鏈模型中的節(jié)點主要有收集人、驗證人和聯(lián)邦成員。收集人監(jiān)控聯(lián)邦地址,收集交易后生成Claim交易進行跨鏈。驗證人則是側(cè)鏈的出塊人。聯(lián)邦成員由側(cè)鏈的用戶投票通過選舉產(chǎn)生,負責(zé)生成新的聯(lián)邦合約地址。
(3)跨鏈交易流程
主鏈到側(cè)鏈
主鏈用戶將代幣發(fā)送至聯(lián)邦合約地址,收集人監(jiān)控聯(lián)邦地址,發(fā)現(xiàn)跨鏈交易后生成Claim交易,發(fā)送至側(cè)鏈
側(cè)鏈到主鏈
側(cè)鏈用戶發(fā)起提現(xiàn)交易,銷毀側(cè)鏈資產(chǎn)。收集人監(jiān)控側(cè)鏈至主鏈交易,向主鏈地址發(fā)送對應(yīng)數(shù)量資產(chǎn)。最后聯(lián)邦在側(cè)鏈生成一筆完成提現(xiàn)的操作交易。
2、代碼解析
跨鏈代碼主要處于federation文件夾下,這里就這部分代碼進行一個介紹。
(1)keeper啟動
整個跨鏈的關(guān)鍵在于同步主鏈和側(cè)鏈的區(qū)塊,并處理區(qū)塊中的跨鏈交易。這部份代碼主要在mainchain_keerper.go和sidechain_keerper.go兩部分中,分別對應(yīng)處理主鏈和側(cè)鏈的區(qū)塊。keeper在Run函數(shù)中啟動。
func (m *mainchainKeeper) Run() {
ticker := time.NewTicker(time.Duration(m.cfg.SyncSeconds) * time.Second)
for ; true; <-ticker.C {
for {
isUpdate, err := m.syncBlock()
if err != nil {
//..
}
if !isUpdate {
break
}
}
}
}
Run函數(shù)中首先生成一個定時的Ticker,規(guī)定每隔SyncSeconds秒同步一次區(qū)塊,處理區(qū)塊中的交易。
(2)主側(cè)鏈同步區(qū)塊
Run函數(shù)會調(diào)用syncBlock函數(shù)同步區(qū)塊。
func (m *mainchainKeeper) syncBlock() (bool, error) {
chain := &orm.Chain{Name: m.chainName}
if err := m.db.Where(chain).First(chain).Error; err != nil {
return false, errors.Wrap(err, "query chain")
}
height, err := m.node.GetBlockCount()
//..
if height <= chain.BlockHeight+m.cfg.Confirmations {
return false, nil
}
nextBlockStr, txStatus, err := m.node.GetBlockByHeight(chain.BlockHeight + 1)
//..
nextBlock := &types.Block{}
if err := nextBlock.UnmarshalText([]byte(nextBlockStr)); err != nil {
return false, errors.New("Unmarshal nextBlock")
}
if nextBlock.PreviousBlockHash.String() != chain.BlockHash {
//...
return false, ErrInconsistentDB
}
if err := m.tryAttachBlock(chain, nextBlock, txStatus); err != nil {
return false, err
}
return true, nil
}
這個函數(shù)受限會根據(jù)chainName從數(shù)據(jù)庫中取出對應(yīng)的chain。然后利用GetBlockCount函數(shù)獲得chain的高度。然后進行一個偽確定性的檢測。
height <= chain.BlockHeight+m.cfg.Confirmations
主要是為了判斷鏈上的資產(chǎn)是否已經(jīng)不可逆。這里Confirmations的值被設(shè)為10。如果不進行這個等待不可逆的過程,很可能主鏈資產(chǎn)跨鏈后,主鏈的最長鏈改變,導(dǎo)致這筆交易沒有在主鏈被打包,而側(cè)鏈卻增加了相應(yīng)的資產(chǎn)。在此之后,通過GetBlockByHeight函數(shù)獲得chain的下一個區(qū)塊。
nextBlockStr, txStatus, err := m.node.GetBlockByHeight(chain.BlockHeight + 1)
這里必須滿足下個區(qū)塊的上一個區(qū)塊哈希等于當前chain中的這個頭部區(qū)塊哈希。這也符合區(qū)塊鏈的定義。
if nextBlock.PreviousBlockHash.String() != chain.BlockHash {
//..
}
在此之后,通過調(diào)用tryAttachBlock函數(shù)進一步調(diào)用processBlock函數(shù)處理區(qū)塊。
(3)區(qū)塊處理
processBlock函數(shù)會判斷區(qū)塊中交易是否為跨鏈的deposit或者是withdraw,并分別調(diào)用對應(yīng)的函數(shù)去進行處理。
func (m *mainchainKeeper) processBlock(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus) error {
if err := m.processIssuing(block.Transactions); err != nil {
return err
}
for i, tx := range block.Transactions {
if m.isDepositTx(tx) {
if err := m.processDepositTx(chain, block, txStatus, uint64(i), tx); err != nil {
return err
}
}
if m.isWithdrawalTx(tx) {
if err := m.processWithdrawalTx(chain, block, uint64(i), tx); err != nil {
return err
}
}
}
return m.processChainInfo(chain, block)
}
在這的processIssuing函數(shù),它內(nèi)部會遍歷所有交易輸入Input的資產(chǎn)類型,也就是AssetID。當這個AssetID不存在的時候,則會去在系統(tǒng)中創(chuàng)建一個對應(yīng)的資產(chǎn)類型。每個Asset對應(yīng)的數(shù)據(jù)結(jié)構(gòu)如下所示。
m.assetStore.Add(&orm.Asset{
AssetID: assetID.String(),
IssuanceProgram: hex.EncodeToString(inp.IssuanceProgram),
VMVersion: inp.VMVersion,
RawDefinitionByte: hex.EncodeToString(inp.AssetDefinition),
})
在processBlock函數(shù)中,還會判斷區(qū)塊中每筆交易是否為跨鏈交易。主要通過isDepositTx和isWithdrawalTx函數(shù)進行判斷。
func (m *mainchainKeeper) isDepositTx(tx *types.Tx) bool {
for _, output := range tx.Outputs {
if bytes.Equal(output.OutputCommitment.ControlProgram, m.fedProg) {
return true
}
}
return false
}
func (m *mainchainKeeper) isWithdrawalTx(tx *types.Tx) bool {
for _, input := range tx.Inputs {
if bytes.Equal(input.ControlProgram(), m.fedProg) {
return true
}
}
return false
}
看一下這兩個函數(shù),主要還是通過比較交易中的control program這個標識和mainchainKeeper這個結(jié)構(gòu)體中的fedProg進行比較,如果相同則為跨鏈交易。fedProg在結(jié)構(gòu)體中為一個字節(jié)數(shù)組。
type mainchainKeeper struct {
cfg *config.Chain
db *gorm.DB
node *service.Node
chainName string
assetStore *database.AssetStore
fedProg []byte
}
(4)跨鏈交易(主鏈到側(cè)鏈的deposit)處理
這部分主要分為主鏈到側(cè)鏈的deposit和側(cè)鏈到主鏈的withdraw。先看比較復(fù)雜的主鏈到側(cè)鏈的deposit這部分代碼的處理。
func (m *mainchainKeeper) processDepositTx(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus, txIndex uint64, tx *types.Tx) error {
//..
rawTx, err := tx.MarshalText()
if err != nil {
return err
}
ormTx := &orm.CrossTransaction{
//..
}
if err := m.db.Create(ormTx).Error; err != nil {
return errors.Wrap(err, fmt.Sprintf("create mainchain DepositTx %s", tx.ID.String()))
}
statusFail := txStatus.VerifyStatus[txIndex].StatusFail
crossChainInputs, err := m.getCrossChainReqs(ormTx.ID, tx, statusFail)
if err != nil {
return err
}
for _, input := range crossChainInputs {
if err := m.db.Create(input).Error; err != nil {
return errors.Wrap(err, fmt.Sprintf("create DepositFromMainchain input: txid(%s), pos(%d)", tx.ID.String(), input.SourcePos))
}
}
return nil
}
這里它創(chuàng)建了一個跨鏈交易orm。具體的結(jié)構(gòu)如下??梢钥吹?,這里它的結(jié)構(gòu)體中包括有source和dest的字段。
ormTx := &orm.CrossTransaction{
ChainID: chain.ID,
SourceBlockHeight: block.Height,
SourceBlockTimestamp: block.Timestamp,
SourceBlockHash: blockHash.String(),
SourceTxIndex: txIndex,
SourceMuxID: muxID.String(),
SourceTxHash: tx.ID.String(),
SourceRawTransaction: string(rawTx),
DestBlockHeight: sql.NullInt64{Valid: false},
DestBlockTimestamp: sql.NullInt64{Valid: false},
DestBlockHash: sql.NullString{Valid: false},
DestTxIndex: sql.NullInt64{Valid: false},
DestTxHash: sql.NullString{Valid: false},
Status: common.CrossTxPendingStatus,
}
創(chuàng)建這筆跨鏈交易后,它會將交易存入數(shù)據(jù)庫中。
if err := m.db.Create(ormTx).Error; err != nil {
return errors.Wrap(err, fmt.Sprintf("create mainchain DepositTx %s", tx.ID.String()))
}
在此之后,這里會調(diào)用getCrossChainReqs。這個函數(shù)內(nèi)部較為復(fù)雜,主要作用就是遍歷交易的輸出,返回一個跨鏈交易的請求數(shù)組。具體看下這個函數(shù)。
func (m *mainchainKeeper) getCrossChainReqs(crossTransactionID uint64, tx *types.Tx, statusFail bool) ([]*orm.CrossTransactionReq, error) {
//..
switch {
case segwit.IsP2WPKHScript(prog):
//..
case segwit.IsP2WSHScript(prog):
//..
}
reqs := []*orm.CrossTransactionReq{}
for i, rawOutput := range tx.Outputs {
//..
req := &orm.CrossTransactionReq{
//..
}
reqs = append(reqs, req)
}
return reqs, nil
}
很顯然,這個地方的交易類型有pay to public key hash 和 pay to script hash這兩種。這里會根據(jù)不同的交易類型進行一個地址的獲取。
switch {
case segwit.IsP2WPKHScript(prog):
if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
fromAddress = wallet.BuildP2PKHAddress(pubHash, &vaporConsensus.MainNetParams)
toAddress = wallet.BuildP2PKHAddress(pubHash, &vaporConsensus.VaporNetParams)
}
case segwit.IsP2WSHScript(prog):
if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
fromAddress = wallet.BuildP2SHAddress(scriptHash, &vaporConsensus.MainNetParams)
toAddress = wallet.BuildP2SHAddress(scriptHash, &vaporConsensus.VaporNetParams)
}
}
在此之后,函數(shù)會遍歷所有交易的輸出,然后創(chuàng)建跨鏈交易請求,具體的結(jié)構(gòu)如下。
req := &orm.CrossTransactionReq{
CrossTransactionID: crossTransactionID,
SourcePos: uint64(i),
AssetID: asset.ID,
AssetAmount: rawOutput.OutputCommitment.AssetAmount.Amount,
Script: script,
FromAddress: fromAddress,
ToAddress: toAddress,
}
創(chuàng)建完所有的跨鏈交易請求后,返回到processDepositTx中一個crossChainInputs數(shù)組中,并存入db。
for _, input := range crossChainInputs {
if err := m.db.Create(input).Error; err != nil {
return errors.Wrap(err, fmt.Sprintf("create DepositFromMainchain input: txid(%s), pos(%d)", tx.ID.String(), input.SourcePos))
}
}
到這里,對主鏈到側(cè)鏈的deposit已經(jīng)處理完畢。
(5)跨鏈交易(側(cè)鏈到主鏈的withdraw)交易處理
這部分比較復(fù)雜的邏輯主要在sidechain_keeper.go中的processWithdrawalTx函數(shù)中。這部分邏輯和上面主鏈到側(cè)鏈的deposit邏輯類似。同樣是創(chuàng)建了orm.crossTransaction結(jié)構(gòu)體,唯一的改變就是交易的souce和dest相反。這里就不作具體描述了。
3、跨鏈優(yōu)缺點
優(yōu)點
(1) 跨鏈模型、代碼較為完整。當前有很多項目使用跨鏈技術(shù),但是真正實現(xiàn)跨鏈的寥寥無幾。
(2) 可以根據(jù)不同需求實現(xiàn)側(cè)鏈,滿足多種場景
缺點
(1) 跨鏈速度較慢,需等待10個區(qū)塊確認,這在目前Bytom網(wǎng)絡(luò)上所需時間為30分鐘左右
(2) 相較于comos、polkadot等項目,開發(fā)者要開發(fā)側(cè)鏈接入主網(wǎng)成本較大
(3) 只支持資產(chǎn)跨鏈,不支持跨鏈智能合約調(diào)用
4、跨鏈模型平行對比Cosmos
可擴展性
bystack的主測鏈協(xié)同工作模型依靠Federation,未形成通用協(xié)議。其他開發(fā)者想要接入其跨鏈網(wǎng)絡(luò)難度較大。Cosmos采用ibc協(xié)議,可擴展性較強。
代碼開發(fā)進度
vapor側(cè)鏈已經(jīng)能夠?qū)崿F(xiàn)跨鏈。Cosmos目前暫無成熟跨鏈項目出現(xiàn),ibc協(xié)議處于最終開發(fā)階段。
跨鏈模型
vapor為主側(cè)鏈模型,Cosmos為Hub-Zone的中繼鏈模型。
5、參考建議
側(cè)鏈使用bbft共識,非POW的情況下,無需等待10個交易確認,增快跨鏈速度。
作者:詩人