Cosmos由很多模块组成,Cosmos Hub、比特币以太坊转接桥、去中心化交易所、Ethermint等,这篇文章大概介绍这些模块的基础性技术,Basecoin框架。
Tendermint
首先来看下什么是tendermint,这个单词有多个概念,首先它是一家公司的名字;狭义的技术角度上它是一种基于BFT的共识算法,有完整的论文资料;广义的技术角度上它是一个完整的区块链开发框架,对应于github上的一个代码仓库。
tendermint共识算法不同于POW或POS挖矿,而是由多位验证人各自拥有投票权,轮流出块,然后验证人们并对每个候选块进行投票,获得大于2/3的投票数,即可认为该区块完成共识,并达成最终确定性,且不会被分叉回滚。Pow和一些Pos共识的容错度是49%,否则会产生双花攻击。tendermint共识中的作恶包括:对虚假数据投赞成票,或者对多个分叉块投赞成票等。如果全体验证人的作恶比例大于1/3,区块链会卡住不能产生新块,并不断延长轮次重试间隔。如果大于2/3,区块链将产生分叉,最终必需要由官方团队采用硬分叉的方式解决。
Tendermint共识本身是一个共识节点一票的,权重都一样,但为了适应公有链的cosmos有代币架构,衍生出了tendermint-dpos共识,有用户代币授权、验证人投票权重、验证人惩罚逻辑和通胀逻辑等。
在此,我们主要讨论其第三个概念,tendermint框架集成了狭义的tendermint共识算法的go版本实现、以及区块数据结构、p2p网络、区块和状态数据存储等基本功能,是一个直接能运行的纯净区块链系统。链上运行的应用,需要用户通过区块链应用接口ABCI(Application Blockchain Interface)来自行拓展。可以使用任意开发语言或架构,做到区块链本身和应用的高度解耦。
Basecoin基础 - ABCI
Basecoin就是tendermint上的一个abci应用,致力于成为数字货币的基本开发框架,或ABCI应用的开发示例。如果到tendermint公司官网可下载到两个二进制文件,basecoin和basecli,前者是节点服务端,后者是节点的命令行轻客户端。
我们可以使用basecli生成账户,用于获得初始资金,并设置验证人集合(也可以只有一个),配置完成后会有一个genesis文件,并初始化一条basecoin链。配置basecli连接节点端口,并根据genesis文件检查chainID。
Tendermint轻节点
这里要重点说明的是由于tendermint的全新轻节点协议。pow区块链的轻节点需要从头至尾下载所有的区块头,才能验证前后哈希连续并挑选出难度最高长度最长的分叉。而tendermint的区块有效性完全由是否包含2/3的验证人投票决定,所以诞生了完全不同于pow区块链的轻节点架构。轻节点客户端只要知道验证人集合,就可以直接跳到最新的区块来验证,省去全区块头下载的麻烦。这也要求验证人集合不能过快大幅变更,这样轻节点就只需定期从区块链更新验证人集合的身份,或直接从其他可信数据源获取。
轻节点对tendermint非常重要,因为传统pow区块链的出块时间大部分是被挖矿占用,真正的区块计算时间只需很短,比如以太坊一个区块的时间目前是20s,而parity直接同步区块链的速度可能是每秒20个块,再考虑到网络传播时延,也就是说可能只有1%的时间在真正计算用户的交易。而tendermint省去了挖矿的时间,除去两轮投票和全球区块传播所占用的不可压缩时间,可能50%的时间都可以用于区块计算,那么如果用户的一个钱包节点从头同步tendermint区块链,他如果每个块都要再重新计算一遍的话,他的追赶速度可能只有出块速度的2倍,要追上最新块就要花很长时间。当然实际情况中区块并不会完全占满,但两者的本质区别已经显而易见,这也是大家把pos类共识作为提升区块链吞吐量的重要手段。
以太坊的状态根是直接在本块中的,比特币没有全局状态根,只有交易树根。所以比特币的轻节点只能证明一笔交易发生过,不能证明这个币有没有被花掉。他们pow挖矿的nonce类似于投票权,也是存储在当前块里。具体到tendermint而言,他们首先对块内交易的有效性做判断,并直接启动共识投票并出块,交易的处理是在共识投票之后,所以区块的状态根保存在后一个块里。这么做是为了把交易排序网络传播共识投票作为区块链主时间线,交易处理不占用出块时间线,而在链下并行执行。而每个节点收到的2/3多数票不一样的,所以也只能放在下一个块里做事后证明。轻节点客户端需要向全节点查询某个交易,全节点会返回给它块头、签名、以及状态根下的交易证明数据。
Basecoin普通转账交易SendTx
账户数据结构:【公钥、序列号、余额列表】
账户余额数据结构为:【名称、数量】
所以basecoin有类似以太坊的账户总余额模型,但包含多资产,而非比特币UTXO的无总余额模型。账户地址是公钥的ripemd160哈希,状态树使用IAVL树。其中:
普通转账交易为SendTx的结构为【Gas,Fee,Input列表、Output列表】
Input结构为【用户地址、token列表、序列号、签名、公钥】
Output结构为【地址、token列表】。
用户地址如果是首次出现时(序列号为0)需要声明公钥,因为tendermint不能像比特币或以太坊那样从签名推倒出公钥。Gas限定了交易可以使用的限额(类似于以太坊的gaslimit),Fee是愿意支付的手续费,这里没有直接声明的gasprice概念(隐含的是Fee/Gas)。这种交易的格式允许多个账户的多种资产在同一笔交易里完成。
Basecoin插件转账交易AppTx
Basecoin只提供了基础的转账功能,类似于只有增强的比特币的功能,但如何能像以太坊那样实现功能丰富的智能合约呢,Basecoin提出了插件的概念。比如basecoin默认安装了一个叫counter的示例插件,对应countercli作为客户端,可以用于查询交易总数。插件可以支持任意功能,是实现了插件接口的go语言包。通过插件更新basecoin的状态,并存储任意数据,插件的数据也通过KV结构存储在梅克尔树中。
Apptx:不像SendTx那样提供多输入和输出的代币转账功能,而是像以太坊交易那样,把一个Input通过交易发送给一个插件,还可以附带一些数据,结构为【Gas,手续费,Input,插件名称,数据】,Input指的是本交易中要转移的价值。
Basecoin跨链插件IBC
跨链插件IBC(InterBlockchain Communication)为了让两条链相互之间都是对方的轻节点,并能够相互验证跨链数据。之前提到为了验证H高度块的执行结果,你需要块H+1里的前块状态根AppHash和2/3的前块签名LastCommit。
假设现在有chain1和chain2,演示如何从chain1向chain2发送数据。
第1步:IBCRegisterChainTx,将chain1的信息注册到chain2上,包含chain1_ID和创世块文件。
第2步:IBCUpdateChainTx:向chain2广播chain1的最新状态,包括块头和签名。
第3步:IBCPacketCreateTx:在chain1上,生成跨链交易tx,包含目标chain2、序列号(两条链间跨链交易的自增编号)、类别(货币或数据)、附属数据。
第4步:IBCPacketPostTx:提交跨链交易tx给chain2,指定来源链ID,以及tx所在的高度,并附带上该交易的证明数据。
同时chain2在chain1上有一个多重签名账户multisig2,账户所有人是chain2上的全体验证人。第3步完成后,chain1的货币就被multisig2锁定,并代chain2上的用户托管了。第4步后,chain2就得知了这个交易,再给chain2上相应的用户发行该货币,至此chain1的代币就转移到了chain2上。
相反,如果chain2上的持币用户申请将代币转回给chain1上的用户,就可以销毁货币并指定chain1上的用户。Chain2的验证人在chain1上发起多重签名交易,释放该部分的代币给chain1的对应用户。
Basecoin Relay节点
上面所说的第2步需要在有新块时就调用,第4不需要在有新跨链交易时就调用,这个工作刚才是手动的,现在我们需要把这些relay的操作进行自动化。通过启动一个专门的relay节点,实时监听两条链的变化,可以将这两个交易的发送自动化。
完成自动relay后,现在就可以直接用一条命令进行转账了,形如basecli1 tx send --amount=12345mycoin --sequence=2 --to=test-chain-2/$BROKE --name=money。之后就可以直接在目标链上查询到余额了。