天天看點

Bystack跨鍊技術源碼解讀

Bystack是由比原鍊團隊提出的一主多側鍊架構的BaaS平台。其将區塊鍊應用分為三層架構:底層賬本層,側鍊擴充層,業務适配層。底層賬本層為Layer1,即為目前比較成熟的采用POW共識的Bytom公鍊。側鍊擴充層為Layer2,為多側鍊層,vapor側鍊即處于Layer2。

Bystack跨鍊技術源碼解讀

(圖檔來自Bystack白皮書)

Vapor側鍊采用DPOS和BBFT共識,TPS可以達到數萬。此處就分析一下連接配接Bytom主鍊和Vapor側鍊的跨鍊模型。

主側鍊協同工作模型

Bystack跨鍊技術源碼解讀

1、技術細節

POW目前因為能源浪費而飽受诟病,而且POW本身在提高TPS的過程中遇到諸多問題,理論上可以把塊變大,可以往塊裡面塞更多的交易。TPS是每秒出塊數*塊裡面的交易數。但是也存在問題:小節點吃不消存儲這麼大的容量的内容,會慢慢變成中心化的模式,因為隻有大财團和大機構才有财力去組建機房裝置,成為能出塊的節點。同時傳輸也存在問題,網絡帶寬是有限的,塊的大小與網絡傳輸的邊際是有關的,不可能無限的去增加塊的大小,網絡邊際上的人拿不到新塊的資訊,也會降低去中心化的程度,這就是為什麼POW不能在提高可靠性的情況下,提高TPS的原因。

而BFT雖然去中心化較弱,但其效率和吞吐量高,也不需要大量的共識計算,非常環保節能,很符合Bystack側鍊高TPS的性能需求

(1)跨鍊模型架構

在Bystack的主側鍊協同工作模型中,包括有主鍊、側鍊和Federation。主鍊為bytom,采用基于對AI 計算友好型PoW(工作量證明)算法,主要負責價值錨定,價值傳輸和可信存證。側鍊為Vapor,采用DPOS+BBFT共識,高TPS滿足垂直領域業務。主鍊和側鍊之間的資産流通主要依靠Federation。

(2)節點類型

跨鍊模型中的節點主要有收集人、驗證人和聯邦成員。收集人監控聯邦位址,收集交易後生成Claim交易進行跨鍊。驗證人則是側鍊的出塊人。聯邦成員由側鍊的使用者投票通過選舉産生,負責生成新的聯邦合約位址。

(3)跨鍊交易流程

主鍊到側鍊

主鍊使用者将代币發送至聯邦合約位址,收集人監控聯邦位址,發現跨鍊交易後生成Claim交易,發送至側鍊

側鍊到主鍊

側鍊使用者發起提現交易,銷毀側鍊資産。收集人監控側鍊至主鍊交易,向主鍊位址發送對應數量資産。最後聯邦在側鍊生成一筆完成提現的操作交易。

2、代碼解析

跨鍊代碼主要處于federation檔案夾下,這裡就這部分代碼進行一個介紹。

(1)keeper啟動

整個跨鍊的關鍵在于同步主鍊和側鍊的區塊,并處理區塊中的跨鍊交易。這部份代碼主要在mainchain_keerper.go和sidechain_keerper.go兩部分中,分别對應處理主鍊和側鍊的區塊。keeper在Run函數中啟動。

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函數中首先生成一個定時的Ticker,規定每隔SyncSeconds秒同步一次區塊,處理區塊中的交易。

(2)主側鍊同步區塊

Run函數會調用syncBlock函數同步區塊。

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
}           

這個函數受限會根據chainName從資料庫中取出對應的chain。然後利用GetBlockCount函數獲得chain的高度。然後進行一個僞确定性的檢測。

height <= chain.BlockHeight+m.cfg.Confirmations

           

主要是為了判斷鍊上的資産是否已經不可逆。這裡Confirmations的值被設為10。如果不進行這個等待不可逆的過程,很可能主鍊資産跨鍊後,主鍊的最長鍊改變,導緻這筆交易沒有在主鍊被打包,而側鍊卻增加了相應的資産。在此之後,通過GetBlockByHeight函數獲得chain的下一個區塊。

nextBlockStr, txStatus, err := m.node.GetBlockByHeight(chain.BlockHeight + 1)

           

這裡必須滿足下個區塊的上一個區塊哈希等于目前chain中的這個頭部區塊哈希。這也符合區塊鍊的定義。

if nextBlock.PreviousBlockHash.String() != chain.BlockHash {
    //..
}
           

