Skip to content

第七章:从应用升级为平台

ch07

大家还记得最早的那种老式手机吗?基本上你只能用它来发信息和打电话,或者砸核桃(仅适用于诺基亚手机)。第六章之前的Q链就类似于这种手机,只能进行简单的转账交易。后来的手机变得先进一点了,内置了一些应用,你可以收发邮件或者玩一些小游戏。第六章增加了脚本执行能力的Q链类似这种手机,变强了,但是有限。再后来智能手机横空出世,有大量的应用供你安装,你直接拥有了一台可以装进口袋的小型计算机。智能手机的这种飞跃会在Q链上重演吗?是的!

第五章的Q链我们可以称之为1.0版,第六章的Q链我们称之为1.1版。这一章将带你见证Q链从1.x版到2.0版的全面进化。升级之后的Q链将可以执行复杂的智能合约,为生态发展提供一个稳固的平台。

应用程序

为了更好地理解Q链的这次升级,必须从传统的应用程序开始说起。我们的电脑上,手机上,甚至手表上,都跑着各种各样的应用程序(Application),简称应用(App)。那么这些应用是如何工作的呢?我们这里只讨论一个极度简化后的模型,如下图所示:

app

以电脑为例。我们下载并安装一个应用程序之后,它通常会作为可执行文件存储在电脑的硬盘上。当我们打开这个应用的时候,它会被操作系统加载到内存里(1)。然后电脑的CPU开始解析并执行这个应用程序的一条条指令(2)。这些指令在执行的过程中,需要一些临时的空间来辅助计算,这些空间也从内存里分配(3),然后用完后要还给系统。如果程序需要读写一些持久化的状态,就必须借助磁盘文件来完成(4)。

这就是普通应用程序的最简工作原理,下面我们来看一下Q公司是如何把这套系统搬到Q链上的。

账户模型

在第三章,为了对交易进行审计,我们把记账模式从账户模型改成了UTXO模型,该模型支撑Q链一路走到了脚本时代。UTXO模型虽确实满足了审计需求,但是较为依赖索引服务,对于复杂的计算并不友好。于是这一章我们选择再退回到账户模型,直接通过账户记录余额等信息,以此规避UTXO的索引依赖、简化复杂计算。但这并不是历史的倒退,因为我们并不是简单回退到原来的版本,而是采用一种新的方式。新的账户表只有两列,一列是账户地址(公钥的哈希值),一列是账户数据,如下图(左)所示:

account

根据账户地址,我们可以从账户表里查到某个账户的数据。暂时可以认为某个账户的所有数据都存储在一起,其详细内容如上图(右)所示。账户数据里,只有“Q币余额”的作用是一目了然的,无需解释。其余的数据,基本也能猜到作用,我们会在后面的小节里详细介绍。

交易格式

由于更换了存储模型,交易格式也要跟着变化。之前基于UTXO的交易格式完全不需要了,取而代之的是新版的交易格式,内容如下图所示:

tx

还记得第二章介绍过的重放攻击吗?这里的交易次数就是用来起同样的防护作用的。一个新的账户,其交易次数是零,然后每执行一笔交易增加一。一笔新的交易,其交易次数必须等于签署账户交易次数加一,否则这就是一笔过时的交易,Q链会拒绝处理。举个例子,如果某账号的交易次数是10,那么它签署的下一笔交易的交易次数必须是11

数字签名的含义跟原来是一样的。注意,通过交易数据和数字签名,可以恢复出签署这笔交易的公钥,进而可以算出对应的账户地址。因此我们没有必要把账户地址附加在交易数据里,这样可以提高交易在Q链中传播和存储的效率。地址的恢复和计算过程如下图所示:

address

目标地址、转账金额、燃料限制、燃料价格、合约调用数据,这些内容要到后面才能解释清楚,暂且不管。这里只提醒一句:转账金额和后面两个带“燃料”二字的字段是两码事,前者是你要转给对方多少Q币,后者关系到这笔交易本身要花多少手续费。

