Bystack 跨链技术源码解读

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

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
一种多样性比特资产的区块链交互协议 杭州

推荐标签 标签

  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    181 引用 • 821 回帖
  • Laravel

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

    20 引用 • 23 回帖 • 741 关注
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 397 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 641 关注
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    239 引用 • 224 回帖
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 279 关注
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    188 引用 • 319 回帖 • 251 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖 • 1 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 400 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 636 关注
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    8 引用 • 26 回帖 • 3 关注
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    369 引用 • 1846 回帖 • 3 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 47 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    29 引用 • 202 回帖 • 29 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    211 引用 • 358 回帖 • 1 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 175 关注
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    336 引用 • 324 回帖
  • App

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

    91 引用 • 384 回帖
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 29 关注
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    118 引用 • 54 回帖 • 3 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 373 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    95 引用 • 901 回帖
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    6 引用 • 15 回帖 • 12 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1708 回帖
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 2 关注