区块链DAPP开发入门、代码实现、场景应用(txt+pdf+epub+mobi电子书下载)


发布时间:2020-05-11 00:49:33

点击下载

作者:李万胜

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

区块链DAPP开发入门、代码实现、场景应用

区块链DAPP开发入门、代码实现、场景应用试读:

开始

了区块链公链方面的研发。目前我从事的项目主要是对以太坊进行进一步升级。虽然以太坊使得区块链可以编程,但是其操作对象仍然是抽象的数字货币,无法与现实世界中有价值的资产进行关联。我希望通过区块链对带宽流量、分布式存储、CPU算力共享等IT资源进行token激励和记账管理,这些资源在现实世界中是有价值的,且其价值是可衡量的。

在项目开发过程中,我整理了很多底层的区块链架构的技术文档,包括一些智能合约相关的技术文挡,因为这些资料相对较少,并且区块链热潮使得很多人对此类知识相对渴望,因此这些技术文档的读者逐渐多了起来。2018年,出版社的朋友希望我整理一些资料,编写成书,让更多的人可以读到这些技术文档。我觉得这对于区块链开发者和投资人来说都是一件非常有意义的事情,因此我欣然答应,这是本书的写作背景。

因为当前区块链尚处在发展初期,包括以太坊的编程语言Solidity,其版本仍然未达到release版本,因此很多知识可能会发生变化,希望读者及时跟进官方的资料。欢迎读者对本书表述不合理的地方提出建议或意见,我一定虚心接受。本书的代码会放在GitHub上:https://github.com/9992800/Dapp-on-Ethereum。我仅以本书抛砖引玉,希望更多的科技人才加入区块链行业中来,一起促进区块链的良性发展。同时希望本书对投资人有所帮助,提高其分辨骗局项目的能力。

再次感谢出版社对我的信任,以及朋友和亲人对我事业的支持。李万胜第1章 智能合约概述

智能合约是运行在区块链公链上的一种代码,该代码由 Solidity 编写,并通过区块链的智能合约虚拟机来执行,以达到对区块链编程的目标。为了更好地理解智能合约的运行环境,本章将讲解区块链公链的基本概念。可以将区块链公链理解为操作系统,Solidity是编写该操作系统应用程序的编程语言,智能合约虚拟机则是编程语言编译之后的代码运行环境。本章除介绍区块链公链的基础知识外,还会讲解智能合约与区块链公链的交互方式,以及智能合约虚拟机的系统架构。

本章主要涉及的知识点有:

●区块链公链的常用术语和基本知识。

●智能合约虚拟机的系统架构。

●DAPP与区块链交互的方式。

●智能合约编程语言Solidity的基本语法结构。1.1 区块链基础知识

本节将简单介绍区块链公链的基本概念和技术架构,从交易的产生到“区块”这个名称的产生,从挖矿到区块链攻击,从公链基础功能到基于公链操作系统的可编程环境。本节将从不同的角度,对公链的基本技术术语进行简明阐述,对公链底层技术感兴趣的读者可以根据本文的介绍,检索相关资料,比特币和以太坊在官网都有详细的技术文档和资料,感兴趣的读者可以自行检索,本节仅讲解与DAPP开发相关的技术点。1.1.1 交易

区块链通常被理解为超级账本,账户与账户之间可以通过交易来完成转账,只是这种转账方式与传统的银行转账有很大的不同。(1)这是一个完全去中心化的金融系统,区块链账户不需要使用者到银行机构或者其他部门申请,因为整个系统中没有这样的中心化部门来管理账户信息,使用者只需要根据一种非对称加密算法来生成一个密钥对,其公钥作为账户地址,也就是常说的区块链钱包地址,这个地址可以在网络中广播,允许网络中所有的账户获取和使用,如图1.1所示。图1.1 以太坊钱包的地址

该账户地址是公开的。私钥作为转账交易的签名和密码,由使用者私人保管,每次转账时,需要通过私钥签名来证明交易的合法性。在现实使用中,私钥往往非常长,而且非常复杂,因此区块链钱包会将私钥进行对称加密,通过使用者输入人类能够理解的密码作为私钥加密的密钥,将区块链密钥以密文的形式保存在磁盘空间里。但是如果忘记解密密钥的密码,用户就失去了对账户的操作权限,也就是说即使账户有余额并且可以查看,也无法进行转账和使用,这就是社交媒体经常报道的丢失比特币的情况。(2)这是一个完全无中心的账本系统,其交易方式与传统的交易系统有着本质的差别,传统的银行系统中,当A转账100元给B时,银行从数据库中将A的账户扣除100元,同时在B的账户下增加100元。在这个过程中,数据库是在银行的严密保护下操作的,扣除与增加金额的操作需要很高的安全级别才能进行,并且查询余额用的数据库和真正写入的数据大多数情况下是分离的。尤其需要注意的是这个过程生成的交易记录至少有2条:1条是从A的账户下扣除金额的记录,另1条是在B的账户下增加金额的记录。

而发生在区块链上的交易则完全不一样,在区块链上每一笔交易都是一条转账记录,如果该交易成功被整个区块链网络认可则转账成功,并且会将转账记录存储在区块链的数据库里面,每个区块链节点都可以访问和操作这个数据库,并且任何人都可以查询交易双方的账户信息。对于比特币网络来说,BTC 的转账,除了转给对方,还要将账户下的余额转给自己,这样就会生成多条记录,这样做是因为BTC没有账户余额的设计。