区块格式

区块格式基本保持不变。但是由于我们不使用UTXO模型了,所有的数据都存在账户表里,所以我们必须想一个办法,防止账户数据被随意更改。可不可以每准备好一个区块(执行完里面的全部交易),就把整个的账户数据表做一次哈希运算,把得到的哈希值记录下来?先不管效率如何,至少思路是对的。新的区块就只是增加了这么一个信息,格式如下图所示:

block

我们把整张账户表表示的全部状态叫做Q链的世界状态(World State),这个哈希值就相当于世界状态的指纹。这个世界中的任何一个微小变化,都会导致它的指纹发生变化。当然,直接算整张账户表的哈希值是非常低效的,下一个小节会介绍Q链是如何高效完成这个计算的。

世界状态

首先,我们完全可以先计算每个账户的哈希值,然后再计算整个世界的哈希值。这样,那些没有变化过的账户数据就不用参与世界哈希值的计算了。按照这个思路,我们还可以先计算代码和持久存储的哈希值,然后再用这两个哈希值去计算某个账户的哈希值。我们后面会看到,相比于其他数据,代码和持久存储是比较大的,所以单独计算它们的哈希值是有好处的。比如某个账户只是Q币余额变了,那么可以非常快的算出这个账户的哈希值。我们干脆就把代码和持久存储放到别的地方,如下图所示:

account2

在第四章的时候,我们介绍过默克尔哈希树。实际上,Q公司并不是直接使用数据库,而是用一种类似默克尔树的结构来存储账户数据的,叫做MPT树。我们不需要了解这种数据结构的细节,只需要知道,这种数据结构既可以很容易通过账户地址查到对应的账户数据,也可以非常快速地算出世界哈希。这里我们先用默克尔树来理解世界状态,如下图所示:

world

智能合约

现在我们可以来解释一些前面没有交代清楚的细节了。先来看账户数据,我们还不清楚代码和持久存储是干啥用的。这里给出答案。Q链允许每个地址附带一个可执行程序,这个程序叫做智能合约(Smart Contract),简称合约。智能合约在执行的时候,可以操作一块专属于自己的持久化的存储空间,简称存储(Storage)。账户数据里的这两个地方,就是用来保存智能合约代码,以及当做存储空间来用的。

那么如何执行智能合约呢?通过发起交易。调用哪个智能合约呢?通过交易的目标地址指定。想让智能合约干一些什么事情呢?通过交易的合约调用数据指定。我们还可以顺便给智能合约转一些Q币,只要把数量写在交易的转账金额里就可以了。交易执行的时候,这些Q币会从发起调用的账户转到被调用的账户。那么剩下的燃料限制和燃料价格又是怎么回事儿?等一下就会介绍。

了解完这些信息以后,我们就可以简单分析一下Q链上的应用(智能合约)是如何工作的了。当某个交易被执行的时候,Q链节点会根据目标地址找到具体的账户,然后加载它的智能合约代码(1),准备执行。交易的合约调用数据和其他一些信息会被当作只读数据(2)加载,供合约在运行期间读取。合约在运行期间还需要一些临时的存储空间来做一些计算,这些空间会在一片虚拟的内存(3)中分配。合约还会读写自己的持久化的存储(4)。最后,一切就绪,Q链启动一台专门的虚拟机(5)来一条一条执行合约的指令。Q链智能合约的简化执行流程如下图所示:

vm

不过Q链智能合约的执行,和普通的计算机程序执行是有很大不同的,这里说明一下。

第一,原子性。Q链的交易具备原子性,因此Q链智能合约的执行也具备原子性。合约在执行期间会读写合约账户的持久存储,或导致某些账户的Q币余额发生变化。如果合约执行成功,所有的改动会全部生效。否则,所有的改动会全部回退(除了执行合约所花费的Q币)。

