第7章 智能合约

在第1章中,我们首先通过一个例子将读者引入区块链的世界,之后详尽地介绍了区块链的背景、基础知识以及构架,并深层次地分析了区块链背后的技术以及所遇到的问题。在这一章中,我们将介绍在未来区块链技术发展中最重要的应用场景,即智能合约的实现。

在第2章中,我们曾经介绍过一个重要的区块链应用平台——以太坊。众所周知,在以太坊平台上,最重要的应用就是设计部署智能合约。那什么是智能合约?智能合约能做什么?如何在以太坊上部署智能合约呢?在这一章中,我们将结合具体的案例逐一解答这些问题。

7.1 智能合约简介

7.1.1 什么是智能合约

虽然在法律范畴上来说,智能合约是否是一个真正意义上的合约还有待研究确认,但在计算机科学领域,智能合约是指一种计 算机协议,这类协议一旦制定和部署就能实现自我执行(self-executing)和自我验证(self-verifying),而且不再需要人为的干 预。从技术角度来说,智能合约可以被看作一种计算机程序,这种程序可以自主地执行全部或部分和合约相关的操作,并产生相应的可以被验证的证据,来说明执行 合约操作的有效性。在部署智能合约之前,与合约相关的所有条款的逻辑流程就已经被制定好了。智能合约通常具有一个用户接口(interface),以供用 户与已制定的合约进行交互,这些交互行为都严格遵守此前制定的逻辑。得益于密码学技术,这些交互行为能够被严格地验证,以确保合约能够按照此前制定的规则 顺利执行,从而防止出现违约行为。

举个例子来说,对银行账户的管理就可以看成一组智能合约的应用。在传统方式中,对账户内存款的操作需要中心化的银行进 行授权,离开银行的监管,用户就连最简单的存取款都无法进行。智能合约能够完全代替中心化的银行职能,所有账户操作都可以预先通过严密的逻辑运算制定好, 在操作执行时,并不需要银行的参与,只要正确地调用合约即可。再比如说,用户的信息登记系统完全可以由智能合约实现,从而完全抛开需要人为维护的中心化数 据管理方式,用户可以通过预先定义好的合约实现信息登记、修改、注销等功能。此外,通过设计更复杂的合约,智能合约几乎可以应用于任何需要记录信息状态的 场合,例如各种信息记录系统以及金融衍生服务。但这要求合约设计者能够深入了解流程的各个细节,并进行合理设计,因为通常来说,智能合约一旦部署成功,就 不会再受到人为的干预,从而无法随时修正合约设计中出现的漏洞。

7.1.2 智能合约的历史

在20世纪七八十年代,随着计算机的发明,对计算机的理论研究达到了一个高潮。研究人员致力于让计算机帮助人类从事更 多的工作,从而解放人类的生产劳动。正是在此时,人们提出了让计算机代替人类进行商业市场管理的想法。与此同时,公钥密码学得到革命性的发展,但使计算机 完全代替人类进行商业管理的技术并未成熟。

直到20世纪90年代,从事数字合约和数字货币研究的计算机科学家尼克萨博(Nick Szabo)第一次提出了“智能合约”这一说法,其致力于将已有的合约法律法规以及相关的商业实践转移到互联网上来,使得陌生人通过互联网就可以实现以前 只能在线下进行的商业活动,并实现真正的完全的电子商务。1994年,尼克萨博对智能合约做出以下描述[1]

“智能合约是一个由计算机处理的、可执行合约条款的交易协议。其总体目标是能够满足普通的合约条件,例如支付、抵押、 保密甚至强制执行,并最小化恶意或意外事件发生的可能性,以及最小化对信任中介的需求。智能合约所要达到的相关经济目标包括降低合约欺诈所造成的损失,降 低仲裁和强制执行所产生的成本以及其他交易成本等。”

尼克萨博以及其他研究者希望借助密码学协议以及其他数字化安全机制,实现逻辑清楚、检验容易、责任明确和追责简单的合 约,这将极大地改进传统的合约制定和履行方式,并降低相关的成本,将所有的合约条款以及操作置于计算机协议的掌控之下。但那时,很多技术还不成熟,并无法 完全实现研究者的想法,这一局面在比特币的出现之后得到很大的改观。借由比特币背后的区块链技术,智能合约得以飞速发展,有许多研究机构已将区块链上的智 能合约作为未来互联网合约的重要研究方向,很多智能合约项目已经初步得以实现,并吸引大量的资金投入其中。

[1] Tapscott,Don;Tapscott,Alex(May 2016).The Blockchain Revolution:How the Technology Behind Bitcoin is Changing Money,Business,and the World.pp.72,83,101,127.

7.1.3 智能合约的优点和面临的风险

现今,虽然智能合约还未被广泛应用和实践,但其优点已得到研究人员和业内人士的广泛认可。总体来说,智能合约具有以下优点:

1)高效的实时更新: 由于智能合约的执行不需要人为的第三方权威或中心化代理服务的参与,其能够在任何时候响应用户的请求,大大提升了交易进行的效率。用户不需要等待银行开门就可以办理相关的业务,只要通过网络一切都可以方便快捷地解决。

2)准确执行: 智能合约的所有条款和执行过程是提前制定好的,并在计算机的绝对控制下进行。因此所有执行的结果都是准确无误的,不会出现不可预料的结果。这也是传统合约制定和执行过程中所期望的。现今,智能合约的准确执行得益于密码学的发展和区块链技术的发明。