如图1.2所示,Bob给Alice转0.5 BTC时,会生成2条交易记录,1条记录是Bob→Alice,另1条是 Bob→Bob,这些交易使用了非对称加密技术的加密和验证签名技术,这个过程属于区块链公链的设计范畴,不在本书讨论范围内。需要指出的是,这样可以在没有任何中心化银行提供服务并且没人掌握交易接收者或者发送者密钥的情况下,实现公开、安全的转账交易。即使交易数据公开在网络中传播,也无法被篡改,即无人可以将Bob转账的0.5 BTC修改为5 BTC,因为任何数据的变更都会导致数据的 Hash 值变化,进而导致数据的签名无法被校验通过,进而无法被全网认可,也就是说伪造任何报文和交易,都是无法被网络认可的。这样,比特币在没有银行并且公开交易信息的情况下也能转账。图1.2 比特币网络交易(3)发生在区块链上的交易,是存储在区块链网络中的所有全节点(拥有所有交易数据的区块链节点叫作全节点)上的,并且也是公开可查的,用户不仅可以查询某个账户下的所有交易,还可以查看其账户余额,这与银行的交易记录也是截然不同的。图1.3所示为比特币区块链浏览器,这些查询系统被称作区块链浏览器。与银行不同的是,即使能看到区块链交易的所有细节及所有账户的余额,也无法将账户与现实世界中的人联系起来,这与银行系统的账户设计完全不同。图1.3 比特币区块链浏览器1.1.2 区块

所有的交易信息都会形成一个结构化的账本,它们会被区块链的节点(矿工)按照一定的方式和时间间隔组织起来,存储在区块链节点中。这个用于存储交易信息的结构体就是区块,除了交易信息,区块还要存储一些额外的信息以保证交易数据的完整性和可靠性,区块数据结构如图1.4所示,该结构图仅仅列举了一些关键信息,不同的公链有各自不同的设计。关于区块的生产间隔,不同的区块链网络有不同的设定,比如以太坊出块的时间间隔约为15s,而比特币网络则需要10min才生产一个区块。图1.4 区块数据结构

区块数据包含区块头与区块体,区块体存储具体的交易及交易相关的原始数据,区块头存储的是原始数据的Hash信息,任何对原始数据的修改,都会引起区块头Hash值的变化,这样对任何信息的篡改都很容易被察觉到并被验证为假数据。除了交易原始数据和对应的 Hash 信息,还有一些为了维持区块安全性和有效性的附加信息,比如区块高度、区块难度、矿工地址等;不同的公链系统,还有不同的额外信息,比如以太坊会有状态变更的原始数据和对应的默克尔树信息、收据信息和账户余额信息等。

因此,区块的作用就是将不同时间阶段内的交易数据按照一定的格式和数量,打包成结构化数据,方便存储和管理。只有被打包到区块中并且被全公链网络认可的交易,才能算真正的有效交易,此时账户下面的数字货币的数量才会真正地发生增减,如果交易失败则转账无效。1.1.3 链

区块头和区块体数据也会被当作输入数据做一次 Hash 运算,其运算结果会被存储在下一个区块的区块头中,这样任何区块内容的修改都会反映到区块的Hash值上,而区块的Hash值又是下一个区块的输入数据,它又会被当作新区块的数据参与一次新区块的 Hash 运算,随着时间的推移和交易量的增加,所有的区块会通过保存前一个区块的Hash运算结果的方式组成一条链。

图1.5所示为区块链数据结构,对这个链的数据进行任何篡改,都需要修改此链的所有数据及对应的Hash值,只有这样篡改数据才能被校验为正确的数据。这需要非常强大的算力来支持,同时需要比较长的时间才能完成,而在篡改数据的过程中,还会不断有新交易加入区块链,因此从工程实现的角度来讲,这种篡改需要消耗极大量的能源并需要组织巨大量的计算资源,从经济的角度来讲,进行篡改不如进行算力挖矿划算,完全得不偿失。图1.5 区块链数据结构

将交易打包成数据块,再将数据块以Hash值的方式组织成链式结构,就是区块链定义的来源。这种结构有点类似于计算机数据结构中的单向链,不同的是,这个链式结构不再局限于单个计算机,而是由分布在全世界的节点构成,任何人都可以公开查询,但又无法任意修改。1.1.4 挖矿

区块在被增加到区块链之前,并不是所有区块都可以生成区块数据,也不是所有区块数据都能被增加到区块链成为最新的数据,这个过程有一定的门槛,需要筛选出一个值得信任的节点来生成数据,然后由其他节点来验证其生成数据的有效性。这个生产区块的过程会得到数字货币的激励,因此很多节点会加入生产区块的竞争。如果某个节点生产的区块数据得到了其他节点的验证,则其他节点会将最新的区块存储在本地,然后加入下一个数据块的生产竞争,这个过程被称作挖矿,而生成数据的节点被称作矿工。

如图1.6所示,所有的交易会被保存在一个交易池中,不同的矿工会选择不同的交易进行打包,然后制作属于自己的区块,假设当前将要生成的区块编号是121,因为区块链网络是一个点对点的网络,没有中心节点进行协调,所以这些矿工节点无法感知到彼此的存在。

在矿工将交易打包成区块之后,还需要查找一个数字n,这个数字n需要满足图1.6中的不等式,得到该数字之后将其放入打包的区块数据中,然后将该区块数据广播到区块链网络中,如果该区块被认可,则这个矿工就成了区块高度121的出块人,而网络中的节点将新生产的区块存储在本地,并将其链接到其他区块数据之后,基于此生产下一个区块。计算有效数字n的过程是需要付出算力的,而这个算力付出的过程是值得信赖的,这就是区块链为何可以在没有中心管理者的情况下正常运作,且任何人都可以在无须信任的情况下参与区块链业务,正所谓“In math we trust”。图1.6 矿工从交易池中选择交易打包,并通过寻找满足不等式的数据n来挖矿

