1. 程式人生 > >Bystack跨鏈技術原始碼解讀

Bystack跨鏈技術原始碼解讀

Bystack是由比原鏈團隊提出的一主多側鏈架構的BaaS平臺。其將區塊鏈應用分為三層架構:底層賬本層,側鏈擴充套件層,業務適配層。底層賬本層為Layer1,即為目前比較成熟的採用POW共識的Bytom公鏈。側鏈擴充套件層為Layer2,為多側鏈層,vapor側鏈即處於Layer2。

(圖片來自Bystack白皮書)

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

主側鏈協同工作模型

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個交易確認,增快跨鏈速度。