在此之後,通過調用tryAttachBlock函數進一步調用processBlock函數處理區塊。

(3)區塊處理

processBlock函數會判斷區塊中交易是否為跨鍊的deposit或者是withdraw,并分别調用對應的函數去進行處理。

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函數,它内部會周遊所有交易輸入Input的資産類型,也就是AssetID。當這個AssetID不存在的時候,則會去在系統中建立一個對應的資産類型。每個Asset對應的資料結構如下所示。

m.assetStore.Add(&orm.Asset{
AssetID:           assetID.String(),
IssuanceProgram:   hex.EncodeToString(inp.IssuanceProgram),
VMVersion:         inp.VMVersion,
RawDefinitionByte: hex.EncodeToString(inp.AssetDefinition),
})           

在processBlock函數中,還會判斷區塊中每筆交易是否為跨鍊交易。主要通過isDepositTx和isWithdrawalTx函數進行判斷。

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
}           

看一下這兩個函數,主要還是通過比較交易中的control program這個辨別和mainchainKeeper這個結構體中的fedProg進行比較,如果相同則為跨鍊交易。fedProg在結構體中為一個位元組數組。

type mainchainKeeper struct {
    cfg        *config.Chain
    db         *gorm.DB
    node       *service.Node
    chainName  string
    assetStore *database.AssetStore
    fedProg    []byte
}           

(4)跨鍊交易(主鍊到側鍊的deposit)處理

這部分主要分為主鍊到側鍊的deposit和側鍊到主鍊的withdraw。先看比較複雜的主鍊到側鍊的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
}           

這裡它建立了一個跨鍊交易orm。具體的結構如下。可以看到,這裡它的結構體中包括有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,
    }           

建立這筆跨鍊交易後,它會将交易存入資料庫中。

if err := m.db.Create(ormTx).Error; err != nil {
        return errors.Wrap(err, fmt.Sprintf("create mainchain DepositTx %s", tx.ID.String()))
}           

在此之後,這裡會調用getCrossChainReqs。這個函數内部較為複雜,主要作用就是周遊交易的輸出,傳回一個跨鍊交易的請求數組。具體看下這個函數。

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這兩種。這裡會根據不同的交易類型進行一個位址的擷取。

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)
        }
    }           

在此之後,函數會周遊所有交易的輸出,然後建立跨鍊交易請求,具體的結構如下。

req := &orm.CrossTransactionReq{
   CrossTransactionID: crossTransactionID,
   SourcePos:          uint64(i),
   AssetID:            asset.ID,
   AssetAmount:        rawOutput.OutputCommitment.AssetAmount.Amount,
   Script:             script,
   FromAddress:        fromAddress,
   ToAddress:          toAddress,
   }           

建立完所有的跨鍊交易請求後,傳回到processDepositTx中一個crossChainInputs數組中,并存入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))
        }
}           

到這裡,對主鍊到側鍊的deposit已經處理完畢。

(5)跨鍊交易(側鍊到主鍊的withdraw)交易處理

這部分比較複雜的邏輯主要在sidechain_keeper.go中的processWithdrawalTx函數中。這部分邏輯和上面主鍊到側鍊的deposit邏輯類似。同樣是建立了orm.crossTransaction結構體,唯一的改變就是交易的souce和dest相反。這裡就不作具體描述了。

3、跨鍊優缺點

優點

(1) 跨鍊模型、代碼較為完整。目前有很多項目使用跨鍊技術,但是真正實作跨鍊的寥寥無幾。

(2) 可以根據不同需求實作側鍊,滿足多種場景

缺點

(1) 跨鍊速度較慢,需等待10個區塊确認,這在目前Bytom網絡上所需時間為30分鐘左右

(2) 相較于comos、polkadot等項目,開發者要開發側連結入主網成本較大

(3) 隻支援資産跨鍊,不支援跨鍊智能合約調用

**4、**跨鍊模型平行對比Cosmos

可擴充性

bystack的主測鍊協同工作模型依靠Federation,未形成通用協定。其他開發者想要接入其跨鍊網絡難度較大。Cosmos采用ibc協定,可擴充性較強。

代碼開發進度

vapor側鍊已經能夠實作跨鍊。Cosmos目前暫無成熟跨鍊項目出現,ibc協定處于最終開發階段。

跨鍊模型

vapor為主側鍊模型,Cosmos為Hub-Zone的中繼鍊模型。

5、參考建議

側鍊使用bbft共識,非POW的情況下,無需等待10個交易确認,增快跨鍊速度。

作者:詩人

繼續閱讀