成为出块人就可以成功拿到奖励,奖励分为系统的奖励和交易中的交易手续费,在每一笔交易数据中,转账人都可以手动设置手续费,这些手续费用于奖励矿工打包的工作。在转账时设置的手续费越高,转账时间就越短,转账速度就越快,这是因为矿工们会优先选择手续费高的交易进行打包,而如果交易的手续费过低,很有可能会因为没有矿工愿意为其打包而导致转账失败。1.1.5 共识算法

在挖矿的过程中,矿工需要付出算力来查找一个满足条件的数字,这种算力的付出是无法伪造的,必须付出相应的算力和电力之后才能得到正确的数字。当某一个节点发现该数字之后,其他节点可以很快验证该数字的有效性,验证并不用花费太多的算力和能源。也就是说图1.6中查找满足不等式条件的数字的过程是困难的,但是验证不等式的过程是简单的。这种为了持续生成区块而被所有网络节点认可的方案就叫作共识算法,而付出算力来证明自己工作的共识算法被称作 PoW (Proof of Work)。

除了PoW这种共识算法,还有一些其他的共识算法,它们的特征都是一样的,全网节点都共同认可并遵守该算法,并且该算法产生的结果无法被伪造,但是可以被轻易验证。目前比较流行的共识算法有DPoS、BFT和PoST等。

●DPoS算法类似于民主选举的运行模式,该算法根据数字货币持有的数量对区块事务进行投票管理,并且轮流来选举出块人进行出块,笔者认为这种弱中心化的方案与区块链的根本价值观背道而驰,但是在编写本书之时,此方案仍然流行。

●BFT是著名的拜占庭容错机制,该机制的产生有着有趣的背景故事,读者可以自行查找相关细节。简单来讲,该方案通过多次通信交互来区分恶意节点和诚实节点,接收诚实节点的区块数据,丢弃恶意节点发送的数据和消息。

●PoST是存储公链的共识算法之一,即统计节点有效存储的数据的大小和时长,将其作为节点的算力,来竞争成为出块节点,算力越大成为出块节点的概率越高,算力越大生成恶意数据的动力就越小,这是通过一种经济手段约束恶意行为的共识算法。

以上共识算法只是众多公链共识算法中的一小部分,是目前比较流行的几种方案。关于算法的技术细节,读者如果感兴趣,可以以本节的内容为线索,进行深入的探究。由于这些技术已经超出了本书的范畴,所以本书仅做简单论述,不再详细讲解。1.1.6 分叉

因为整个区块链系统是点对点的对等网络,没有统一的中心机构协调各个节点的行为,所以在生产区块时,各个节点的行为都是相互独立的,很有可能同时由多个矿工在同一区块高度生产出2个以上的区块来。这些区块打包的交易很可能是不一样的,同时满足条件的数字n不是唯一的,多个矿工之间生产的数字n是不一样的,但是同样是满足不等式的。在这种情况下,网络中的其他节点很可能同步到不同的区块数据,并且这些数据在数学上都是合法的、有效的。当不同的节点中的不同的区块作为当前最新区块时,就会存在分叉的情况,即不同的矿机对同一高度的区块生产了内容不一样的新区块,并且这些矿工都找到了满足不等式的数字n。

如图1.7所示,矿工A和B同时对编号121的区块进行打包并签名,它们同时广播了自己的区块到系统中,不同的节点有些会收到矿工A的区块数据,然后验证通过,并加入本地的账本,有些会收到矿工B的区块,这样整个系统的账本就会出现不一致的情况。为了解决这个问题,区块链采用了一种长链抛弃短链的决策方式:即当矿工 C 挖出了第122块数据,并将数据指向了矿工 B 的121区块时,因为矿工B所在的区块链更长,凝聚了更多的算力,既是最安全的,也是付出算力和资源最多的,此时矿工A所在的区块链在区块120位置发生的分叉将会被抛弃。区块121_A内所包含的交易也将失败,如果有一笔交易既被区块121_A 打包,又被区块121_B 打包,那么即使区块121_A被抛弃,该交易仍然成功;如果交易只在121_A区块中,则交易会在短链被抛弃前显示成功,在短链被抛弃后显示失败,失败后交易双方的账户余额会恢复到转账之前的状态。

这个分叉非常像我们在微信群里参加某项活动时的报名接龙,由于报名者在看到报名接龙消息后,并不知道其他报名者的报名意愿和状态,很可能出现多个人报同一个序号的情况,在这种情况下,接下来的人会选择其中一个信息次序往下接龙,而未被选中的那一个次序将被抛弃,被抛弃的报名者只能基于最新的接龙信息来重新报名。图1.7 分叉1.1.7 攻击

区块链对于交易的组织方式及账户的管理方式决定了只有私钥持有者才有权修改本账户下的数字货币信息。任何区块链网络中的参与者都无法控制或者篡改其他人的账户信息,也可以说从经济的角度看,篡改这些数据的成本极高,这与中心化的银行有着完全不同的特点,在前文曾讲到的A转100元给B的过程中,如果银行有内部员工,在给A扣除金额或者给B增加金额的环节修改数据,则A和B都无法控制这样的事情发生。

但是这并不意味着区块链是绝对安全的,不同于攻击银行是违反法律法规的情况,由于区块链网络是无主的,每个持有数字货币的账户和区块打包的矿工都无须审批、无须信任,随时随地可以参与,因此,只要拥有足够的算力就可以对整个网络发起攻击,比如著名的51%算力攻击,BCH区块链网络的算力大战就证明了区块链在安全上也有自己天然的弱点。除了算力攻击,还有很多中心化银行不会存在,仅属于区块链点对点网络特有的攻击,比如女巫攻击、日食攻击等,在本书的最后会对此类安全问题做一个较为概括的论述。本节仅以一种算力攻击为例,简单讲解一下区块链特有的安全问题。