第二,运行时间。普通的程序可以一直运行,直到自动结束,或者用户把它关掉。但是智能合约每次只能运行很短的时间。想象一下,智能合约是在交易中执行的,如果某个智能合约执行时间过长,会导致整个节点卡住。这就是我们在第六章介绍过的拒绝服务攻击。之前为了解决这个问题,Q链直接把循环结构给删除了,导致Q链只能执行相对简单的脚本,并且要按照脚本的大小(字节数)掏手续费。现在Q链要执行复杂的智能合约,就必须要有图灵完备的虚拟机。那么把循环结构加回来后,Q链怎么解决停机问题呢?这个问题下一小节来回答。

第三,安全性。相比普通程序,智能合约的安全性尤为关键。传统应用即便出现内存泄漏或崩溃等问题,影响多局限于功能使用,而智能合约绝不能存在严重漏洞,否则可能直接导致用户资金全部损失。更值得警惕的是,还存在可重入攻击(Reentrancy Attack)、溢出攻击(Overflow Attack)等专门针对智能合约的恶意攻击手段。正因如此,智能合约通常会交由专业审计公司进行安全审计,但即便经过审计,也难以完全杜绝漏洞。这就像计算机领域的“停机问题”,机器无法判定另一个程序是否会终止,人类同样无法百分百确认一款程序已彻底无懈可击。

另外补充一点:虽然我们将部署在Q链上的代码称为智能合约,但这个概念的诞生时间远早于区块链技术。它最早由著名密码学家尼克・萨博(Nick Szabo)在20世纪90年代提出,核心是通过预设代码自动执行合约条款、无需第三方介入。只不过受限于当时的技术条件,这一构想未能落地,直到区块链技术出现后,才为智能合约提供了可信的执行环境,使其真正发挥价值并被广泛应用。

指令计费

你想象一台汽车,行驶在一个环线上。假设它的刹车突然失灵了,无法减速,会发生什么呢?如果这辆车的方向盘也失去了控制,只能走直线,那么它很快会偏离道路,最后撞到障碍物停下来。如果我们还可以控制方向,可以让它一直沿着环线转圈,直到汽油(Gas)烧完,慢慢停止。你是否可以从这个“停车”问题得到一些启发?

对于停机问题,前一章的解法就好比把汽车的方向盘给拆掉。车只能前进无法后退,直到无路可走。而这一章呢?我们知道汽车只能携带有限的汽油,烧光这些油自然也就走不动了。好的,懂了,但是Q链能用这个办法吗?能,我们只要对Q链虚拟机执行的每一条指令都计费就可以了。

Q链规定,虚拟机执行每一条指令都是要消耗一定数量虚拟燃料的。注意不是直接收取Q币,而是消耗燃料。为什么要这样做呢?是为了动态调整燃料的价格(Gas Price,按Q币计价)。在Q链压力大的时候,系统会调高燃料价格,这样执行相同的操作花的燃料费(Gas Fee)就会变高,导致压力下降。反过来,系统空闲的时候燃料费会降低,用户可以利用这个时间段进行一些不太重要的操作以便节约燃料费。燃料费用、燃料数量、燃料价格之间的关系如下所示:

消耗的燃料费用 = 消耗的燃料数量 × 燃料价格

现在我们可以回过头来看看交易里的那几个字段了。当某个账户发起交易的时候,Q链会从他的余额里扣走两笔互不相干的钱。第一笔是交易里写明的转账金额,它会原封不动地转给目标账户,一分不多一分不少。第二笔是这笔交易可能烧掉的燃料费,Q链会按“燃料限制×燃料价格”先冻结一笔钱,也就是这笔交易最多可能花掉的手续费。等合约执行完毕,实际消耗掉的那部分付给打包这笔交易的矿工,没烧完的部分退还给发起者。请注意这两笔钱是分开的:转多少Q币完全由你自己说了算,而烧掉多少燃料费,则取决于合约实际执行了多少条指令。如果燃料费都用光了,合约还没有执行结束,Q链系统会把它强制停止,并回退所有的状态变更(包括那笔转账)。注意,合约执行用掉的燃料费是不能回退的,否则黑客就可以不停地发交易对Q链进行攻击。