3)较低的人为干预风险: 在智能合约部署之后,合约的所有内容都将无法修改,合约中的任何一方都不能干预合约的执行,也就是说任何合约人都不能为了自己的利益恶意毁约,即使发生毁约事件,事件的责任人也会受到相应的处罚,这种处罚也是在合约制定之初就已经决定好的,在合约生效之后无法更改。

4)去中心化权威: 一般来说,智能合约不需要中心化的权威来仲裁合约是否按规定执行,合约的监督和仲裁都由计算机来完成。在区块链上的智能合约更具有这一特性,在一个区块链 网络中一般不存在一个绝对的权威来监督合约的执行,而是由该网络中绝大部分的用户来判断合约是否按规定执行,这种大多数人监督的方式是由PoW或PoS技 术来实现的。如果将这种情况搬到现实世界中,或许现在的所有法官都要失业了,而与此同时我们每个人都是法官,都参与监督和仲裁。

5)较低的运行成本: 正因为智能合约具有去人为干预的特点,其能够大大减少合约履行、裁决和强制执行所产生的人力成本,但要求合约制定人能够将合约的各个细节在合约建立之初就 确定下来。这可能会使在传统行业(如银行)工作的部分员工面临失业,但从长远来说会促进行业的转型,向更新更好的领域发展。

虽然智能合约具有许多显而易见的优点,但对智能合约的深入研究才刚刚开始,其广泛应用还面临着潜在的甚至是毁灭性的各类风险。其中一个已知的风险恰恰是来自于智能合约的去人为干预的特性。

2016年4月,史上最大的一个众筹项目The DAO正式上线。经过一个多月的众筹,总共募集到超过价值1.5亿美元的以太币用于建立该项目。从这令人震惊的数字上可以看出区块链技术以及之后的智能合 约广泛应用的前景是多么让人充满信心。但就在短短一个多月之后,The DAO所在的平台以太坊的创始人之一Vitalik Buterin在其Slock.it社区里面发表声明,表示The DAO存在巨大的漏洞,在其上的大量的以太币已经被“偷”,未来或许还会有大量的以太币被偷,而The DAO的设计执行者对此攻击却无能为力。这一攻击的出现,恰恰是因为The DAO的智能合约在设计之初就存在漏洞,由于基于区块的智能合约的去人为干预特性,这一漏洞无法被线上修复,只能眼睁睁地看着黑客把更多的以太币从项目中 偷走。虽然在后续的对策研究中,以太坊的设计者们想出了让以太坊分叉的解决办法来挽回损失(从根本上将丢失以太币的交易作废),但很多分叉的反对者认为, 人为分叉完全背离了去中心化思想,并会大大降低以太坊在人们心目中的信用。由于分歧的存在,人们发起了投票,以决定是否分叉。无论最终是否分叉,都将会对 The DAO以及未来的智能合约发展产生深远的影响,迫使合约的设计者将工作重点放到讨论合约的安全性上来。此外,由于智能合约具有自我验证的特性,其上的数据 隐私保护也面临着巨大的风险。

The DAO攻击事件的发生恰恰是由于其公认的优点,这很值得业内人士反思,技术的应用要有坚实的理论基础做支撑,那么完全去中心化的智能合约是否已经成熟以及 面临攻击该如何应对都将成为未来主要探讨的课题。但不管怎样,业内人员普遍认为,区块链技术和智能合约都将成为未来互联网发展的重要方向,现在面临的挫折 是新技术成熟的必然过程。

我们在第9章会详细介绍The DAO事件的来龙去脉。

7.2 以太坊智能合约详解

这一节我们将结合最前沿的智能合约平台——以太坊,进一步介绍其上的智能合约。阅读这一节的读者需要对区块链技术和智能合约有一定的了解,如需补充基础知识,请阅读之前的几章。

7.2.1 以太坊上的账户

账户是以太坊的核心操作对象,但和比特币以及传统的区块链不同,在以太坊上,账户被分为两类:一类叫作外部所有账户(Externally Owned Accounts,EOA),另一类叫作合约账户。

其中外部所有账户可被简单称为“账户”,这是由于外部所有账户与一般的区块链电子货币的账户(例如,比特币账户)类 似,都是人为创建的、能够存取货币、由公钥加密系统加密和分享的账户。不同的是,在以太坊上,外部所有账户有能力创建合约账户,并部署智能合约。总之,在 以太坊内部,外部所有账户和合约账户都被统称为状态对象(state objects),这些对象都具有自己的状态,其中外部所有账户的状态体现在其包含多少货币余额,而合约账户既含有货币余额状态还有合约存储状态。这些对 象的状态随着每个区块的产生而发生变化(也可能不变),因此,简而言之,以太坊的基础就是通过区块链技术记录这些状态的变化,通过工作量证明,这些状态变 化被大多数用户所认可,从而达成共识。

1.钥匙文件

每一个账户都通过一对私钥和公钥来确定。每个账户都拥有一个地址,这个地址来自于该账户的公钥的最后20个字节。每一 账户的地址和私钥都被编码成一个JSON格式的“钥匙文件”(keyfile)。因此,以太坊用户不能通过文本编辑器直接看到自己的私钥。存储在本地的以 太坊账户私钥总是处于加密状态,而加密所使用的密钥就是在账户创建时用户所输入的密码。这一机制是为了保护账户的安全。以下就是一个钥匙文件所存储的数 据。


 

