Bystack 跨链技术源码解读

本贴最后更新于 1936 天前,其中的信息可能已经事过景迁

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 个交易确认,增快跨链速度。

作者:诗人

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
bytom
一种多样性比特资产的区块链交互协议 杭州

推荐标签 标签

  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    26 引用 • 222 回帖 • 164 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    176 引用 • 995 回帖 • 1 关注
  • 倾城之链
    23 引用 • 66 回帖 • 138 关注
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    209 引用 • 2031 回帖
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 212 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 1 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 69 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1433 引用 • 10052 回帖 • 483 关注
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    117 引用 • 99 回帖 • 224 关注
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    34 引用 • 467 回帖 • 740 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    30 引用 • 123 回帖 • 1 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 346 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    12 引用 • 52 回帖 • 84 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    70 引用 • 533 回帖 • 778 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    20 引用 • 23 回帖 • 725 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    565 引用 • 3532 回帖
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    36 引用 • 37 回帖 • 534 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    152 引用 • 3780 回帖 • 1 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    168 引用 • 504 回帖
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 2 关注
  • 自由行
    3 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    11 引用 • 5 回帖 • 606 关注
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    130 引用 • 793 回帖
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 47 关注