结合上一节讲到的分叉问题,如果矿工D拥有足够的算力,则当矿工A生产区块121时,矿工D已经生产了区块121,但是矿工D可以将最新的区块保留,而不是将其广播到网络中(区块链代码都是开源的,任何人都可以修改属于自己的版本,只要符合区块链的基本协议,就可以加入区块链网络,只要其行为被网络中的其他节点认可即可),并且继续生产最新的区块122,这样在A生产出来121区块之后、其他矿工生产122之前,矿工D可以将区块121和122一次性地全部广播到网络中,那么之前矿工A生产的区块就会被当作短链而抛弃,而矿工A中的所有交易也将被重置为失效。

在这样的一种攻击模型下,假设Alice在矿工A生产的区块中发生了一笔交易,这笔交易是将余额转到数字货币交易所变现,在数字货币交易所收到这笔交易之后,Alice在数字货币交易所的账户余额会增加。然后Alice将矿工D的2个区块同时广播,则Alice转账到交易所的交易就会失效, Alice的区块链账户余额会恢复,但是因为数字货币交易所的数字余额是中心化管理的,在感知到区块链被攻击之前,Alice可以将数字货币兑换成法币。这样Alice就可以在数字货币余额不变的情况下将其成功变现为法币。1.2 以太坊智能合约

在讲解了区块链的基本知识之后,我们聚焦于以太坊这条公链的独有特征,除了拥有上述区块链的基本功能,以太坊还在比特币网络的基础上增加了以太坊智能合约虚拟机,即EVM。在增加了EVM 之后,以太坊就成了一个可编程的去中心化平台,任何系统开发者在支付一定的部署费用之后,就可以拥有一套完全去中心化的业务系统,这样的业务系统被称作 DAPP,这是本书论述的主要内容。1.2.1 以太坊

在前一节讲解区块链基本知识的基础上,本节简单讲解以太坊公链特有的公链特性。首先以太坊可以视作大型的状态机,由分布在全球的以太坊节点来运行这个状态机,每一次交易的产生都会修改状态机的状态。将交易打包成区块之后,可以以区块为单位来衡量状态的迁移,如图1.8所示。

以太坊的状态是由以太坊上的所有账户组成的,也就是说,以太坊上的所有账户组成了以太坊的全局状态,以太坊账户是地址与账户状态的一个映射结构。以太坊账户又分为两种:一种是外部账户,也就是用于存放用户余额和转账的账户;另一种是智能合约账户,是在部署智能合约时生成的一个关于该智能合约的区块链地址及其状态的映射关系。

以太坊全局状态及账户结构如图1.9所示。账户1是智能合约账户,其地址映射到一个账户状态,这个状态由4部分组成:nonce、余额、存储数据的Hash值、EVM代码的Hash值。存储空间是智能合约虚拟机运行时需要的存储介质,EVM 代码是智能合约代码编译并部署到以太坊之后的数据。账户2是外部账户,该账户的状态仅仅由nonce和余额组成。以太坊账户由这两类账户组成,所有这些账户组成了以太坊的全局状态。图1.8 以太坊状态示意图图1.9 以太坊全局状态及账户结构

以太坊上的账户之间会有系统调用,本书仅仅讲解与DAPP相关的系统调用。

首先是账户生成交易对以太坊状态的修改,如图1.10所示。每次创建和部署智能合约,都会创建一个新的账户,创建账户的代码包含在交易中,交易可以由外部账户发起,也可以由合约账户发起,创建合约账户的代码通过Solidity编程语言编写,通过以太坊的智能合约编译器编译生成。图1.10 账户生成交易对以太坊状态的修改

其次,账户生成之后,会开辟账户存储空间和以太坊虚拟机可以理解并执行的代码,如图1.11所示。当智能合约生成之后,可以通过ABI接口调用该智能合约,这种信息调用也会对区块链的状态发送读写操作。通过ABI接口传入外部数据,然后通过以太坊智能合约虚拟机读取合约代码,结合输入的数据和区块链上存储的数据,修改区块链状态,并将最新的状态信息存放在以太坊公链上。关于ABI接口,将在第4章中详细介绍。图1.11 消息调用交易对以太坊状态的迁移1.2.2 EVM

在以太坊之前的公链项目中,如果需要修改某条公链的某些特征或者增加对某种场景的支持,开发者必须在原有的公链设计的基础上修改系统底层源代码,并重新维护一套公链生态,这往往被称作硬分叉。比如比特币的众多分叉币,开发人员为了修改诸如区块大小的参数,需要重新分裂一条公链,这是一项极其繁重的工作,不仅会消耗资源、时间,有时候还会分裂原有公链的共识社区。

而以太坊 EVM 的出现,使得任何需要实现某一行业具体逻辑的开发者,无须复制以太坊的整套代码,然后修改出符合自己逻辑的公链,而是基于以太坊现有的公链网络和矿机组织、共识社区,通过EVM提供的编程API来编写智能合约,就可以完成一套区块链系统,一套满足自己业务需求的系统,该系统具有区块链所有的通用特征:去中心化、公开透明、无法篡改等。

因此,EVM的出现使得对区块链编程成为可能,具有EVM的以太坊公链技术可以视作是对原有区块链技术的一次重大革新。如果把区块链比作PC操作系统,那么EVM就是类似于JVM一样的运行环境,Solidity智能合约就是类似于Java的高级编程语言,而EVM出现之前的比特币网络仅有有限的指令可以对区块链编程,有点类似于底层的汇编语言编程。从这个角度来讲,EVM使得区块链高级语言编程成为可能。

EVM作为一个离线的、独立的运行环境,它无法访问外界的文件系统、网络接口等资源,目前EVM 的指令中尚未支持对这些资源的操作。当编写的智能合约被编译成 EVM 能够理解的代码之后,EVM会在自己独立的运行环境中执行用户编写的智能合约程序,EVM的架构及代码执行流程如图1.12所示。图1.12 EVM的架构及代码执行流程