{
   "address": "24265935827a9332a97cd0db938f2e0e0855853c",
   "Crypto": {
       "cipher": "aes-128-ctr",
       "ciphertext": "eb59eebe4b944627939db96db8e0d7125d85495965a41e29a0c
           102465a0898e5",
       "cipherparams": {
           "iv": "20391682b7c25dcf29e1b8c48312e4cc"
       },
       "kdf": "scrypt",
       "kdfparams": {
           "dklen": 32,
           "n": 262144,
           "p": 1,
           "r": 8,
           "salt": "3932589c8ce28c0d1c15e5e888c45a73145e6d141f729ee5e7196
               8086636e335"
       },
       "mac": "08a95f73166a70ef5b1846196d6af170781721a4972440ce41d05ba3f44fcc5b"
   },
   "id": "76aa7f19-cd6a-4974-a44c-3c7a699d8edf",
   "version": 3
}


可以看到,钥匙文件中只存储了以太坊账户私钥的密文(在ciphertext字段),只有知道用户自己设定的密码才能 得到以太坊账户的真正私钥,该私钥用于此账户所有交易的签名。由于以太坊采用区块链这种去中心化技术,因此一旦钥匙文件丢失就意味着账户再也找不回来了。 所以用户要确保已备份好自己的钥匙文件,并确保自己的密码不会外泄。

2.创建账户

在第2章已经介绍过了以太坊的多种客户端。到目前为止,最普遍使用的是基于Go和C++程序设计语言的客户端(即go-ethereum和cpp-ethereum),因此在这一章我们主要采用go-ethereum[1] 来演示如何操作以太坊账户。

通过go-ethereum创建账户十分容易。以Linux操作系统为例,创建一个账户需要以下几个步骤:

1)安装好以太坊和go-ethereum,在客户端执行以下命令。


 

sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum


2)执行geth account new命令创建账户,并设置密码。


 

geth account new
Your new account is locked with a password. Please give a password. Do not
   forget this password.
Passphrase:
Repeat passphrase:
Address: {850fa7796f372a5f6a7b59976ac5cca6e6565cbb}


设置好密码之后,客户端会显示所创建的新账户的地址,供以后使用。

3)之后执行geth命令,同步已有的所有区块。


 

geth


当区块链全部同步好之后,就可以进行挖矿并部署智能合约了。

