大概整整两年前,我从一篇介绍“区块链”技术的文章里知道了“以太坊”这个项目,当时当然没想到它会对我个人产生如此大的影响。在其后这两年时间里,我投入了大量的时间和精力来学习它、研究它,做了多次技术分享,甚至还写了讲解智能合约开发的书、开发了关于智能合约开发的在线课程;所以不管未来我们各自的走向如何,以太坊都已经在我生命中留下了重重的一笔。在我们相识两周年之际,我觉得我总该要写点儿什么。
新年的这第一个月我过的很充实:只感了两次冒,每次大概半个月……。本来我是计划在月底前写完技术专题“工程师眼中的比特币和以太坊”的,可惜也无奈地再次被教育了:“年龄不是问题,但身体是问题。”所以临近月底只能以这篇文章勉强交个作业吧。
2017 年底的时候,我曾在简书发过一篇博客,介绍“以太坊是什么”,但那是一篇对以太坊 Homestead 官方文档的翻译稿节选。客观地讲,当时我本人对以太坊的理解也还不是非常深入透彻,所以这篇原创的“再谈:以太坊是什么”还是有一些参考价值的,我相信这篇文章也许比一年前的那篇翻译稿更好懂一些,而且真正讲出了一些“本质性”的基本概念。
当然,这不是一篇 hardcore 的技术文章,我的初衷是基于我本人对以太坊的理解,从概念上给那些还不是很清楚地知道“以太坊是什么”的朋友做一个简单的扫盲。这也是我写这个新的技术专题的初衷:我希望让更多软件工程师能了解区块链、了解智能合约,希望有更多的人能真正搞懂一些最基本的概念,并以此为契机深入到区块链和智能合约的世界中。因为本文的主体内容选自我的技术专题“工程师眼中的比特币和以太坊”,所以文中对比特币系统技术细节的引述可能会略显突兀、缺少铺垫;如果你对比特币的技术细节还不了解,也可能会引起一些迷惑,对此我也表示歉意。不过我还是希望你能喜欢本文的内容和讲解方式;此外如果能给我提供任何形式的反馈,我也将感激不尽。
完整阅读本文约需要 15 到 20 分钟。
时间已经来到了 2019 年 1 月,相信对很多人来讲,“区块链”这个词恐怕已经不陌生了,并且大概也已经听过“比特币”的名号;不过,如果有人问你:“以太坊是什么”,你能够很清晰、准确地给他解释清楚吗?
我想如果你看过“以太坊白皮书(Ethereum White Paper)”,你大概会用它的标题来回答这个问题:“A Next-Generation Smart Contract and Decentralized Application Platform”,翻成中文就是“下一代的智能合约和去中心化应用程序平台”。没错,这很权威,也很准确。但是,我想即使对于软件工程师来讲,这个答案中引入的两个新概念“智能合约”和“去中心化应用程序”其实也并不那么“一目了然”。显然,我们不应该用未知的概念去解释其他未知的概念。不过我想这仍然称得上是一个不错的开端,就让我先沿着这个思路聊聊“智能合约”和“去中心化应用程序”吧。
什么是智能合约
智能合约(Smart Contract)这个词最初是由一位美国的计算机科学博士尼克萨博(Nick Szabo)在他 1997 年发表的一篇论文中提出来的。智能合约指的是一种基于计算机技术实现的,可以免除人工干预而自动执行、自动校验、自动基于外部指令给出回应的具有交互性或者互操作性的合约;其本质是一种计算机程序。这个概念本身相对比较抽象,所以尼克萨博本人在后来的一些访谈中曾经用自动取款机或者自动售货机来作为智能合约的物理实例;它们就是基于某些公开的、众所周知的规则,能够根据用户的指令和实际业务数据的状态给出可预期的回应的这样一种应用程序。
比如我们去自动售货机购买某种商品,我们先要选择商品和购买数量,如果自动售货机中库存不足,它应该会给出相应的提示;如果库存充足,它会提示用户进行实际支付,比如用现金或者电子支付;如果支付金额不足,它会给出提示,并继续等待,直到支付金额足够或者用户取消交易;如果支付金额足够,它们就会吐出正确数量的商品,并进行找零。自动售货机在这整个过程中对用户操作的响应应该是确定的、可预测的、可自动完成的,当然也应该是正确的。那么能够基于实际情况给出这些正确响应的程序,就可以认为是智能合约。
当然,其实这个过程并不“智能”,从某种意义讲,也并不是“合约”。所以这个词从其诞生开始就有一些争议,尤其是它会给一些不明所以的用户某种程度的误解。从本质来看,所谓“智能合约”实际上只是一种可以基于某种状态数据自动给出回应的应用程序而已。不过出于历史原因,“智能合约”的叫法还是被业内接受并延用了下来。
智能合约有什么用
这里我仅举两个最简单的例子来解释智能合约的意义。
首先是一个租房合约的例子。比如我和房东订立了一个租房合约,约定我每月付给房东 1000 元房租,合约期限为一年。在没有智能合约的情况下,这个合约的履行是需要很多人工介入的,我付钱的动作必须由我主动完成;如果出现意外情况,也需要第三方的介入才能解决,比如中介机构的居间调停乃至民事诉讼、强制执行等等。而如果有一种无需信任的智能合约平台,这整个过程就可以变为下面这样:
我和房东可以在某个智能合约平台上订立一个智能合约,创建合约的时候,我将一年的租金锁定到合约里,然后从订立合约开始,每经过一个月,房东可以从合约中提取一个月的租金,直到合约期限完结或者经双方确认中止合约,合约自动给我退回剩余未支付的租金。这样的场景是可以在像以太坊这样的智能合约平台上简单实现的,这整个过程无需任何第三方介入,并且可以保证执行的过程严格按照约定的条件完成。
请注意,这个过程是“无需信任(trustless)”的。也就是合约双方不需要相互认识乃至相互信任,而是基于一种不可篡改、不可抵赖、不可逆转的自动化的技术方案保证了整个过程的可信性,同时不需要任何第三方的介入。这就是智能合约带来的最重要的特性。
当然,这个例子中的一些细节是和我们普遍采用的人工处理的方式有差异的:比如我要支付的一年的租金是需要在订立合约时就要锁定到合约里的,而不是可以像人工操作的方式那样每月提供;又比如要中止合约时是需要双方确认的,如果有一方故意不进行确认,那么合约会被锁死,这需要事先考虑到,并在合约中设计相应的解决方法。不过即使存在实操上的差异,这种自动化的、不可抵赖的智能合约选项依然称得上是一种进步。
第二个例子是一个简单的保险。比如某地区的一个农民可以与某保险公司订立一份合约,合约条款大概可以是这样:该农民于今年 6 月 30 日前支付 x 元保险费给保险公司;如果今年 7 月到 9 月某地区的平均降雨量不足 100 毫米,则农民可以在 9 月 30 日之后得到 y 元的保险赔偿;否则无需赔偿。
这个保险,同样可以用像以太坊这样的智能合约平台简单地实现:农民和保险公司可以在 6 月 30 日之前订立合约,农民将 x 元、保险公司将 y 元同时锁定到这个合约中;在 9 月 30 日之后,农民或者保险公司都可以向合约申请执行;合约可以自动从无利益关系的第三方公共服务(比如气象台网站)获取 7 月到 9 月的降水量信息;如果降水量低于 100 毫米,则将 x + y 元支付给农民,反之则将 x + y 元支付给保险公司。这整个过程同样无需人工介入,可以基于智能合约自动完成。
这个例子中提到的“无利益关系的第三方公共服务”,通常是通过一种区块链系统/智能合约平台以外的独立数据源来提供的。这是因为区块链系统本身无法产生或者直接获取某些特定的数据,比如天气数据、股票价格等等,那么就需要独立的“链外”数据提供方来提供具体的数据。这种提供链外数据的服务,也就是所谓的“预言机(Oracle)”。因为本文只是扫盲性质,这里不再具体展开了。
所以,从逻辑概念上看,智能合约可以理解为一种会经过“技术公证”的状态存储和状态转换(也就是程序执行),以此来确保这个过程“无需信任”。(稍后我会从技术角度来解释这个概念。)
去中心化应用程序又是什么
在理解了智能合约的概念之后,再来理解去中心化应用程序就比较容易了。
目前的绝大多数 Web 应用,其用户数据都是保存在由某个公司、组织或者个人控制的“服务器”或者“服务器组”之中的,这些应用中的绝大部分业务计算,也是由这些服务器或者服务器组来提供的;所以这种模式,可以认为是一种“中心化(centralized)”的模式。
那么“去中心化应用程序(Decentralized Application)”,也就是将数据保存到“基于点对点网络的时间戳服务器(即区块链)”这样的,并不是由中心化的公司或组织控制的服务中,并且由这样的“去中心化”服务来提供具体的业务数据计算能力的一种应用程序。“去中心化应用程序”也可以简单地理解为是基于智能合约进行状态追踪和计算的一种应用程序。
这里的“基于点对点网络的时间戳服务器”的说法是出自比特币白皮书,这也是“区块链”一词的原始出处。
当然,对于一个应用程序来讲,并不是所有运算都需要用智能合约来实现,通常也不应该这样设计;而是应该根据业务需求,将那些需要做“全网公证”的状态计算在智能合约中实现,来使应用程序获得某些“无需信任”的可信性。
好了,在了解了智能合约和去中心化应用程序的基本概念之后,我相信你已经大概知道以太坊是什么了。这里再重复一下:以太坊就是一个智能合约和去中心化应用程序平台。 不过,我还有第二种答案。请你稍安勿躁,让我尝试基于以太坊的总体设计思路和技术方案概要来慢慢为你揭晓。
以太坊的诞生
与比特币不同,以太坊白皮书的作者不是匿名的,它的初稿是由 Vitalik Buterin 于 2013 年底完成的。Vitalik 在 1994 年出生于俄罗斯,后来随父母移居加拿大,在写出以太坊白皮书的时候,他只有 19 岁,刚刚从滑铁卢大学大一辍学。不过,在他写出以太坊白皮书之前,他已经是一个知名的比特币研究者和写作者,他也是在 2011 年创刊的世界上第一个加密货币期刊——Bitcoin Magazine 的联合创始人。可以说,以太坊白皮书并不是像比特币白皮书那样“凭空出现的”。Vitalik 本人从 2013 年下半年就开始思考如何对比特币的脚本系统进行扩展来支持“通用目的”的计算任务。
2013 年 12 月,Vitalik 完成了以太坊白皮书,并在小范围内进行了分享,引起了一些业内人士的关注,其中就包括了“精通比特币(Mastering Bitcoin)”一书的作者 Andreas M. Antonopoulos,但他当时在忙于写作精通比特币,所以并未直接参与以太坊创建初期的具体工作。真正在工程上帮助 Vitalik 实现了以太坊这个伟大构想的则是 Gavin Wood 博士。
Gavin Wood 博士是英国约克大学的计算机科学博士,在以太坊项目最初的两年里,他是在工程上贡献最大的人:他于 2014 年 1 月完成了以太坊的 PoC-1(即第一个概念验证程序,也是以太坊的 C++ 客户端的最初原型);随后在 3 月发表了以太坊黄皮书(即以太坊协议的细节说明和技术手册,是实现以太坊客户端的基础技术资料,在工程上具有极其重要的指导意义,它也是我们了解和学习以太坊协议技术细节的主要参考资料);同年 8 月公布了专门为智能合约开发而设计的高级语言 Solidity。
2015 年 7 月 30 日,以太坊主网正式上线,创世区块产生,世界上第一个成功的公共智能合约平台才真正诞生。
以太坊的开创意义在于:它借鉴了由比特币系统创造出来的“通过工作量证明算法达成共识的、基于点对点网络的时间戳服务器”(也就是所谓的“区块链”),构建出了一种可以执行“通用目的”计算任务的基础设施,将“区块链”的可编程性提升到了一个新的高度;创造出了真正的“智能合约”平台。
区块链范式
从技术层面讲,我们可以把类似于比特币这样的、通过分布式共识进行“公证”的公共账本系统(也就是所谓的“区块链”)抽象地理解为一个“状态转换系统(state transition system)”。这种系统会维持一个“全局的状态”(我们用 S 来表示),然后通过系统中发生的“交易”(我们用 tx 来表示)来进行相应的状态转换以达到一个“新的全局状态”(我们用 S' 来表示)。那么这种状态转换就可以抽象地表示为:Apply(S, tx) = S'
,其中的 Apply 即“状态转换函数”。
下图是比特币中的状态转换示意:
图中用 <txid>:<output_index>
的形式来表示某个 UTXO:其中 txid
即交易哈希,用来标识某个具体的交易;output_index
即交易生成的输出(UTXO)的索引。
UTXO,即 Unspent Transaction Output,是基于比特币的数据模型产生的一个术语。比特币中的交易都是由若干输入(Input)和若干输出(Output)组成的。按照协议约定,一个输入必定是某个历史交易的某个输出的引用,通过在输入中包含特定的“解锁脚本(unlocking script)”来对历史交易的输出中指定的“锁定脚本(locking script)”进行解锁,以此来“消耗/使用(spend)”相应的输出。那些未被任何“输入”引用过的“输出”就是 UTXO。
如上图所示,比特币中的交易会消耗/使用若干 UTXO,并通过合并和拆分来生成等量的新的 UTXO。上图中的实例就是通过 7b53ab84 这个交易的第 1 个输出和 3ce6f712 这个交易的第 2 个输出来生成了 3 个新的 UTXO,即 bb75a980 这个交易(也就是上图中这个交易)的输出 0、1 和 2。于是,系统整体的状态,就从 5 个 UTXO 变为了 6 个 UTXO。
所以,比特币系统其实就是一个对当前系统中所有可用的 UTXO 的状态记录,它通过一个具体交易来对当前可用的若干 UTXO 进行合并和拆分来生成新的 UTXO,并将系统状态转换为新的 UTXO 集合;同时,这个过程是在一个点对点网络中进行“全网公证”的。
考虑到像比特币这样的“区块链”系统是以区块为单位来打包记录交易的,我们其实已经可以对“区块链”下一个技术上的定义了:“区块链”,其实就是一个通过交易来触发状态变动,并以区块为单位来记录状态变动的状态转换系统。刚刚介绍过的状态转换范式 Apply(S, tx) = S'
,也就是所谓的“区块链范式(blockchain paradigm)”。
请注意,这是一个通用的范式,它适用于比特币,也适用于以太坊,并且同样适用于其他类似于比特币和以太坊这样的基于“区块链”的分布式共识账本系统。而不同的区块链系统的差别就在于如何表示系统中的状态以及如何定义状态转换函数(也就是交易)。
那么,对于目标是要实现一个“智能合约平台”的以太坊来讲,使用比特币的 UTXO 模型来保存系统状态和构造交易数据可行么?
以太坊的“账户模型”和“存储”
在刚刚介绍智能合约的概念时我提到过:智能合约其实可以看作是一种用于记录和修改“状态”的应用程序。从计算机科学的角度来看,这其实就是“状态机(state machine)”。每个智能合约,都可以看作是一个自定义的状态机。那么对于“智能合约平台”而言,最关键的特性就是允许用户自定义状态机,同时保证这些自定义状态机可以自动地、正确地、不可篡改地执行,且执行过程应该被进行“技术公证”,以此来保证合约参与方之间“无需信任”。
很明显,“技术公证”的问题已经被比特币解决了:我们只需要把所有自定义状态机的状态数据保存到区块链上,通过工作量证明算法达成共识,实际上就实现了对自定义状态数据的“全网公证”。但是,如何将“自定义状态”保存到区块链上呢?比特币的 UTXO 模型能够做到么?
简单地说,参考业内到目前为止的研究和工程实践成果,基于 UTXO 模型来保存自定义状态数据大概是可行的,但相对而言会非常复杂,或者需要一些特殊的简化或限制。(因为篇幅原因,这里不做详细解释了。)所以以太坊采用了简单直接的“账户模型(account model)”,这与我们已经非常熟悉的大部分中心化软件系统中所使用的数据模型非常相似。
在以太坊的账户模型中,用“地址(address)”来作为账户的全局唯一标识,将自定义状态数据与地址进行绑定来实现快速查找、增删和修改;这种思路就像我们在传统的业务系统中所使用的账户数据模型那样,通过唯一的用户账户标识来关联所有业务数据。这可以说是一种为了实现通用应用平台而进行的工程上的设计妥协和技术简化。
在账户模型中,每个账户都有一个表示其可用“余额(balance)”的字段,来记录其以太币(以太坊协议中的代币)数量。而在比特币系统中,一个所谓的“账户”是可以持有(使用)多个 UTXO 的,我们在客户端或者钱包中看到的“账户余额”实际上是这个账户持有的(可以使用的)所有 UTXO 的比特币数量之和。比特币中的交易是对可用 UTXO 的合并和拆分,而不是像账户模型这样直接对余额数值进行增减。
以太坊中的“账户”,逻辑上分为两种:EOA(External Owned Account,即由外部用户基于椭圆曲线数字签名的私钥控制的账户)和 Contract Account(即合约账户)。不过从基础数据上看,这两种账户是一样的,它们的区别仅在于账户是否与合约代码相关联:如果一个账户没有关联代码,它就是 EOA;否则它就是合约账户。
应该注意到,对于自定义状态机来讲,需要持久化保存的应该是自定义状态数据和用来修改状态数据的可执行代码,这其实就是所谓的“合约状态”和“合约代码”。对于智能合约平台来讲,必须能够持久化地保存这两种数据。基于账户模型,以太坊使用了一个全局的数据结构来保存所有的合约状态和合约代码。因为合约是可以由用户自定义的,所以保存这些数据的数据结构必须是可以进行高效的动态查找、增删和修改的,并且是可以进行简单验证的。以太坊中就使用了一种叫做 Merkle Patricia Tree(简称 MPT)的数据结构来实现这一点。
MPT 与比特币系统使用的 Merkle Tree 类似,也是一种“哈希树”,所以它们具有相同的特性:任意叶节点数据的变动都会导致根节点哈希的变动,所以根节点哈希可以用来标识树中所有数据的某个特定的版本。基于这个原理,就可以在点对点网络的参与者之间通过由各个客户端分别维护的一个全局 MPT 的根节点哈希来验证所有自定义状态数据、账户余额和代码数据的版本一致性。这个全局的 MPT 也就是以太坊的“状态树(state tree)”,它相当于比特币系统中所有可用 UTXO 的全集。
以太坊中用来保存所有自定义状态数据的全局 MPT 被称为“存储(storage)”,它是独立于“状态树”之外的另一个全局 MPT,但因为其根节点哈希是作为账户数据的一个字段来保存的,所以它实际上也是“状态树”的一部分。
以太坊中的每个合约账户都可以在“存储”中使用若干“存储单元(storage slot)”:每个合约的代码中可以自定义若干状态数据,每个状态数据对应一个或多个“存储单元”;这些存储单元都可以通过简单的寻址算法进行存取操作。这就是以太坊的持久化数据存储方案。
以太坊中的“状态转换”
基于“账户模型”和“存储”,以太坊中的状态转换自然也就有了不同的形式。下图是以太坊中的状态转换示意:
图中演示了以太坊中的一个由交易引发的状态转换:在初始状态 State 中,14c5f8ba、bb75a980、892bf92f 和 4096ad65 代表了 4 个账户地址,其中 14c5f8ba 和 4096ad65 是 EOA,bb75a980 和 892bf92f 则为合约账户(因为有关联代码)。然后图中所示的交易,其逻辑意义也很简单,即从 14c5f8ba 向 bb75a980 发送 10 以太币,并附加了一些额外的数据 Data,这个 Data 数据的意思是将目标合约的第 2 个“存储单元”的数值设定为“CHARLIE”。这个交易的执行会产生一个新的状态 State',其中 14c5f8ba 地址的余额减少了 10 以太币,bb75a980 地址的余额增加了 10 以太币,并且 bb75a980 地址的第 2 个“存储单元”的数值变为了“CHARLIE”。
很明显,因为基础数据模型的改动,以太坊中的状态转换与比特币中的状态转换几乎完全不同。与之相应地,构造交易、验证数据等等的逻辑自然也会完全不同。当然,这里只是一个示意介绍,实际上在以太坊的交易中附加的 data 是一种“可执行代码”,是可以由用户自定义的,用户可以用这些具体的代码来执行任意的计算任务;这种方式与在比特币交易的输入和输出中附加脚本代码是类似的。
以太坊中的“可执行代码”
在以太坊协议中,我们可以在交易的 data 字段中附加一种特殊的“可执行代码”来完成任意的计算任务,这种“可执行代码”是在一个虚拟的执行环境中运行的。与比特币脚本系统的执行环境类似,以太坊的这种虚拟执行环境也是基于“栈(stack)”的;但以太坊的虚拟执行环境还提供了可以供应用程序在运行时使用的临时存储空间“内存(memory)”和刚刚介绍过的永久存储机制“存储(storage)”。这就使以太坊的虚拟执行环境有了更强的计算能力,更像是一种“虚拟的计算机”,这个虚拟的执行环境就是大名鼎鼎的“以太坊虚拟机(Ethereum Virtual Machine,简称 EVM)”,EVM 也是以太坊被称为“世界计算机(World Computer)”的由来,它也是以太坊与比特币相比在技术层面的最大创新。
以太坊协议中的这种可以在 EVM 中运行的“可执行代码”就是所谓的“EVM 字节码”。EVM 字节码是以太坊计算能力的根本,这是一种“图灵完备(Turing-complete)”的操作码系统。以太坊的高级开发语言 Solidity 就可以编译为 EVM 字节码,然后在 EVM 中运行;这种方式与其他编译型的高级语言是类似的,就像 Java 语言和 Java 虚拟机字节码的关系那样。
EVM 字节码的图灵完备性,也是其与比特币的脚本系统相比最大的区别。图灵完备性标志着 EVM 字节码可以用来完成任意的计算任务,同时因为 EVM 中还提供了临时存储机制和永久存储机制,这才使以太坊成为了真正的可以完成“通用目的(general propose)”计算任务的“智能合约平台”。
小结
至此,我想我可以再回答一下“以太坊是什么”这个问题了:以太坊其实就是“区块链”+“以太坊虚拟机”。 就本文的目的而言,你记住这个答案就够了。
当然,以太坊并不像本文中说的这么简单,还有许许多多的技术细节等待着你去学习理解,文中所提及的术语大多也可以进行更多的技术解读。但就像我在引言中说的那样,本文的目的仅仅是去尝试解释一些最基本、最重要的概念,帮助你进入区块链和智能合约的世界。希望你读完本文之后能有些许收获。我也要感谢能耐着性子看到这里的朋友!
彩蛋:
本文中的两个插图都是从以太坊白皮书中抄来的,细心的你可能会发现:以太坊的状态转换示意图中交易数据的 From 地址写错了!是的,你没看错,就是写错了。如果你发现了这个错误,我必须恭喜你,你是个细心人,大概是做技术的人才。而没有发现的朋友可能需要检讨一下了……。
当然,也请不要多想,我引用这个错误的图片,仅仅是因为我不会 PS,然后还懒,所以这个锅只能 V 神背了……。