通过智能合约编译器,可以将智能合约编译为虚拟机能够理解的指令,然后通过以太坊账户发起一个交易,将这些指令部署到区块链上,部署成功后会得到一个以太坊的地址,这个地址指明了该合约代码存放的位置。该过程会在第6章中进行详细的讲解。

当合约被调用时,可以根据合约的地址找到指令集的存储位置,虚拟机通过程序指令计数器记录当前指令的执行位置,随着程序的执行,指令计数器也会随之变化,它存储了下一条需要执行的指令的地址(偏移地址)。在指令中会有JUMP之类的跳转指令,因此指令计数器的数值并非总按顺序逐渐增加。

EVM的指令执行不在寄存器中进行,而是在一个被称作栈的内存空间中进行。这个栈最多可以容纳1024条栈指令,每条栈指令可以达到256位,也就是说EVM是256位的计算机,这个长度的指令特别适合Keccak-256 Hash数据及椭圆函数运算。

智能合约的指令中包含了操作码和操作数,EVM加载到栈中之后,根据操作码运算,在执行操作码的过程中,会产生中间状态的临时数据,这些数据会被存放在栈空间上、内存中或者持久存储的空间上。指令中也有从内存或者持久存储空间中读取数据的操作,有些操作需要在区块链状态的基础上进行下一步的操作,这些状态信息就存储在持久存储空间上;有些操作是存储的运算,这些数据一般通过内存进行读写。使用内存和持久存储空间需要支付一定的 GAS 费用。GAS 指的是EVM执行指令时消耗的代价,这个代价以以太坊的数字货币ETH来表示。1.2.3 智能合约

以太坊公链是操作系统,EVM 是区块链代码的运行环境,而 Solidity 则是区块链的编程语言,通过编程语言编写的逻辑模块被称作智能合约。智能合约与人工智能并没有任何关系,甚至和智能也没有多大关系。因为其运行过程不再需要人为干预,一旦提交到区块链之后就再也无法篡改,并且可以在无监管、无干预的环境下自主运行,因此将这样的代码称作智能合约。

图1.13所示为EVM的系统架构。智能合约部署成功后是EVM代码,它们运行在以太坊虚拟机即 EVM 上,而 EVM 又是以太坊公链逻辑的一部分,所有的这些模块都通过底层的操作系统API来实现对硬件资源和网络资源的访问。目前以太坊公链的代码支持多种操作系统编译,并且有多种语言编写的版本,目前较稳定的是Go语言和C++语言的版本。图1.13 EVM的系统架构

智能合约在区块链上的可执行代码是一种类似汇编语言的指令集,这些指令集通过EVM的解释和执行,对区块链的状态进行读写,实现合约规定的业务逻辑。因此通过Solidity这种高级编程语言,加上 Solidity 编译器,可以将高级语言编译成汇编指令代码,再将其部署到区块链上执行。

图1.14所示为合约代码到EVM代码的转换流程。在这个过程中,开发人员按照Solidity的语法编写业务逻辑,编译器会将智能合约编译成合约初始代码和一些人机交互需要使用的辅助数据,其中包括ABI接口描述数据和一些其他的说明性文件。账户发起创建合约交易时,以太坊交易中会加载合约创建代码,矿工在打包交易时会执行该合约的初始化代码,并生成智能合约对应的 EVM代码和该合约对应的账户地址,当该交易所在的区块被成功打包并同步到其他节点时,其他节点就可以通过消息调用来访问该合约对外开放的接口和功能。图1.14中各个步骤涉及的技术内容是本书的主要知识点,笔者将会在后面的语法环节、编译文件分析环节和DAPP案例讲解环节进行详细阐述。图1.14 合约代码到EVM代码的转换流程1.2.4 DAPP

Solidity编程语言解决了编写智能合约的不友好的问题,但是当合约编译并部署之后,对于一般的使用者来说,访问这些接口的门槛较高。为了使广大用户理解并方便快捷地访问区块链及区块链上的智能合约系统,开发者必须提供操作界面和结果查看界面,来简化用户访问和操作区块链的方式。因此一套完整的区块链 DAPP,除智能合约这些可以查询和改变区块链状态的代码外,还需要用户操作界面及连接用户操作与智能合约代码的接口。

图1.15所示为DAPP架构图,该架构图包含了从用户通过可视化界面发起操作到以太坊状态机发生改变的全流程。首先,用户通过 Web 界面或者手机 App 将操作数据发送到一个传统的业务服务器,该业务服务器是传统互联网中心化的服务器,但是与传统系统不同的是,该系统没有像传统互联网设计那样将数据放入中心化的数据库存储,而是通过一个Web 3.0接口,将数据传送到以太坊区块链公链上。

该接口是一个JSON RPC协议,该协议有很多代码实现。目前最流行的是运行在Web容器中的Web3.js模块。Solidity编程语言经过编译之后,除了交易需要的合约初始化代码之外,还有ABI接口等描述文件,Web3.js通过这些描述文件,可以构建与以太坊智能合约虚拟机进行通信的模块,通过JS(全称JavaScript,下文都简称JS)代码将用户的操作数据传入以太坊公链上的合约地址,智能合约虚拟机会根据函数签名和加载的函数参数,在虚拟机内执行编译成 EVM Code 的智能合约。在第6章的DAPP案例讲解中,会对Web3.js的设计进行详细的讲解。图1.15 DAPP架构图

如果涉及区块链数据的读取,则虚拟机会读取区块链上的区块数据。如果虚拟机的指令代码修改以太坊公链的状态,那么通过调用相关的状态机指令,并消耗一定的GAS之后,就可以将修改操作提交到以太坊区块链公链网络中,这些操作往往以交易的方式体现。