除了以上命令行的方式之外,以太坊的开发者还开发了拥有图形界面的以太坊钱包(Ethereum Wallet),以方便用户更容易地管理账户和部署智能合约。用以太坊钱包创建账户十分简单,首先从以太坊在github上的官方网页(https://github.com/ethereum/mist/releases )上下载相对应的操作系统的钱包程序压缩包,并解压程序,运行钱包。第一次运行钱包时会出现如图7-1所示界面。

image.png

图7-1 以太坊钱包初始界面

此时,用户可以选择同步以太坊的主链还是其公共的测试链(testnet)。在测试链上,用户不需要长时间地挖矿就可以很快地获得大量测试用的以太币,并运行测试自己的智能合约。而在主链上,用户则需要花费大量计算资源挖矿才能获得标准的以太币。

在选择好同步的网络之后,用户可以选择是否同步完区块链后再进入主界面。其主界面如图7-2所示。

此时,用户可以通过界面上的提示,一步一步创建自己的账户,单击ADD ACCOUNT按钮。账户的创建和区块链的同步无关,因此用户可以一边同步,一边创建账户。

3.账户的备份

备份以太坊的账户十分容易,只需要找到相应的以太坊目录即可。根据不同的操作系统,以太坊的文件存储的目录如下。

·Windows:C:\Users\username\%appdata%\Roaming\Ethereum

·Linux:~/.ethereum

·Mac:~/Library/Ethereum

image.png

图7-2 以太坊钱包主界面

以太坊文件的目录结构如下:

image.png

图7-3 以太坊目录结构

其中,chaindata文件夹存储着以太坊主链的所有区块,keystore文件夹存储着用户的账户数据,testnet文件夹内有一套完整的用于以太坊测试链的文件,其中也含有chaindata和keystore文件夹,存储着测试链上的所有区块和用户的账户信息。

spacer.gif 注意:一定要备份好keystore文件夹内的文件,以免丢失,造成财产损失。

[1] http://www.ethdocs.org/en/latest/.

7.2.2 以太币和Gas

1.以太币

与所有基于区块链技术的去中心化系统一样,以太坊也有一套激励机制,以鼓励矿工花费计算资源进行挖矿,从而维持以太坊 的运行,这一机制就是以太币(Ether)。以太坊上所有的账户管理操作和智能合约的部署都需要支付以太币才能正常运行,因此每个以太坊用户都需要获得并 花费以太币,这促使矿工努力挖矿。

以太坊的最小货币单位是1wei,其和以太币的兑换率为:1Ether=1018 wei。每当一个区块被矿工挖出,挖出这一区块的矿工就将获得一定数量的奖励。这一奖励由两部分组成。

·静态奖励:该矿工可获得5个以太币作为奖励。

·动态奖励:挖出的区块中所有交易的费用归该矿工所有;如果该区块中包括叔区块,那么矿工还可从每个叔区块中获得额外的1/32以太币作为奖励,但每个区块中最多只能包含2个叔区块。

这里,叔区块是指那些没有在最长的那条链上,而是在分叉链上所挖出的有效区块。挖掘这些区块的矿工可能是由于网路延迟 的原因而没有同步到最新的区块。以太坊采用这种机制来分散中心挖矿现象(即大矿池垄断生产区块,导致单个的矿工总是落后于大矿池获得区块信息,因此即使单 个矿工找到正确的区块,也无法获得任何收益)。一个叔区块一旦被包含在有效的区块链中,挖到的矿工可获得4.375以太币作为奖励。这也保证了以太坊能够 以很短的时间产生区块(平均15秒),而不会因为网络同步的延迟而产生多个分叉。

2.Gas

智能合约一旦部署在以太坊上就无法再被修改。为了防止恶意用户部署无限循环运行的合约,以太坊要求用户要为所部属合约 的每一步支付费用,而这些费用的基础单位就是Gas。例如,部署智能合约,每一步需要支付1Gas,停止合约不需要支付任何Gas,创建合约需要支付 100Gas,而每次合约交易需要支付500Gas[1] 。因此,Gas就相当于部署和执行智能合约所需要的燃料,没有燃料,就无法使用智能合约。这种燃料机制维持着以太坊的经济体系的运行,用户只能通过挖矿或从矿工那里购买以太币来补充燃料。

spacer.gif 注意:Gas并不是以太币的单位,而是部署运行智能合约所需的相对花费,可通过燃料价格转化成以太币。

在以太坊上面,一个智能合约所需的Gas是固定的,这是因为,智能合约程序的每一步都可以分解成特定的操作组合,每种 操作所需的Gas由以太坊的设计者们来决定,以确保以太坊正常运行。但将每个智能合约的所需的费用完全固定是不明智的,这是因为,不同的用户有不同的需 求,有些用户希望自己的交易能得到快速的确认,有些则希望用较少的以太币来执行合约。因此,以太坊还引入了“Gas价格”(Gas Price)这一概念,即消耗每个Gas需要多少以太币。Gas价格可由用户在一定范围内自行定义,价格定得越高,交易被确认得就越快,反之则越慢。同 时,为了防止部署执行合约的真实花费随着以太币的市值发生大幅度波动,Gas价格还会随着以太币的市值波动。如果以太币升值,那么Gas价格将会适当降 低,反之相反。

因此,与Gas相关的概念总结如下。

·Gas花销(Gascost):Gas花销是静态的,其在针对某一种操作时是不变的。其目的是保证每种操作所需的计算资源保持不变。

·Gas价格(Gasprice):花费每个Gas所需的以太币的数量。Gas价格可由用户自行调整,其基准价格随以太币的市值波动,以保证智能合约所需的真实花费不会出现大幅度变化。

·Gas费用(Gasfee):Gas价格乘以Gas花销,即合约所需的真实费用,其单位是以太币。

[1] Gavin Wood. Ethereum Yellow Paper: http://gavwood.com/paper.pdf.

7.2.3 合约和交易

1.合约账户

我们在7.2.1节介绍了以太坊的账户类型,了解到现阶段以太坊的账户分为两类:外部所有账户和合约账户,并介绍了如 何创建外部所有账户。这一节,我们重点介绍如何创建合约账户,换句话说就是如何在以太坊上部署和运行智能合约。以太坊的设计者们计划在以太坊发展的下一阶 段取消这两类的账户的区别,将它们合并成一类账户 。

以太坊的外部所有账户的主要具有以下几个特点:

·可以存储以太币;

·可以发起交易,其中包括交易以太币和部署运行智能合约;

·用户创建账户密钥,并管理账户;

·不支持智能合约代码。

与外部所有账户相比较,合约账户具有以下特点:

·可以存储以太币;

·可支持智能合约代码;

·可响应别的用户或合约执行此智能合约的请求,并返回结果;

·可调用别的智能合约。

在以太坊上,所有被记录在区块链内的活动都是由外部所有账户发起的。每当一个合约账户收到一个交易申请,其接收传递而 来的参数,并通过运行在每个节点上的以太坊虚拟机(Ethereum Virtual Machine,EVM)执行自身的代码。每一笔有效的交易都将被记录在区块链上,通过所在区块在整个链的位置记录该交易的时间,整个区块链则反映了所有 交易的执行顺序。

从形式上看,以太坊上的智能合约并不像传统合约那样需要得到合约方的履行,而看上去更像一种存在于以太坊网络中的“自 治代理程序”(autonomous agents)。当这些程序接到申请,则总会按照已制定的程序代码来执行,并将其自身的状态变化永久地存储在区块链中。

在使用以太坊时,有两个概念需要区分。

·交易(Transaction):交易是指一个外部所有账户将一个经过签名的数据包发送到另一个账户的过程,这个过程中产生的账户状态变化将被存储到区块链上。

·消息(message):以太坊上的合约账户有能力向其他合约账户发送“消息”。这里的消息是一个虚拟的对象,并不会具体地存在以太坊的区块链内,可以将其想象成一个函数调用的过程。

本质上来说,交易和消息是两个非常相似的概念。区别在于,消息是由合约账户产生的,而交易是由外部所有账户产生的。因此,合约账户和外部所有账户一样,可以同其他合约账户产生联系。

2.智能合约的编写

以太坊上的一个智能合约就是一段可被以太坊虚拟机执行的代码,这些代码以以太坊特有的二进制形式存储在区块链上,并由以太坊虚拟机解释,因此被称为以太坊虚拟机位码(bytecode)。

相较于其他可部署智能合约的区块链系统,以太坊的最大特色就是,以太坊虚拟机的建立使得智能合约的编写变得非常容易。 这些智能合约通常可由高级语言编写,并通过虚拟机转化成位码存储在区块链上。目前来说,用于以太坊智能合约开发的语言主要有3种。Solidity、 Serpent、LLL。

作为最流行的智能合约语言,Solidity以其简单易用和高可读性受到以太坊设计者们的推荐。目前,编译Solidity代码最简单的方式是使用在线的编译器(https://ethereum.github.io/browser-solidity/ ),也可以在命令行下使用solc编译器对代码进行编译。目前,很多编辑器和集成开发环境IDE(如Visual Studio)已开始支持Solidity代码的编写。此外,专门为以太坊设计的IDE也在不断开发完善中,例如,Ethereum Studio[1] 和Mix IDE[2]

3.智能合约的部署流程

在部署合约时,以太坊虚拟机负责将用户编写的智能合约代码编译成位码。这些位码被存在区块链上,在需要时通过 web3.js Javascript API调用,并可用来构建与之交互的Web应用。这些API由web3.js库提供,是和以太坊节点建立联系的媒介,其本质是通过JSON-RPC协议与 本地的以太坊节点进行通信。这里,JSON是一个轻量级的、以文字为基础、易于阅读的数据存储和交换语言,其本质是JavaScript的一个子集,常作 为Web应用的数据存储格式。JSON-RPC则是一个由JSON格式编码的、轻量级的远程过程调用协议(remote procedure call protocol),其定义了一些数据结构、规则、过程和接口,可用于网络上绝大多数的数据通信协议(如HTTP)。

总的来说,在以太坊上部署和运行智能合约需要以下几个步骤:

1)启动一个以太坊节点(如geth)。

2)使用智能合约语言编写智能合约(如Solidity)。

