Bystack跨链技术源码揭秘
123458243
发表于 2023-1-7 09:02:03
245
0
0
Vapor侧链采用DPOS和BBFT共识,TPS可以达到数万。此处就分析一下连接Bytom主链和Vapor侧链的跨链模型。
主侧链协同工作模型
h0 N4 t: s6 ] ^7 w8 G
1、技术细节! {! N" s5 e4 a% _* |, c
POW当前因为能源浪费而饱受诟病,而且POW本身在提高TPS的过程中遇到诸多问题,理论上可以把块变大,可以往块里面塞更多的交易。TPS是每秒出块数*块里面的交易数。但是也存在问题:小节点吃不消存储这么大的容量的内容,会慢慢变成中心化的模式,因为只有大财团和大机构才有财力去组建机房设备,成为能出块的节点。同时传输也存在问题,网络带宽是有限的,块的大小与网络传输的边际是有关的,不可能无限的去增加块的大小,网络边际上的人拿不到新块的信息,也会降低去中心化的程度,这就是为什么POW不能在提高可靠性的情况下,提高TPS的原因。0 @( K6 U2 l& f% I# `9 r
而BFT虽然去中心化较弱,但其效率和吞吐量高,也不需要大量的共识计算,非常环保节能,很符合Bystack侧链高TPS的性能需求
(1)跨链模型架构% F' `/ S J! D/ y4 G9 @! p
在Bystack的主侧链协同工作模型中,包括有主链、侧链和Federation。主链为bytom,采用基于对AI 计算友好型PoW(工作量证明)算法,主要负责价值锚定,价值传输和可信存证。侧链为Vapor,采用DPOS+BBFT共识,高TPS满足垂直领域业务。主链和侧链之间的资产流通主要依靠Federation。- F c; g- @: U' @( v
(2)节点类型
跨链模型中的节点主要有收集人、验证人和联邦成员。收集人监控联邦地址,收集交易后生成Claim交易进行跨链。验证人则是侧链的出块人。联邦成员由侧链的用户投票通过选举产生,负责生成新的联邦合约地址。! J, ~) `! Q' E1 Q, w
(3)跨链交易流程5 ^& M# g: e9 J! {6 d9 j) z
主链到侧链3 X- X$ C( B/ ]4 U8 z
主链用户将代币发送至联邦合约地址,收集人监控联邦地址,发现跨链交易后生成Claim交易,发送至侧链2 U3 u# J0 a, E+ n* r
侧链到主链
侧链用户发起提现交易,销毁侧链资产。收集人监控侧链至主链交易,向主链地址发送对应数量资产。最后联邦在侧链生成一笔完成提现的操作交易。8 G; O; {8 t0 z# f( U
2、代码解析
跨链代码主要处于federation文件夹下,这里就这部分代码进行一个介绍。! W) A# U0 \, m; L, @; d
(1)keeper启动* e L% D' k; \. W/ D0 z
整个跨链的关键在于同步主链和侧链的区块,并处理区块中的跨链交易。这部份代码主要在mainchain_keerper.go和sidechain_keerper.go两部分中,分别对应处理主链和侧链的区块。keeper在Run函数中启动。" e1 K. U% |- a. @ `3 t; r; ^
func (m *mainchainKeeper) Run() {
ticker := time.NewTicker(time.Duration(m.cfg.SyncSeconds) * time.Second)& y" r( {! T4 O, J/ o, l- J4 }
for ; true;
Run函数中首先生成一个定时的Ticker,规定每隔SyncSeconds秒同步一次区块,处理区块中的交易。
(2)主侧链同步区块
Run函数会调用syncBlock函数同步区块。2 Z2 V% C. r& W# [: C* _; b
func (m *mainchainKeeper) syncBlock() (bool, error) {1 I+ ^5 u2 ^* }+ \; L
chain := &orm.Chain{Name: m.chainName}2 Z) p+ h% S; J+ Y$ Z
if err := m.db.Where(chain).First(chain).Error; err != nil { k ]! L7 E% G9 ^2 ]2 K
return false, errors.Wrap(err, "query chain")
}
height, err := m.node.GetBlockCount()
//.. A% k3 _- a k# W& z" G
if height " M0 v7 ]# K% [% E$ }: k; X
这个函数受限会根据chainName从数据库中取出对应的chain。然后利用GetBlockCount函数获得chain的高度。然后进行一个伪确定性的检测。
height
主要是为了判断链上的资产是否已经不可逆。这里Confirmations的值被设为10。如果不进行这个等待不可逆的过程,很可能主链资产跨链后,主链的最长链改变,导致这笔交易没有在主链被打包,而侧链却增加了相应的资产。在此之后,通过GetBlockByHeight函数获得chain的下一个区块。* F4 o' f, C4 c2 z
nextBlockStr, txStatus, err := m.node.GetBlockByHeight(chain.BlockHeight + 1)
这里必须满足下个区块的上一个区块哈希等于当前chain中的这个头部区块哈希。这也符合区块链的定义。
if nextBlock.PreviousBlockHash.String() != chain.BlockHash {3 J& c4 m1 U8 o" t
//..5 t D! Z# @8 p, G! j/ i' v1 O
}
在此之后,通过调用tryAttachBlock函数进一步调用processBlock函数处理区块。
(3)区块处理6 z- f" B" x0 S. g; |# G0 V7 C
processBlock函数会判断区块中交易是否为跨链的deposit或者是withdraw,并分别调用对应的函数去进行处理。- ~6 s- z7 t- r9 u9 R F- a+ h
func (m *mainchainKeeper) processBlock(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus) error {+ L/ b n9 H$ t/ _" }
if err := m.processIssuing(block.Transactions); err != nil {
return err! y7 G) _2 X6 s' i) f; @9 Y, ~+ ]
}* `" [, G9 k1 z) z
for i, tx := range block.Transactions {; d! n& _$ r% m. n# G
if m.isDepositTx(tx) {
if err := m.processDepositTx(chain, block, txStatus, uint64(i), tx); err != nil {3 ?7 A, V% [7 i4 V5 H1 Q0 S8 d
return err+ r* V0 x7 c; l& B% W1 C
}6 ?1 d1 X! ~! _8 e: F! {
}
if m.isWithdrawalTx(tx) {
if err := m.processWithdrawalTx(chain, block, uint64(i), tx); err != nil {- f5 ~& b( r" ]* H
return err
}+ M: l$ t3 C2 d6 N( u1 s8 P
}! g" m. ]* p# J, K3 L+ c& j
}, M; G. I, O* @6 `2 _
return m.processChainInfo(chain, block)
}. u, K9 j" n* J- q: ^ X$ ^
在这的processIssuing函数,它内部会遍历所有交易输入Input的资产类型,也就是AssetID。当这个AssetID不存在的时候,则会去在系统中创建一个对应的资产类型。每个Asset对应的数据结构如下所示。& E* L+ l0 F; q, f
m.assetStore.Add(&orm.Asset{3 F$ h: H2 z7 f
AssetID: assetID.String(),
IssuanceProgram: hex.EncodeToString(inp.IssuanceProgram),- \. ~, P M1 b) r6 @$ L* u- a
VMVersion: inp.VMVersion,
RawDefinitionByte: hex.EncodeToString(inp.AssetDefinition),% A& [! W( J L3 a& x+ t" E
})" q- C# g6 X7 {
在processBlock函数中,还会判断区块中每笔交易是否为跨链交易。主要通过isDepositTx和isWithdrawalTx函数进行判断。
func (m *mainchainKeeper) isDepositTx(tx *types.Tx) bool {
for _, output := range tx.Outputs {: r2 Z/ x1 t+ y3 ^2 n& \, p
if bytes.Equal(output.OutputCommitment.ControlProgram, m.fedProg) {
return true
}5 a6 m2 |; [% b7 S% c! \4 g; f& O
}
return false7 m9 S" B3 ?* E/ B/ O# b9 ^- v8 q
}' V' } u# p1 @9 R3 Y/ e- n4 D
func (m *mainchainKeeper) isWithdrawalTx(tx *types.Tx) bool {; J* ?4 e0 m% M0 J2 t* a( d i
for _, input := range tx.Inputs {
if bytes.Equal(input.ControlProgram(), m.fedProg) {
return true
}- s4 n5 A! l/ y( g- b
}; a: L; A5 F# O& F; R
return false
}
看一下这两个函数,主要还是通过比较交易中的control program这个标识和mainchainKeeper这个结构体中的fedProg进行比较,如果相同则为跨链交易。fedProg在结构体中为一个字节数组。
type mainchainKeeper struct {0 m! V" k3 Y5 P1 |& c# E" ?
cfg *config.Chain
db *gorm.DB w% i+ q! I: f" `. @/ W
node *service.Node
chainName string
assetStore *database.AssetStore, s n8 E, r) z$ \1 L
fedProg []byte M7 U- S7 O Z! `. H
}
(4)跨链交易(主链到侧链的deposit)处理5 a; i; ~" B1 O: j6 F" P8 K
这部分主要分为主链到侧链的deposit和侧链到主链的withdraw。先看比较复杂的主链到侧链的deposit这部分代码的处理。
func (m *mainchainKeeper) processDepositTx(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus, txIndex uint64, tx *types.Tx) error {& ]- G- Y0 d/ q; Z" y
//..0 H9 \) C4 [( `2 F0 z! |) l3 O9 x/ ]
rawTx, err := tx.MarshalText()
if err != nil {
return err
}2 Y- b7 H4 m' Y
ormTx := &orm.CrossTransaction{
//..( p2 d' F( i9 n
}
if err := m.db.Create(ormTx).Error; err != nil {9 ~4 o/ l: V/ ~; g7 N/ K) S
return errors.Wrap(err, fmt.Sprintf("create mainchain DepositTx %s", tx.ID.String()))
}3 h4 I, V1 E) [0 P
statusFail := txStatus.VerifyStatus[txIndex].StatusFail
crossChainInputs, err := m.getCrossChainReqs(ormTx.ID, tx, statusFail)
if err != nil {
return err/ @9 R! N- q5 K( p
}
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))4 s W. H) O3 C A: T
}
}6 r; k2 M* U7 Q3 c# \
return nil
}
这里它创建了一个跨链交易orm。具体的结构如下。可以看到,这里它的结构体中包括有source和dest的字段。
ormTx := &orm.CrossTransaction{
ChainID: chain.ID,
SourceBlockHeight: block.Height,) Z) t( T7 e4 ~, M4 y9 P3 C
SourceBlockTimestamp: block.Timestamp,
SourceBlockHash: blockHash.String(),
SourceTxIndex: txIndex,
SourceMuxID: muxID.String(),
SourceTxHash: tx.ID.String(),7 T4 b1 }; n& p
SourceRawTransaction: string(rawTx),0 c2 v% F! w+ o
DestBlockHeight: sql.NullInt64{Valid: false},
DestBlockTimestamp: sql.NullInt64{Valid: false},' ~' c% h7 ? x9 H$ T) }
DestBlockHash: sql.NullString{Valid: false},
DestTxIndex: sql.NullInt64{Valid: false},
DestTxHash: sql.NullString{Valid: false},2 ~( H1 {! ^% C+ o8 f, P0 U& K
Status: common.CrossTxPendingStatus,! D. R6 }% m+ J; y ~
}
创建这笔跨链交易后,它会将交易存入数据库中。
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) {4 {- q5 V/ n2 ^8 O |2 u$ c" b
//..
switch {3 ~0 }% i4 e/ @( l( S' _
case segwit.IsP2WPKHScript(prog):) f8 P, O( c0 {$ n+ u/ ?8 D# ?: w) |
//..! H% q9 N; _4 |) {! Q
case segwit.IsP2WSHScript(prog):! h7 q) N3 u* x) t0 @
//..
}9 |. T, E4 h! E$ O6 Y9 ]
reqs := []*orm.CrossTransactionReq{}9 g n! ]7 z, Z; G) s$ b! T
for i, rawOutput := range tx.Outputs {- C- v6 _4 g9 v
//..' O! K" K( w2 m- _/ \( @) u% t
req := &orm.CrossTransactionReq{: J0 m( w ^9 }& c/ J- J2 h6 }
//..# P( K, i8 J& a
}
reqs = append(reqs, req)
}
return reqs, nil5 \' ]- B" |! c9 {; Q |- x
}+ ^# ^ L" u f9 l9 a
很显然,这个地方的交易类型有pay to public key hash 和 pay to script hash这两种。这里会根据不同的交易类型进行一个地址的获取。
switch {
case segwit.IsP2WPKHScript(prog):2 Z" w+ I# U# W* Y! ^
if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
fromAddress = wallet.BuildP2PKHAddress(pubHash, &vaporConsensus.MainNetParams)$ K/ R* @, o8 [6 k5 z
toAddress = wallet.BuildP2PKHAddress(pubHash, &vaporConsensus.VaporNetParams)! V4 L" j V4 I& v/ J8 E) K- G
}
case segwit.IsP2WSHScript(prog):
if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
fromAddress = wallet.BuildP2SHAddress(scriptHash, &vaporConsensus.MainNetParams)( E$ K! D- v* r* ]/ R
toAddress = wallet.BuildP2SHAddress(scriptHash, &vaporConsensus.VaporNetParams)
}
}5 x& g" Q; F3 w' [
在此之后,函数会遍历所有交易的输出,然后创建跨链交易请求,具体的结构如下。
req := &orm.CrossTransactionReq{2 s1 b! H6 j5 B2 {
CrossTransactionID: crossTransactionID,3 I: `5 r2 y% a& y+ I
SourcePos: uint64(i),
AssetID: asset.ID,
AssetAmount: rawOutput.OutputCommitment.AssetAmount.Amount,& t4 S7 K V( A3 q1 O9 [6 H
Script: script,, O# Y8 A8 t1 W
FromAddress: fromAddress,% a) ]. s, f! b* w& W+ `4 Y
ToAddress: toAddress,( z; E: r. U/ @# l
}; f, g2 K6 ~$ Y. ]1 l
创建完所有的跨链交易请求后,返回到processDepositTx中一个crossChainInputs数组中,并存入db。
for _, input := range crossChainInputs {
if err := m.db.Create(input).Error; err != nil {& ]' V# w+ N2 A* e# Z
return errors.Wrap(err, fmt.Sprintf("create DepositFromMainchain input: txid(%s), pos(%d)", tx.ID.String(), input.SourcePos))
}! w/ }; D: a1 R' M4 S6 x% s6 [
}
到这里,对主链到侧链的deposit已经处理完毕。
(5)跨链交易(侧链到主链的withdraw)交易处理( H2 C# L6 d. L# v
这部分比较复杂的逻辑主要在sidechain_keeper.go中的processWithdrawalTx函数中。这部分逻辑和上面主链到侧链的deposit逻辑类似。同样是创建了orm.crossTransaction结构体,唯一的改变就是交易的souce和dest相反。这里就不作具体描述了。3 T9 S. D* Q$ |* c
3、跨链优缺点
优点 x5 t& ~4 |! j) @. w! `
(1) 跨链模型、代码较为完整。当前有很多项目使用跨链技术,但是真正实现跨链的寥寥无几。
(2) 可以根据不同需求实现侧链,满足多种场景9 A& |2 |: ~2 N9 Z) `
缺点
(1) 跨链速度较慢,需等待10个区块确认,这在目前Bytom网络上所需时间为30分钟左右, M7 v/ x- a' M6 ~) B' h4 T
(2) 相较于comos、polkadot等项目,开发者要开发侧链接入主网成本较大
(3) 只支持资产跨链,不支持跨链智能合约调用$ W7 q: ]: s: ^! w
4、跨链模型平行对比Cosmos
可扩展性
bystack的主测链协同工作模型依靠Federation,未形成通用协议。其他开发者想要接入其跨链网络难度较大。Cosmos采用ibc协议,可扩展性较强。+ ?$ a) e$ B% a0 `" V+ V
代码开发进度
vapor侧链已经能够实现跨链。Cosmos目前暂无成熟跨链项目出现,ibc协议处于最终开发阶段。* d: I% m. e1 n8 A% e$ Y6 j
跨链模型7 v$ D: l! [! T+ N6 M0 \, c
vapor为主侧链模型,Cosmos为Hub-Zone的中继链模型。
5、参考建议
侧链使用bbft共识,非POW的情况下,无需等待10个交易确认,增快跨链速度。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人