在虚拟机执行任务结束后,其对区块链状态的修改会被矿工打包。当状态修改被全网共识时,虚拟机对公链网络的状态修改也相应成功。可以通过查询相关的执行结果,将执行状态返回给用户交互系统,这样终端用户就可以通过交互系统查看DAPP操作的执行结果。1.3 简单的智能合约

本节将以2个简单具体的示例来说明智能合约编程语言Solidity的基本语法,通过阐述Solidity编程语言的API及使用方式,使读者对智能合约编程有一个初步印象。本节的2个示例均来自以太坊官方网站。1.3.1 示例1

下面仅以一个简单的智能合约示例,介绍智能合约的基本组成元素,本合约定义一个uint类型的变量,以及这个变量对应的读写函数。

第1行代码指明语法解释使用的编译器版本,目前以太坊的智能合约虚拟机仍然在开发升级中,尚未有release版本,不同版本之间的API变动比较大,因此在编写智能合约时,必须指明合约逻辑使用的API的版本号;第3行代码通过contract关键字,声明了一个智能合约结构体,可以将该关键字与面向对象编程的class做类比,contract的设计模式和方法重载、父子继承等特性,与面向对象的设计非常接近,因此说 Solidity 作为一种高级的编程语言,极大地降低了对以太坊公链编程的难度。

第4行代码定义了一个成语变量,该变量的可见范围在contract的大括号之内;第6行、第7行代码定义了一个函数,该函数有可见范围的修饰符 public,关于可见范围的详细解释放在了第3章中,在此读者仅仅需要了解,该描述符说明该函数可以被区块链以外的系统通过Web 3.0接口调用,也可以通过以太坊内的其他智能合约调用。

第10~12行代码定义了一个读取函数,函数的可见范围也是public,但是因为这个函数没有修改区块链上的任何数据和状态,所以可以将该函数定义为view,类似于其他编程语言的readonly属性,同时在函数的最后定义了函数的返回类型。

这个智能合约相对简单,仅仅是将一个uint的数据保存在区块链分布式账本中的合约。任何账户都可以读写这个数据,本示例并没有对数据做任何访问限制,这意味着任何人、任何账户都可以读取和修改这个数据,在后面的章节中,会详细介绍数据的访问权限和安全性问题。1.3.2 示例2

本示例定义了一个简单的数字货币的智能合约。当前关于区块链技术的讨论,几乎全部围绕数字货币展开,但是大多数人对数字货币的基本知识都比较匮乏,其实目前很多数字货币本质上都是以太坊公链上的智能合约。关于基于以太坊公链发行的数字货币的详细知识,笔者将会在第5章详细论述。本节以一个简单的数字货币智能合约来讲解数字货币智能合约应该具有的基本元素。

第4行代码定义了一个区块链地址类型(address)的变量,该变量为public类型,定义这个变量是为了记录铸造该合约的账户地址,该地址在合约构造函数中进行了赋值。第9~11行代码定义了该合约的构造函数,该构造函数仅仅在合约部署到以太坊公链时执行一次,合约部署成功之后就无法修改此合约的逻辑了,第10行代码中的 msg.sender 表示的是包含该合约初始化代码的交易发起者的区块链地址。也就是说Coin这个合约的铸造者就是部署本合约的账户,该账户可以是外部账户,也可以是智能合约账户。

第6行代码定义了一个映射关系数据结构,这个结构定义于区块链地址下,关于该数字货币的余额,目前基于以太坊的数字货币,只要符合ERC20协议,很多第三方开源的数字货币钱包都可以显示数字货币的余额,其实这个余额就是该Coin合约中,balances变量存储的数值,不同的地址存储了不同的数值,在数字货币钱包中,通过查询以太坊公链下某一地址下的ERC20数字货币合约的balances变量,就可以显示数字货币余额。

第13~17行代码是一个铸币函数,所谓的铸币函数,本质上就是修改balances这个数据结构的内容,因为该结构存储了各个区块链地址下的数字货币的余额,所以调用这个函数时,需要检查调用者的区块链地址是否与智能合约初始化时合约的部署者一致,如果不一致则该函数执行失败,同时要检查本次铸币的数量不能高于1e60个,如果数字货币总量超过这个数字,铸币操作也会失败,如果以上条件都满足,那么就会在receiver这个账户下增加amount个数字货币。如果以上require的条件不满足,该合约调用会失败并会导致以太坊状态机发生回滚,以太坊的状态会恢复到调用该函数之前。

第9~24行代码是一个转账函数,在检查调用者有充足的余额后,在调用者名下减少amount个本智能合约代表的数字货币,同时在receive这个地址下增加相应数量的数字货币,在函数被成功执行之后,将本次执行时间emit一个事件,该事件产生log,并被永久的保存在以太坊区块链上。该事件需要首先在第8行代码进行声明,然后在后面的函数中调用。由本函数可知,所谓的数字货币转账实际上只是修改balances下存储的数据而已。

本示例是一个简单的铸币智能合约,简单地演示了铸币的权限检查、数字货币的账户余额管理及账户之间互相转账的功能,本智能合约还有很多关于数字货币的问题没有解决,只是简单地给读者展示一个发行数字货币的逻辑,以便读者可以对当下火爆的数字货币有初步代码级的认知。以上代码仅供学习使用,完全没有达到可以商用的要求,请读者使用时注意。1.4 小结

本章主要介绍了区块链公链的通用知识,以及以太坊公链特有的属性,本书论述的DAPP系统的原理与实践案例都是基于以太坊公链的技术特点展开的。读者需要通过本章的学习对以下问题有清晰的认识:

1,区块链公链系统与中心化的银行系统是截然不同的,在运行方式、数据记录及账户管理方式等方面有着巨大的差异,读者可以通过与传统银行系统各个方面进行对比的方式,掌握区块链公链的技术特点。