3)使用solc编译器将编写好的合约代码转换成以太坊虚拟机位码(如Browser-Based Compiler)。

4)将编译好的合约代码部署到网上需要消耗用以太币购买的GAS,并且需要合约发起用户使用自己的外部所有账户对将要 部署的合约进行签名,通过矿工的确认后,将合约代码存于以太坊的区块链上。在这一步中,用户可获得合约的地址,以及调用合约所需的接口 (interface),以便之后使用。

5)使用web3.js库所提供的JavaScript API接口来调用合约。这一步也会消耗以太币,具体消耗值取决于所调用的合约功能。

以太坊上的智能合约部署和调用的过程如图7-4所示。在7.4节中,我们将通过几个简单的智能合约的实例,具体展示如何在以太坊上部署和运行智能合约。

image.png

图7-4 以太坊合约的部署和调用

[1] https://live.ether.camp.

[2] https://github.com/ethereum/mix.

7.3 以太坊虚拟机

以太坊并不是唯一一个可以在区块链上部署智能合约的平台(例如,很多智能合约都可以部署在比特币的区块链上),但使得 以太坊与众不同的重要一点就是建立在区块链上的以太坊虚拟机。虚拟机的引入使得编写智能合约变得异常容易,高度脚本化的程序设计语言(如 Solidity)使得普通用户也能轻松地开发自己的智能合约,而不需要太多的专业学习。在未来,以太坊的设计者们还有更大的野心,他们试图建立一个类似 于苹果电脑公司App商店的中心化App(DApp)商店,这将极大地扩展以太坊的应用范围。

简单来说,以太坊虚拟机是建立在以太坊区块链上的一个代码运行环境,但虚拟机本身并没有存储在区块链内,而是和区块链 一样同时存储于各个节点计算机上。每个参与以太坊网络中的校验节点都会运行虚拟机,并将其作为区块有效性校验协议的一部分。每个节点都会对合约的部署和调 用进行相同的计算,并存储相同的数据,以确保将最权威(最真实)的结果记录在区块链内。

以太坊虚拟机是一个图灵完备的256位虚拟机,这说明以太坊虚拟机可以进行任何种类的计算。但为了防止恶意用户设计无 限循环代码使虚拟机的运行瘫痪,以太坊虚拟机中执行的代码严格受到一个参数的制约,这个参数就是Gas。这规定了可运行的计算指令的数量上限,从而不会产 生无限循环(无限循环最终会因耗尽Gas而中止)。

以太坊虚拟机的构架实际上是一个简单的堆栈式结构,每个堆栈项目为256位,即虚拟机的位宽为256位,其目的是使之 能够方便地应用于256位的Keccak散列算法和椭圆曲线计算。堆栈的存储(storage)是一个基于字段地址的数组,其最大包含1024个元素。此 外,虚拟机还包含一个独立的基于字段地址的内存,但不同于普通的内存模型,这个独立的内存是一个非易失性内存(non-volatile memory),即当虚拟机不运行时,其所存储的数据不会丢失。该内存中的记录作为整个以太坊系统状态记录的一部分。虚拟机的存储和内存在初始时都被设置 为0。

以太坊虚拟机还可以处理异常执行,其中包括堆栈溢出和无效指令等。同时,针对GAS不足的异常,虚拟机会立即停止工作,并将问题报告给交易处理器或运行环境的代理程序,由它们单独处理。

1.GAS的消耗

在以太坊虚拟机内部,GAS的消耗会出现在下列3种情况中(其中第一种情况最常见)。