有没有可能发起合约的调用者是善意的,但是可能因为不小心调用了错误的合约,或者传入了错误的参数,或者合约有bug,导致不小心陷入死循环把燃料费都用光呢?也是有可能的,所以我们在交易中增加了燃料限制这个信息。如果燃料的消耗达到了燃料限制,Q链就会按照上面描述的方式终止交易。

指令计费,以及整个合约执行的流程如下图所示:

halt

生态系统

和传统的应用程序不同,Q链的智能合约是分布式(去中心化)的,会被整个Q链的所有节点执行。为了与传统的应用进行区别,我们把Q链上的智能合约叫做去中心化应用(Decentralized Application),通常简称为DApp。

Q链升级以后,很快就出现了各种类型的DApp。有各种各样的代币(Token),也有各种数字收藏品(NFT)。出现了去中心化自治组织(Decentralized Autonomous Organization,简称DAO),在Q链上通过投票共同作出一些决议。甚至还出现了虚拟空间游戏,允许玩家在Q链上打造自己的数字乐园,我们通常称这种基于区块链的虚拟交互空间为元宇宙(Metaverse)。

我们知道互联网发展经历了不同的阶段。Web1时代是“只读”模式,内容由平台提供,用户只能看。典型的代表就是早期的雅虎和搜狐等门户网站。Web2时代变成“可读可写”,用户能发布文字或照片、拍短视频创作内容,但这些内容和数据仍归平台所有。微信、微博、抖音等是这个时期的代表。而Q链支持DApp后,很多公司开始转型,改用用户钱包登录 —— 不用再记账号密码,钱包地址就是身份,用户在应用里的内容和资产也归自己管,不再被平台把控。我们称这个新的时期为Web3时代。

不过,在Q链上涌现的各类DApp里,最受关注的要数那些模拟现实经济活动的应用。Q链升级后的某一年夏天,一大批这类应用集中落地Q链:有的复刻现实中的交易场景,让用户能在链上完成虚拟资产兑换;有的模仿传统借贷平台,提供点对点的数字资产借贷服务;还有的参照保险业务逻辑,开发出基于智能合约的去中心化保障产品。短短一个夏天,这类应用的爆发式增长让整个行业都为之惊叹。我们把这种以数字形式模拟现实金融、依托区块链运行的体系,称为“去中心化金融”(Decentralized Finance),简称DeFi;而那个因DeFi爆发而被永远记住的夏天,也被大家称作DeFi之夏(DeFi Summer)。

本章小结

这一章发生了太多的事情,我们来总结一下。首先,Q链拥有了执行复杂智能合约的能力。为了达到这个目的,Q链记账模式从UTXO模式改回了账户模式,并通过指令计费的方式解决了停机问题,让虚拟机变成图灵完备。通过这些改变,Q链完成了从分布式记账系统到分布式计算系统的转变,变成了一台强大的去中心化计算机,可以在上面执行DApp。

这次升级带来的变化远不止技术层面。Q币的角色也跟着变了,它曾经是“数字黄金”,现在又多了一重身份:驱动整个网络运转的可编程燃料。DApp生态迅速繁荣起来,互联网也随之从Web2迈向Web3。而那个见证了无数金融创新的“DeFi之夏”,至今仍是让后来者心向往之的行业传奇。至于挖矿耗电的争议,Q链也从未回避,多年以后它将从PoW切换到PoS,不过那又是另一个故事了。

基于Q链的DApp生态极为庞大,本书难以穷尽所有内容。在后续章节中,我们将聚焦部分具有代表性的应用展开讨论:第八章会讲解各类代币协议和链上资产,第九章将介绍极具创新性的恒定乘积自动做市协议(AMM),第十章则会阐述链上借贷协议。

本书以 CC0 1.0 协议发布,可自由使用