2,智能合约是运行在虚拟机上的,智能合约的编程语言是高级编程语言Solidity,该语言编写的智能合约需要经过编译器编译成 EVM 能够访问和执行的代码才能够与以太坊公链进行交互。该设计类似于Java编程语言,读者可以通过类比的方式对Solidity编程语言有初步的认知。

3,要形成一套完整的DAPP系统,除了需要与以太坊公链交互,还需要设计一套与用户交互的UI系统。UI系统需要经过RPC与区块链节点客户端进行交互才能够对以太坊公链的状态进行修改,并且通过标准的Web 3.0协议进行余额读写和账户转移等业务。第2章 开发环境搭建

同其他的编程语言一样,以太坊智能合约开发语言也有自己的开发环境,只是目前不同的开发环境侧重的开发环节不一样。不同应用领域的开发者可以选择适合自己工作需要的开发环境。从轻量级的页面语法编译环境到重量级的以太坊公链模拟器,读者可以根据需要选择不同的内容进行学习。

本章主要涉及的知识点有:

●通过Remix,掌握调试智能合约代码的语法。

●通过Ethereum Wallet,在以太坊公链测试环境下调试智能合约。

●通过Truffle和Ganache,从交互到智能合约编写,从部署到运行调试,建立一整套完整的开发环境。

●通过MetaMask的浏览器插件,给普通用户提供DAPP服务。2.1 Remix的使用

Remix是一款可以在线编写调试代码的编辑器,也支持部署代码和查询区块链数据,它的优势在于可以快速编写合约代码并找到语法错误,并且不需要很复杂的配置环境,我们在编写代码初期使用这款软件特别合适,工具地址: http://remix.ethereum.org。2.1.1 编程界面

登录Remix首页,其中主界面的编译器分为3个部分,如图2.1所示。

首先来简单熟悉一下Remix编程工具提供的功能,左侧提供的是文件管理能力,图中区域1中包含新增按钮,单击该按钮可以生成一个后缀为,solde 的智能合约文件。将第1章中用到的第一个Solidity源代码复制到图2.1所示的中间的输入框中,文件名以合约的名称来命名:SimpleStorage.sol。图2.1 Remix编程环境

文件的内容相对简单,仅有12行代码,需要注意的是第1行代码,上一章曾经讲解过:该代码指明了该合约代码需要的编译器版本,该内容可以根据编译环境进行微调。在编译器的右侧,Compile这个标签页下面,最新版的Solidity编译器是0.5.2版本,为了使用最新版编译器,可以将代码调整为与该编译器一致。如果不修改代码,则可以从图2.1中区域7的下拉菜单中,选择适合自己合约版本的编译器,不同的编译器语法会有所不同。截至本书出版之时,最新的编译器版本号是0.5,距离成熟的1.0版本还有很长的路要走,由此可见区块链领域仍有广阔的开拓空间。

在版本选择器下方有3个选项,其中Auto compile表示可以将编译器设置为实时编译,这样在编写智能合约时,可以实时显示语法错误,这些错误信息会显示在图2.1的区域9中。如果存在语法错误,区域9会给出相应的语法错误及修改意见;如果显示绿色,则没有语法错误。当编写的智能合约文件过大或者智能合约依赖的文件过多时,可以关闭自动编译的功能,选择图2.1中的编译按钮来手动编译代码。因为在开启自动编译的情况下,当创建的文件过多时,每次切换文件都会产生一次编译动作,这会使浏览器的速度变慢,所以可以通过手动编译的方式来解决该问题。

图2.1中区域10涉及的Bytecode和ABI曾在第1章中解释过,Remix作为Solidity的编译器,也可以生成这些描述文件,开发者可以通过这些数据,方便快速地调试DAPP。除此之外,图2.1的区域3中的加号和减号可以改变编程区内文本的字体大小,同时Remix编译器还会在代码发生变化后的5s后开始自动保存文件。2.1.2 运行环境

合约代码编译成功之后,操作者就可以将合约部署到以太坊公链上,在图2.1右侧的Run标签下,可以看到运行和部署智能合约的相关选项。图2.2中的区域2表示的是本智能合约运行的环境,其下拉选项目前有3个,分别是JavaScript VM、Injected Provider和Web3 Provider,这3个不同的运行环境表示本合约需要部署的目的不同。图2.2 Remix合约运行环境

●JavaScript VM使用的是一个沙箱(或沙盒)运行环境,它并不会与以太坊公链发生交互。

●Injected Provider表示使用嵌入到浏览器的公链运行环境,往往使用MetaMask这个插件提供的运行环境间接与以太坊环境发生交互。

●Web3 Provider则表示直接与真实的以太坊网络环境进行交互。

为了简便起见,使用JavaScript VM来测试编写的程序,可以在无须任何配置的情况下,快速对代码的语法进行检查,这也是本节介绍Remix的目的,它非常适合快速地检查和测试智能合约。因为正式的开发环境或者与区块链系统发生交互时需要产生真实的交易,并支付一定的费用,并且需要矿工来挖矿打包,才能将合约真正地部署到区块链系统上,这些知识点曾在上一章中简单介绍过。而如果选用 JavaScript VM 运行环境,则可以直接在浏览器中检查和调试智能合约,调试通过之后再正式部署。

图2.2中的区域3、4和5是开发和部署智能合约必不可少的元素,本开发环境的智能合约必须通过一个外部账户来发起,其中的区域3表示账户地址,地址后面的数字表示账户的余额为99.9 ETH。图2.2中的区域4表示该智能合约需要消耗的成本,以太坊将其称为程序部署需要的燃料,图2.2中的区域5表示在生成智能合约的同时将一定的余额转移到新创建的智能合约账户下,具体内容将在以后的案例中讲解。