1)当需要执行特定的内部抽象操作时,例如,运行SHA3散列运算时。

2)当进行一个从属的消息调用或合约创建时,例如,执行CREATE、CALL或者CALLCODE操作时。

3)当需要增加账户内存使用量时。

在账户进行操作时,需要支付费用的账户内存使用量应该是32个字节的整数倍,以保证使用的所有内存都能包括在计费范围 内。例如,如果使用了33个字节的内存,那么账户需要支付两个32字节的费用。此外,内存使用计费机制还有助于激励用户使用较少的内存。当执行账户内存清 理操作时,该操作不仅不会消耗任何GAS,还会得到一定数量的内存使用费用的折扣,以鼓励用户尽量释放不用的内存。在实际操作中,这种折扣在账户执行之前 就已经被支付给用户,这是由于内存初始化使用所产生的费用要高于一般的内存使用。

2.虚拟机运行环境

假设整个以太坊网络的状态为σ,合约运算剩余的GAS为g,那么在整个运行环境中还有许多重要的信息。

·Ia:当前代码的合约地址;

·Io:发起这次合约交易的发起者地址;

·Ip:用户为这次交易设置的Gas价格;

·Id:这次交易的输入数据,该输入的数据结构是一个数组;

·Is:执行这次合约交易的账户地址;

·Iv:合约账户的余额;

·Ib:用于执行虚拟机代码所需的数组;

·IH:目前区块的数据头;

·Iε:目前执行的CALL操作和CREATE操作的数量。

假设以上信息都包含在一个元组I内,系统状态变化的函数是Ξ,σ′为系统运行后的状态,g′为运行后剩余的Gas,s 为执行终止(suicide)操作的合约列表,l为记录序列,r为运行后所返还的Gas,o为合约运行后所产生的输出,那么整个以太坊的状态转换可定义为 以下公式:

(σ′,g′,s,l,r,o)=Ξ(σ′,g,I)

3.状态转换函数(三)

为了完成整个以太坊系统的状态转化,需要定义状态的转换函数Ξ。在大多数实际情况下,整个系统的状态转换是一个不断地迭代系统临时状态和虚拟机临时状态的过程。迭代的过程需要调用异常检查函数和指令输出函数。迭代的终止由以下两个条件决定:

·系统状态是否出现异常而使虚拟机停止工作,其中包括Gas不足、指令无效、虚拟机堆栈容量不足等情况,任何正常的系统指令都不会造成异常状态的出现。

·虚拟机在正常状态下停止工作,例如,所有指令执行完毕返回结果。

在每一次迭代过程中,智能合约的指令被压入堆栈,虚拟机按堆栈的索引执行指令。每执行一条指令,将支付相应的Gas,直到所有指令执行完毕,堆栈被清空。其中如果遇到异常,虚拟机则停止工作逐层向上返回异常。

4.区块链系统状态的验证

在以太坊虚拟机正确执行所有指令之后,系统的状态得以转换。为了保证这种转换权威而有效,每个以太坊节点都可能会对系 统的状态进行验证,并达成共识,确认交易的有效性。在以太坊上,对于交易记录的信任建立在对最权威区块链的信任的基础之上。以太坊上最权威的区块链是在一 个树结构中从根节点(root)到叶子节点(leaf)的路径。为了确认哪条路径是权威区块链,理论上来说,是找到哪条路径通过工作量证明花费的计算量最 大,即最“重”的那条路径。实际情况当中,最权威的区块链是由从根节点(即起源区块)到某个叶子节点(即新生区块)间最长路径来决定的。这条路径越长,就 意味着在这条路径上所消耗的计算资源越多,因而由于工作量证明,说明这条区块链是最权威的,能够得到所有用户的认可。

每产生一个新的有效区块,以太坊系统需要以下几个步骤才能将该区块加入权威区块链上。

1)验证该新区块的ommer区块的有效性。这里ommer区块是指该新区块的“祖父”区块除当前新区块所在链的其他后继区块,即叔区块。每个区块中最多可包含两个ommer区块。

2)验证该新区块中所包含的交易的有效性,即所有交易所花费的GAS是否与该区块链中所标记的GAS花费量一致,并与每笔交易一一对应。

3)对相应的由于新有效区块的产生而能得到以太币奖励的账户发放奖励,其中包括挖到该区块的矿工账户和包含在该区块内的ommer区块所属的矿工账户。

4)验证该新区块链的工作量证明,并确认将新区块连接在权威区块链上,并将整个系统更新到最新状态。

7.4 实例:在以太坊上开发实施智能合约

前几节介绍了智能合约和在以太坊上部署运行智能合约的相关基础知识,这一节将通过实例展示如何在以太坊上部署运行一个 真正的智能合约。通常来说,在以太坊上可以通过两种常用的方式部署运行智能合约:一种方式是使用图形界面的以太坊钱包,另一种方式是使用go- ethereum通过交互命令部署智能合约。下面将分别介绍这两种方法。

7.4.1 通过以太坊钱包部署智能合约

使用以太坊钱包部署智能合约并不需要太多的操作程序,除了需要使用合约语言编写智能合约之外,不需要编辑任何其他代码。其所有的合约部署和调用操作都可以在图形界面下完成,十分方便快捷。

1.部署智能合约

以一个公司分配股份给权益人的智能合约[1] 为例。通过以太坊钱包部署智能合约包括以下几个步骤:

