1.2 以太坊智能合约
在讲解了区块链的基本知识之后,我们聚焦于以太坊这条公链的独有特征,除了拥有上述区块链的基本功能,以太坊还在比特币网络的基础上增加了以太坊智能合约虚拟机,即EVM。在增加了EVM之后,以太坊就成了一个可编程的去中心化平台,任何系统开发者在支付一定的部署费用之后,就可以拥有一套完全去中心化的业务系统,这样的业务系统被称作DAPP,这是本书论述的主要内容。
1.2.1 以太坊
在前一节讲解区块链基本知识的基础上,本节简单讲解以太坊公链特有的公链特性。首先以太坊可以视作大型的状态机,由分布在全球的以太坊节点来运行这个状态机,每一次交易的产生都会修改状态机的状态。将交易打包成区块之后,可以以区块为单位来衡量状态的迁移,如图1.8所示。
图1.8 以太坊状态示意图
以太坊的状态是由以太坊上的所有账户组成的,也就是说,以太坊上的所有账户组成了以太坊的全局状态,以太坊账户是地址与账户状态的一个映射结构。以太坊账户又分为两种:一种是外部账户,也就是用于存放用户余额和转账的账户;另一种是智能合约账户,是在部署智能合约时生成的一个关于该智能合约的区块链地址及其状态的映射关系。
以太坊全局状态及账户结构如图1.9所示。账户1是智能合约账户,其地址映射到一个账户状态,这个状态由4部分组成:nonce、余额、存储数据的Hash值、EVM代码的Hash值。存储空间是智能合约虚拟机运行时需要的存储介质,EVM代码是智能合约代码编译并部署到以太坊之后的数据。账户2是外部账户,该账户的状态仅仅由nonce和余额组成。以太坊账户由这两类账户组成,所有这些账户组成了以太坊的全局状态。
图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++语言的版本。
智能合约在区块链上的可执行代码是一种类似汇编语言的指令集,这些指令集通过EVM的解释和执行,对区块链的状态进行读写,实现合约规定的业务逻辑。因此通过Solidity这种高级编程语言,加上Solidity编译器,可以将高级语言编译成汇编指令代码,再将其部署到区块链上执行。
图1.13 EVM的系统架构
图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操作的执行结果。