图2.2中的区域6的位置是一个下拉菜单,当维护和管理多个智能合约文件时,可以通过下拉菜单来选择需要部署的合约文件。图2.2中的区域7是部署按钮,单击之后可以执行部署操作。

当部署结束之后,图2.2中的区域8、9和10展示了部署后的结果,在JavaScript VM中部署会非常快速,因为这只是个简单的沙盒环境,可以省去很多底层的调用。区域8表示合约地址,区域9和10则表示该合约提供给外部的功能。

Remix为所有的对外合约接口提供调试界面,本例中提供了2个函数:一个是get函数,另一个是set函数。get函数可以通过合约读取区块链上的状态信息,set函数则可以设置区块链状态。Remix调用部署后的智能合约界面如图2.3所示。图2.3 Remix调用部署后的智能合约界面

调用get函数,会产生一个Message Call,该操作不会修改区块链的状态,只是从区块链上查询数据,在默认情况下,可以看到调用get函数的结果是0,如图2.4所示。

调用 set 函数会产生一个交易,该交易修改了区块链的状态,并且需要矿机将交易打包到区块链中,set函数才能够调用成功,调用set函数之后,再一次通过get函数来查询合约中存储的数据,图2.4中的区域4显示修改已经生效。图2.4 get函数操作产生的消息

图2.5所示为set函数操作产生的交易数据,第一次调用get函数和第一次调用set函数时,系统发送给区块链的参数,这些参数内容会在后面的章节中用到,此处读者仅仅需要注意两次调用的不同之处,即调用set函数时,有status状态栏,并且该栏有矿机挖矿成功的提示。图2.5 set函数操作产生的交易数据2.1.3 其他设置

Analysis 标签页中的很多选项都是在以太坊发生了多起被攻击的事件之后,编译器做出的改进性检查,像其中ECR20的检查,在2018年就有很多故事,读者可以自行搜索代币攻击事件。在本书的第7章中,笔者也会以案例的形式讲解其中的一种区块链合约攻击,这里不再展开讨论。有兴趣的读者可以逐条搜索每个选项背后的含义,这里建议保留默认选项,不要轻易修改,除非读者深刻地理解每条选项的含义。智能合约自动检测选项如图2.6所示。

像其他的IDE环境一样,Remix也提供了单元测试脚本的运行环境,如图2.7所示,并且Remix可以帮助开发者生成Solidity版本的测试代码。在修改代码后,可以单击Run Tests按钮来测试代码修改之后业务逻辑上的准确性,如果单元测试失败,会有相应的提示。图2.6 智能合约自动检测选项图2.7 Remix单元测试

智能合约运行时环境的复杂性使得智能合约的单步调试成为一件非常烦琐的事情,而Remix提供的单步调试界面如图2.8所示,这是Remix与其他开发环境相比有优势的地方。图2.8 单步调试界面2.2 Ethereum Wallet的安装与使用

不同于Remix的轻量级、无须安装的开发环境,Ethereum Wallet需要下载和安装后才能够使用,属于比较复杂的开发环境,但是其优势也比较明显,可以直接与测试或者正式的以太坊公链环境进行交互,并且可以直接显示交互结果。2.2.1 安装

在以太坊官方网站中可以下载最新版本的Ethereum Wallet,用户无须选择,浏览器会根据访问者的操作系统版本自动展现合适的版本,单击DOWNLOAD按钮下载即可安装,如图2.9所示,其下载网址:https://ethereum.org/。图2.9 以太坊官方网站

安装完成之后,先修改设置,将客户端切换到测试网下面,正式网络环境是真实的以太坊交易网络,在熟练掌握智能合约开发之前,建议读者首先在测试网络中编写和部署合约。在图2.10所示的下拉菜单中选择Ropsten网络,同时将Sync mode设置为Light(图2.10中并未展示,请读者自行查找),因为笔者之前创建过2个账户,所以会显示账户内容,并且有ETH余额。往往新安装的客户端并没有账户,需要单击ADD ACCOUNT按钮来创建账户。

创建账户时需要输入密码,此密码是用来对RSA非对称加密的密钥进行对称加密的口令,创建账户的过程就是生成公钥和私钥的过程,公钥就是账户的地址,如图2.10所示,可以单击ACCOUNT 1和ACCOUNT 2查看其具体的地址,这个地址就是账户的公钥,而私钥是转账的关键,如果丢失私钥就意味着钱包的所有权丢失,所以不能将私钥的明文存储在磁盘上。

通过输入密码,将密码用作对称加密的密钥对私钥再进行加密,加密之后再将其存储在磁盘上,如图2.11所示。这样即使丢失磁盘上的文件,偷盗者得到的也仅仅是加密后的私钥,而破解这些私钥密文需要投入很大的算力和成本。

图2.12所示为公钥、私钥密文等信息的存储位置,图中的文件是笔者以太坊正式网络中的账户信息,用到的测试网络的账户信息在testnet目录下,读者可以通过菜单中的:账户→备份→账户来打开此目录。图2.10 Ethereum Wallet界面图2.11 Ethereum Wallet 创建账户图2.12 公钥、私钥密文等信息的存储位置2.2.2 部署合约

账户创建完成之后,账户余额是0,但是部署合约是需要消耗GAS的,因此需要获取一定量的以太币才能够继续操作。可以通过挖矿模式在测试网中获取以太币,在开发菜单中可以选择打开挖矿模式,但是这需要将Syn Mode模式修改为Full模式,这意味着需要将测试网中的全部区块数据同步下来才可以进行挖矿,这些数据非常大且同步缓慢,因此建议采用第2种方案:到以太坊基金会申请以太币,读者可以自行搜索“获取测试网以太币”,在相关的网址可以获取以太币,一般需要输入读者自己的测试环境下的以太坊账户。(1)本节使用的代码案例需要2个以上的以太坊账户,请读者申

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载