1)下载最新的以太坊钱包或Mist浏览器[2] 。Mist浏览器是未来实现发布浏览调用DApp的工具,现在还在开发之中,目前的版本集成了以太坊钱包,可在Mist浏览器内部使用钱包的所有功能。

2)运行以太坊钱包,按照之前在7.2.1节介绍的方法选择想要同步的区块链。为了方便获得以太币进行测试,本节所有智能合约的相关操作都在测试网络(testnet)下进行。

3)按照7.2.1节介绍的方法创建用户外部所有账户,并等待测试区块链全部同步完成。

4)为了测试的方便,假设钱包内已有两个外部所有账户(即MAIN ACCOUNT和ACCOUNT 1),每个账户中都有一些测试用的以太币。其当前状态如图7-5所示。

image.png

图7-5 以太坊钱包当前状态

5)现在创建股权的合约。单击右上角的CONTRACTS按钮进入合约菜单,选择部署智能合约(Deploy contract),选择发起智能合约的账户(这里由MAIN ACCOUNT发起智能合约),并可在AMOUNT菜单中向将要创建的合约账户发送以太币。这里我们不需要合约账户支付以太币,所以将AMOUNT设置为 0。将用Solidity语言编写好的智能合约代码复制到SOLIDITY CONTRACT SOURCE CODE菜单。其合约代码如下:


 

contract MyToken {
   /*在合约中使用public关键字定义所有能被别的合约访问的变量*/
   string public name;
   string public symbol;
   uint8 public decimals;
   /*建立一个数组存储账户的余额*/
   mapping (address => uint256) public balanceOf;
   /*建立一个公共的事件用于用户通知*/
   event Transfer(address indexed from, address indexed to, uint256 value);
   /*初始化合约,当合约内的函数名和合约名相同时(MyToken),



则该函数是合约的构造函数*/
   function MyToken(uint256 _supply, string _name, string _symbol, uint8
       _decimals) {
       /*默认将股权分为10000份,即股权的最小单位是0.01%*/
       if (_supply == 0) _supply = 1000000;
       /*可自定义股权数量和最小单位*/
       balanceOf[msg.sender] = _supply;
       /*定义股权名称*/
       name = _name;    
       /*设定股权所使用的单位符号,例如%*/
       symbol = _symbol;
       /*设定小数位数*/
       decimals = _decimals;
   }
   /*创建股权转移函数*/
   function transfer(address _to, uint256 _value) {
       /*检验是否有足够的股权*/
       if (balanceOf[msg.sender] < _value) throw;
       if (balanceOf[_to] + _value < balanceOf[_to]) throw;
       /*更新股权转让信息*/
       balanceOf[msg.sender] -= _value;
       balanceOf[_to] += _value;
       /*通知用户股权转让成功*/
       Transfer(msg.sender, _to, _value);
   }
}


6)在SELECT CONTRACT TO DEPLOY菜单中选择要部署的合约(即My Token)。在CONSTRUCTOR PARAMETERS菜单中输入参数,其页面如图7-6所示。

image.png

图7-6 输入参数

7)从下拉菜单中可以看到部署合约的Deploy按钮和希望支付的交易费用,费用越多则该合约确认得越快,反之则越慢。单击Deploy按钮出现如图7-6所示的确认界面。

用户可在此界面中看到可能需要的交易费数量和当前GAS的价格,在Data菜单下可看见合约的以太坊虚拟机位码。之后 单击SEND TRANSACTION部署合约。此时,在WALLETS界面下可看到刚刚发送出的合约正在接受确认。以太坊要求交易必须在12个区块产生之后才能得到最 终确认,但只要有一个区块确认了合约,就可以调用合约的函数了(这不代表交易得到最终确认,只是临时确认)。其交易确认状态可从WALLETS界面下看 到,如图7-8所示。

当12个区块确认完成时,合约才被真正保存到区块链中,即部署到以太坊的网络上,并可被调用。

image.png

图7-7 确认部署合约界面

image.png

图7-8 部署合约交易确认状态

2.调用智能合约

在合约部署完之后,在钱包的CONTRACT菜单下可看到刚刚部署成功的合约(My Shares)。部署合约相当于创建了一个合约账户,因此在合约My Shares内可以看见返回的合约地址和合约接口(interface)。地址和接口是找到合约并调用合约的必要信息。如果用户希望在另外一个以太坊节点 调用刚刚创建的合约,可在Watch Contract界面中输入合约的地址和接口,合约的名字可以任意选取,其状态如图7-9所示。

image.png

图7-9 调用新合约

通过钱包调用合约也十分容易,这是因为以太坊钱包已经将合约接口的所有调用集成在钱包界面内,而不需要用户自己访问合约接口。合约接口实际上是一个JSON文件,定义了参数字段,用来告诉客户端如何和这个合约进行交互。

spacer.gif 注意:请保存好合约的地址和接口,以便在任何一个以太坊节点调用合约。

单击My Shares合约进入合约界面,可看到所有该合约内部的公共参数和合约内定义的函数。这里我们只定义了transfer函数,它用于将股权分配给其他客户,还定义了一个名为balanceOf数组用来查询客户的股权。具体操作如下:

·查询客户的股权余额只需将账户的地址输入Balance Of菜单下,就可看到该账户的股权余额。这里我们输入MAIN ACCOUNT账户的地址,就可看到数字10000,这说明股权全部在MAIN ACCOUNT账户内,还未被分发出去。

·当需要把股权分配给别的客户时(这里假设将10%的股权转让给ACCOUNT 1账户),选择transfer函数,并填入参数,其中包括接收账户的地址和要发送的股权数量,其状态如图7-10所示。

image.png

图7-10 分配股权给ACCOUNT 1账户

在设置好参数之后,单击EXECUTE按钮发送交易。和创建合约时一样,经过12个区块的确认,该交易被写入区块链。 此后在My Shares合约界面输入ACCOUNT 1账户的地址,可以看到数字1000,说明10%的股份已转移给ACCOUNT 1,如图7-11所示。

[1] https://blog.ethereum.org/2015/12/03/how-to-build-your-own-cryptocurrency.

[2] https://github.com/ethereum/mist/releases.

7.4.2 通过控制台部署智能合约

除了使用以太坊钱包外,用户还可以通过web3.js Javascript API在控制台命令行上部署调用智能合约。

1.部署智能合约

在命令行部署智能合约需首先确认已经安装了go-ethereum客户端,其具体步骤如下。

image.png

图7-11 合约交易结果

1)打开一个命令行窗口,运行geth命令同步区块链。由于我们使用测试网进行智能合约的部署,这里需要给geth命令加上参数,执行以下命令:


 

geth --testnet


2)打开另一个命令行窗口,执行geth attach命令。这个命令会打开一个Javascript控制台,通过这个控制台可使用web3的方法和geth自身的管理API调用部署的智能合约。 默认的geth attach命令打开的是附加于标准的以太坊区块链上的控制台。如需打开附加于测试链的控制台,则要明确为geth attach命令指明访问的位置,此时需要加入参数,执行以下命令:


 

geth attach ipc:/home/*用户名*/.ethereum/testnet/geth.ipc


其中,geth.ipc为以太坊的进程间通信接口,此接口用于测试网节点。同样在以太坊文件系统的主目录下也有一个geth.ipc文件,用于标准以太坊节点。

3)用户在部署合约之前,需要知道自己的账户地址和余额。在控制台输入以下命令可看到当前的所有外部所有账户:


 

personal.listAccounts


4)执行以下命令可以以以太币为单位查询账户的余额:


 

web3.fromWei(eth.getBalance("账户地址"), "ether")


此外,还需要解锁要发起智能合约的账户,解锁时需要输入账户创建时所设置的密码,其命令如下:


 

personal.unlockAccount("账户地址")


5)通过控制台部署一个简单的给商品打分的智能合约,其代码如下:


 

contract Rating {
   function setRating(bytes32 _key, uint256 _value) {
       /*为特定编号的商品打分*/
       ratings[_key] = _value;
   }
   /*显示特定商品的分数*/
   mapping (bytes32 => uint256) public ratings;
}


为了方便起见,我们将代码放到Solidity语言的在线编译器[1] 上进行编译,其界面如图7-12所示。

image.png

图7-12 Solidity在线编译器

编译完成之后,在右侧的菜单栏中将Web3deploy菜单下的全部内容复制到控制台中,如图7-13所示。

经过一段时间的确认,控制台提示“Contract mined!”,则合约被创建成功,并返回合约的地址(address)和此次交易的散列值(transactionHash)。可通过这两个数值在区块链上寻找合约的信息。

2.调用智能合约

在部署合约所使用的控制台窗口下,可直接使用合约名和函数名调用合约。假设一个用户想为1号商品打3分,需要调用rating合约的setRating函数,需要执行以下命令:


 

rating.setRating.sendTransaction(1,3,{from:eth.accounts[0]})

image.png

图7-13 在控制台部署智能合约

由于用户需要发起一个交易并改变区块链的状态,因此需要用到合约对象的sendTransaction()方法来发起 交易。该方法的前几个参数为setRating函数的参数,最后一个参数为发起交易的地址,也就是需要为本次交易支付费用的账户的地址。这里 eth.accounts[0]代表钱包内的第1个账户,即基准账户MAIN ACCOUNT。

经过一段时间的确认,该交易被保存到区块链中,此时可使用rating合约中的ratings数组来显示1号商品的打分,其命令如下:


 

rating.ratings(1)


这时将会显示3,表示1号商品被打了3分。由于此时并不需要改变区块链系统的状态,因此不需要使用sendTransaction方法,也就不需要支付任何费用。

如果想在其他以太坊节点通过控制台调用合约,则需知道合约的地址和接口。

执行以下命令实例化合约对象:


 

var NewRatingContract = eth.contract(interface).at(“address”)


这里,interface的信息可从Solidity在线编辑器上获得,而address在合约部署之后返回得到。之后,使用NewRatingContract.ratings(1)命令就可查找到1号商品的打分。

[1] https://ethereum.github.io/browser-solidity.

7.5 本章小结

在这一章,我们首先介绍了什么是智能合约、智能合约的应用以及其起源。之后介绍了在以太坊上部署智能合约的基本知识以 及背后的原理。接下来我们介绍了以太坊最大的特色——以太坊虚拟机的相关知识。最后,通过实例向读者分别展示如何用图形界面的以太坊钱包和控制台命令行部 署运行智能合约。通过这章的学习,读者能够很快地了解智能合约,并顺利地在以太坊上部署自己的第一个智能合约。


来源:我是码农,转载请保留出处和链接!

本文链接:http://www.54manong.com/?id=1010

'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646208", container: s }); })();
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646147", container: s }); })();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值