区块链项目开发指南.pdf
http://www.100md.com
2020年1月6日
![]() |
| 第1页 |
![]() |
| 第9页 |
![]() |
| 第14页 |
![]() |
| 第30页 |
![]() |
| 第35页 |
![]() |
| 第313页 |
参见附件(8088KB,329页)。
区块链项目开发指南,这是一本为读者阐述区块链概念的书籍,读者将在这里学习到如何创建端到端的区块链应用,同时介绍加密货币中的密码学、以太币安全、挖矿、智能合约和Solidity等概念。

区块链项目开发指南介绍
区块链是近十年来颇具颠覆性的新兴信息技术之一,它正以一种全新的方式建立人类交易过程的信任、仲裁和记录基础。本书共9章,首先介绍去中心化应用、DApp等基本概念,并据此展开对比特币、以太坊、超级账本等热门DApp的阐释。其次在解析以太坊工作原理的基础上介绍智能合约的编写方法,并介绍web3.js的应用方法。后利用上述知识进行钱包服务、智能合约部署平台、投注App、企业级智能合约以及联盟区块链等具体应用程序的创建。
区块链项目开发指南特点
“学生导向”,跟着这本书可以由浅及深地学习以太坊技术应用。
给出了多个真实的以太坊智能合约编写示例,可帮助初学者迅速上手编写代码。
通俗易懂,讲解细致,方便自学。
区块链项目开发指南目录
第1章 去中心化应用
第2章 以太坊的工作原理
第3章 编写智能合约
第4章 开始使用web3.js
第5章 创建钱包服务
第6章 创建智能合约部署平台
第7章 创建投注App
第8章 创建企业级智能合约
第9章 创建联盟区块链
区块链项目开发指南章节概述
第1章阐释DApp的概念,并简述其工作原理。
第2章阐释以太坊的工作原理。
第3章阐释如何编写智能合约和使用geth交互接口来部署合约,以及使用web3.js广播交易。
第4章介绍web.3js的概念及其导入方法、连接到geth的方法,并阐释了如何在node.js或者客户端JavaScript使用它。
第5章阐释如何创建钱包服务,以方便用户创建和管理以太坊钱包,甚至离线创建和管理钱包。我们将专门使用LightWallet库实现。
第6章展示如何使用web3.js编译智能合约,以及使用web3.js和EthereumJS部署智能合约。
第7章阐释如何使用Oraclize从以太坊智能合约发出HTTP请求,以访问万维网中的数据。我们还将学习访问存储在IPFS中的文件、使用字符串库处理字符串等方法。
第8章阐释如何使用truffle。truffle将使创建企业级DApp变得容易。我们将通过创建代币来学习truffle。
第9章阐释创建联盟区块链的方法。
区块链项目开发指南截图


区块链技术丛书
区块链项目开发指南
Building Blockchain Projects:Develop Real-time Practical DApps Using
Ethereum and JavaScript
(印度)纳拉扬·普鲁斯蒂(Narayan Prusty) 著
朱轩彤 闫莺 董宁 译
ISBN:978-7-111-58400-1
本书纸版由机械工业出版社于2017年出版,电子版由华章分社(北京华章图文信息有限公
司,北京奥维博世图书发行有限公司)全球范围内制作与发行。
版权所有,侵权必究
客服热线:+ 86-10-68995265
客服信箱:service@bbbvip.com
官方网址:www.hzmedia.com.cn
新浪微博 @华章数媒
微信公众号 华章电子书(微信号:hzebook)目录 译者序
前言
第1章 去中心化应用
1.1 什么是DApp
1.1.1 去中心化应用的优点
1.1.2 去中心化应用的缺点
1.2 去中心化自治组织
1.3 DApp中的用户身份
1.4 DApp中的用户账户
1.5 访问中心化应用
1.6 DApp中的内部货币
1.7 什么是授权的DApp
1.8 热门的DApp
1.8.1 比特币
1.8.2 以太坊
1.8.3 超级账本项目
1.8.4 IPFS
1.8.5 Namecoin
1.8.6 达世币
1.8.7 BigChainDB
1.8.8 OpenBazaar
1.8.9 Ripple
1.9 总结
第2章 以太坊的工作原理
2.1 以太坊概览
2.2 以太坊账户
2.3 交易
2.4 共识
2.5 时间戳
2.6 随机数
2.7 区块时间
2.8 分叉
2.9 创世区块
2.10 以太币面值
2.11 以太坊虚拟机
2.12 gas
2.13 发现对等节点
2.14 Whisper和Swarm
2.15 geth
2.15.1 安装geth
2.15.2 JSON-RPC和JavaScript操作台2.15.3 子命令和选项
2.15.4 创建账户
2.16 以太坊钱包
2.17 浏览器钱包
2.18 以太坊的缺点
2.19 serenity
2.20 总结
第3章 编写智能合约
3.1 Solidity源文件
3.2 智能合约的结构
3.3 数据位置
3.4 什么是不同的数据类型
3.4.1 数组类型
3.4.2 字符串类型
3.4.3 结构类型
3.4.4 枚举类型
3.4.5 mapping类型
3.4.6 delete操作符
3.4.7 基本类型之间的转换
3.4.8 使用var
3.5 控制结构
3.6 用new操作符创建合约
3.7 异常
3.8 外部函数调用
3.9 合约功能
3.9.1 可见性
3.9.2 函数修改器
3.9.3 回退函数
3.9.4 继承
3.10 库
3.11 返回多值
3.12 导入其他Solidity源文件
3.13 全局可用变量
3.13.1 区块和交易属性
3.13.2 地址类型相关
3.13.3 合约相关
3.14 以太币单位
3.15 存在、真实性和所有权合约的证明
3.16 编译和部署合约
3.17 总结
第4章 开始使用web3.js
4.1 web3.js概述
4.1.1 导入web3.js4.1.2 连接至节点
4.1.3 API结构
4.1.4 BigNumber.js
4.1.5 单位转换
4.1.6 检索gas价格、余额和交易细节
4.1.7 发送以太币
4.1.8 处理合约
4.1.9 检索和监听合约事件
4.2 为所有权合约创建客户端
4.2.1 项目结构
4.2.2 创建后端
4.2.3 创建前端
4.2.4 测试客户端
4.3 总结
第5章 创建钱包服务
5.1 在线钱包和离线钱包的区别
5.2 Hook ed-Web3-Provider和EthereumJS-tx库
5.3 分层确定性钱包
5.4 密钥衍生函数
5.5 LightWallet
5.6 创建钱包服务
5.6.1 必要条件
5.6.2 项目结构
5.6.3 创建后端
5.6.4 创建前端
5.6.5 测试
5.7 总结
第6章 创建智能合约部署平台
6.1 计算一个地址的交易nonce
6.2 solcjs概述
6.2.1 安装solcjs
6.2.2 solcjs API
6.3 创建合约部署平台
6.3.1 项目结构
6.3.2 创建后端
6.3.3 创建前端
6.3.4 测试
6.4 总结
第7章 创建投注App
7.1 Oraclize概述
7.1.1 Oraclize的工作原理
7.1.2 数据源
7.1.3 真实性证明7.1.4 定价
7.1.5 开始使用Oraclize API
7.1.6 加密查询
7.1.7 Oraclize Web IDE
7.2 处理字符串
7.3 创建投注合约
7.4 为投注合约创建客户端
7.4.1 项目结构
7.4.2 创建后端
7.4.3 创建前端
7.4.4 测试客户端
7.5 总结
第8章 创建企业级智能合约
8.1 探索ethereumjs-testrpc
8.1.1 安装和使用
8.1.2 可用RPC方法
8.2 什么是事件主题
8.3 开始使用truffle-contract
8.3.1 安装和导入truffle-contract
8.3.2 建立测试环境
8.3.3 truffle-contract API
8.4 truffle概述
8.4.1 安装truffle
8.4.2 初始化truffle
8.4.3 编译合约
8.4.4 配置文件
8.4.5 部署合约
8.4.6 单元测试合约
8.4.7 包管理
8.4.8 使用truffle的操作台
8.4.9 在truffle环境中运行外部脚本
8.4.10 truffle的创建管线
8.4.11 truffle的服务器端
8.5 总结
第9章 创建联盟区块链
9.1 什么是联盟区块链
9.2 什么是权威证明共识
9.3 parity概述
9.3.1 Aura的工作原理
9.3.2 运行parity
9.3.3 创建私有网络
9.3.4 许可和隐私
9.4 总结译者序
从去年开始,“区块链”成了一个高热度的关键字,受到各行各业的关注。越来越多的人
渴望用区块链这一变革性技术来解决商业中的关键问题。自然,有更多的人渴望深入了解和
运用区块链技术甚至开发自己的区块链应用。最近一年多,总是有很多朋友和学生问
我:“如何学习以太坊?有什么资料推荐吗?”通常我的回答就是:“从以太坊白皮书和黄皮
书看起吧。”显然,仅仅精读两篇文章是不够的,要想对区块链有进一步的认识,还需要更
多的知识储备。但是目前国内很难找到一本系统介绍区块链技术和开发平台的书籍。作为在
区块链领域具有较高知名度和丰富从业经验的专家团队,我们非常希望能给大家提供一套系
统的学习资料。
作为这场区块链技术热潮的弄潮儿,以太坊是最先进区块链技术的代表。以太坊的社区
和开发工具都相对比较完善和活跃。正因如此,很多企业级区块链解决方案都在积极地拥抱
以太坊。但是很遗憾,系统介绍以太坊的中文资料非常匮乏。首次接触到《Building
Blockchain Projects》这本英文书后,我们便感觉它是关于以太坊在广度上难得的技术资料,于是想尽快呈现给国内的读者。在翻译过程中,我们在保证表述流畅的同时,对原著的内容
进行了验证,并对其中的错误进行了修正。因此,本书应该比英文原版书更加易懂和准确。
在本书中,读者将了解如何编写智能合约、如何用JavaScript开发以太坊程序,以及如何
为区块链创建端到端应用。
本书具有如下特点:
·“学生导向”,跟着这本书可以由浅及深地学习以太坊技术应用。
·给出了多个真实的以太坊智能合约编写示例,可帮助初学者迅速上手编写代码。
·通俗易懂,讲解细致,方便自学。
在翻译本书的同时,我们的团队没有停止前进的脚步。我们不断努力,以求在技术深度
上更进一步。读者掌握本书的内容后,可以阅读我们即将于近期出版的其他关于以太坊和
Hyperledger的书,以加深对区块链的关键技术的认识。详情请见我们的微信公众号“智链
ChainNova”。前言
区块链是一个防篡改的去中心化账本,其中包含不断增长的数据记录列表。每个用户都
可以连接到网络,发送新的交易、验证交易和创建新的区块。
本书将阐释区块链的概念,讲述其如何保证数据真实性,以及如何使用以太坊创建现实
世界的区块链项目。通过有趣的现实世界案例,读者将了解如何编写完全按照程序运行、没
有欺诈、没有中心机构或者第三方干预的智能合约,并学习如何创建端到端的区块链应用。
本书还将介绍加密货币中的密码学、以太币安全、挖矿、智能合约和Solidity等概念。
区块链是比特币中最有创造性的技术,是记录比特币交易的公共账本。
本书内容
第1章阐释DApp的概念,并简述其工作原理。
第2章阐释以太坊的工作原理。
第3章阐释如何编写智能合约和使用geth交互接口来部署合约,以及使用web3.js广播交
易。
第4章介绍web.3js的概念及其导入方法、连接到geth的方法,并阐释了如何在node.js或
者客户端JavaScript使用它。
第5章阐释如何创建钱包服务,以方便用户创建和管理以太坊钱包,甚至离线创建和管
理钱包。我们将专门使用LightWallet库实现。
第6章展示如何使用web3.js编译智能合约,以及使用web3.js和EthereumJS部署智能合
约。
第7章阐释如何使用Oraclize从以太坊智能合约发出HTTP请求,以访问万维网中的数
据。我们还将学习访问存储在IPFS中的文件、使用字符串库处理字符串等方法。
第8章阐释如何使用truffle。truffle将使创建企业级DApp变得容易。我们将通过创建代币
来学习truffle。
第9章阐释创建联盟区块链的方法。
设备环境
Windows 7 SP1+、Windows 8、Windows 10或者Mac OS X 10.8+。
读者对象
本书适合想使用区块链和以太坊创建防篡改数据(和交易)应用的JavaScript开发人员阅读,也适合对密码学及其逻辑以及相关数据库感兴趣的人阅读。
下载实例代码
可以从http:www.packtpub.com 下载本书的实例代码文件。如果您在其他地方购买了本
书,可以访问http:www.hzbook.com 注册并下载。第1章 去中心化应用
我们以前用过的所有互联网应用几乎都是中心化的,即每个应用的服务端由一个特定企
业或个人所有。长期以来,开发人员创建中心化应用,用户使用中心化应用。但是中心化应
用存在一些问题,包括不透明、有单点故障、不能防止网络审查等,导致几乎不可能创建某
些特定类型的应用。为了解决这些问题,一项新的技术诞生了,它创建以网络为基础的去中
心化应用(DApp) 。在本章中,我们将学习去中心化应用。
在本章中,我们将讲解以下内容:
·什么是DApp。
·去中心化、中心化和分布式应用之间的区别。
·中心化和去中心化应用的优点和缺点。
·概述一些最热门的DApp所使用的数据结构、算法和协议。
·学习一些创建在其他DApp之上的流行DApp。1.1 什么是DApp
DApp是一种互联网应用,其后端在去中心化的点对点网络上运行,且其源代码是开源
的。网络中不存在能够完全控制DApp的节点。
根据DApp的功能不同,使用不同的数据结构来存储应用数据。例如,比特币DApp使用
区块链数据结构。
这些对等节点(peer)可以是网络中的任何计算节点,因此,发现和防止节点对应用数
据进行非法篡改或者与其他人分享错误信息是一个重要挑战,所以需要对等节点之间有一些
关于某个节点发布的数据是否正确的共识。在DApp中,没有一个中心服务器来协调节点,或者决定什么是对、什么是错,因此应对这个挑战确实不容易。一致性协议(concensus
protocol)可用于解决这个问题。不同的DApp通常使用不同数据结构类型的共识协议,例如
比特币使用工作量证明协议(PoW)来达成共识。
为了让用户使用DApp,每一个DApp都需要一个客户端(client)。使用DApp时,用户首先需要运行DApp中自己的节点服务端,然后将客户端连接至节点服务端。DApp的节
点只提供应用程序编程接口(Application Programming Interface,API),并允许开发者社
区使用API开发多种客户端。一些DApp开发人员会提供一个官方的客户端。DApp客户端应
该是开源的,并可以被下载使用,否则整个去中心化的想法就失败了。
但是建立客户端架构比较麻烦,如果用户不是开发人员,就更麻烦。因此,客户端通常
作为服务和或节点形式出现,以便让使用DApp的过程更容易。
什么是分布式应用?
分布式应用是指应用分布在多个服务端上,而非只有一个服务端。当应用数据和通信量
变得巨大,且应用的停机时间难以承受时,分布式是必要的。在分布式应用中,数据在多个
服务端中备份,以具有较高可用性。中心化应用可能是分布式的,也可能不是分布式的,但
去中心化应用肯定是分布式的。例如Google、Facebook、Slack、DropBox等是分布式的,而
简单的投资组合网站或者个人微博通常不是分布式的,除非通信量很大。1.1.1 去中心化应用的优点
中心化应用的一些优点如下:
·DApp能容错,没有单点故障,因为它们默认是分布式的。
·防止某单一机构的干扰。因为没有一个中心机构,任何第三方机构无法向中心机构施压
逼迫其删除一些内容。甚至没有单一机构能关闭应用的域名或者IP地址,因为DApp不是通
过一个特定的IP地址或者域名访问的。或许某些机构可以通过IP地址追踪网络中的单个节点
并关闭它,但是如果网络很庞大,则几乎不可能关闭应用。
·用户容易相信该应用。因为它不是由某个通过欺骗用户来牟利的机构所控制的。1.1.2 去中心化应用的缺点
显然,每个系统都不是完美的。去中心化应用的一些缺点如下:
·修改bug或者更新DApp很困难,因为网络中的每一个节点都需要更新其节点软件。
·一些应用要求验证用户身份(即KYC),却没有中心化的机构来验证用户身份,开发
应用时会遇到问题。
·创建去中心化应用比较困难,因为它们应用复杂的协议达成共识,且必须从最开始就自
行创建并扩大规模。所以我们不能仅仅实现一个想法,然后不断添加功能,使其规模扩大。
·应用通常独立于第三方API,以获取或者存储数据。DApp不能依赖中心化应用API,但
是可以依赖其他DApp。因为目前DApp的生态圈还不太大,所以创建起来比较困难。尽管
DApp理论上可以依赖其他DApp,但在实践中紧密融合DApp仍比较困难。1.2 去中心化自治组织
一般来说,被签署的文件可以代表组织,而且政府能对它们产生影响。根据组织类型的
不同,组织可能有股东,也可能没有股东。
去中心化自治组织 (Decentralized Autonomous Organization,DAO)是由计算机程序代
表的组织(即组织根据程序中写明的规则运行),完全透明,完全由股东控制,不受政府影
响。
为了达到这些目标,我们需要把DAO作为DApp来开发。因此,我们可以说DAO是
DApp的一个子类。
Dash和DAC是DAO的一些例子。
什么是去中心化自治公司(DAC)?
DAC和DAO尚无很明显的差别。很多人认为它们是一样的,有些人则在DAO为股东谋
取利益时将DAC定义为DAO。1.3 DApp中的用户身份
DApp的主要优点之一是它一般能保证用户的匿名性,但是许多应用要求用户必须经过
身份验证这个过程才能使用应用。因为DApp中没有中央机构,验证用户身份成了一个挑
战。
在中心化应用中,人们要求用户提交特定的扫描文件、OTP验证等,再验证用户身份。
该过程称为Know Your Customer(KYC)。但是由于DApp中没有人负责验证用户身份,所
以DApp不得不自己验证用户身份。DApp显然不能理解和验证扫描文档,也不能发送短信,因此需要用户提供那些它们可以理解和验证的数字标识。主要问题是几乎没有DApp有数字
身份,只有少数人知道如何得到数字身份。
数字身份有多种形式。目前最受推崇、最热门的形式就是数字证书。数字证书(也称为
公钥证书或者标识证书)是一个用来证明公钥所有权的电子文档。基本上,一个用户拥有私
钥(private k ey)、公钥(public key)和数字证书(digital certificate)。私钥是秘密的,用
户不应当与其他人分享;公钥可以与其他人分享;数字证书包含公钥和谁拥有公钥的信息。
显然,生产这种证书并不难,因此数字证书总是由用户可以信任的授权机关颁发。数字证书
有一个加密部分是用证书颁发机构(certificate authority)的私钥加密的。为了验证证书的真
实性,我们只需要使用证书颁发机构的公钥解码该部分,如果成功解码,那么证书就是合法
的。
即使用户成功获得了数字身份并得到DApp验证,还是有一个关键问题,那就是有各种
各样的数字证书颁发机构。为了验证一个数字证书,我们需要该证书颁发机构的公钥。掌握
所有证书颁发机构的公钥、更新添加新的证书颁发机构的公钥是很困难的。因此,数字身份
验证程序通常在客户端里,这样可以方便更新。但仅把验证程序转移到客户端并不能彻底解
决问题,因为有很多颁发数字证书的机构,跟踪所有机构并把它们加到客户端是很麻烦的。
为什么用户不验证彼此的身份?
在现实生活中,用户做交易时,通常会自己验证对方的身份或者可以请一个机构来验证
身份,这个想法也可以应用到DApp中。在进行交易之前,用户可以手动验证彼此的身份,这个想法适用于人们彼此进行交易的DApp。假设有一个DApp是去中心化的社交网络,显然
可以通过这种方式验证身份信息。假设一个DApp是用来买卖商品的,在支付之前买卖双方
可以验证彼此的身份,尽管这个想法看起来是可行的,但是在实践中很难实现,因为你可能
并不想每次进行交易的时候都验证身份,也不是每个人都知道如何验证身份。例如,假设有
一个DApp是打车软件,用户显然不想每次叫出租车之前都进行身份验证。但如果只是偶尔
交易,也知道怎样验证身份,就可以按程序验证身份。
由于这些原因,我们目前剩下的可选择方案是,由提供客户端的公司派人手动验证用户
身份。例如,创建一个比特币账户不需要身份证明,但是当提取比特币并兑换成货币时,交
易所会要求提供身份证明。客户端可以忽略未经验证的用户,不让他们使用,也可以对已经
身份验证的用户开放使用。这个解决办法也会产生一些小问题,即如果转换客户端,会发现交互的用户不一样了,因为不同的客户端有不同的验证用户集。因此,所有用户可能决定只
使用一个特定客户端。但这不是一个很大的问题,因为如果客户端不能有效验证用户,用户
就可以方便地转向另一个客户端,而且不丢失关键数据,因为关键数据的存储是去中心化
的。
在应用中验证用户身份的想法是,使用户在进行一些欺诈行为之后难以逃脱,防
止有欺诈犯罪背景的用户使用应用,以及为网络中的其他用户提供相信某个用户就是他自称
的人的方法。用什么过程来验证用户身份并不重要,用户总有办法伪装成其他人;用数字身
份或者用扫描文件进行验证并不重要,因为二者都可能被盗或者被重复使用。重要的是让用
户难以伪装成其他人,并收集足够的数据追踪用户,证明该用户进行了一些欺诈行为。1.4 DApp中的用户账户
许多应用需要用户账户功能。与账户相关的数据只能由账户所有者进行修改。DApp和
中心化应用不一样,DApp没有以用户名和以密码为基础的账户功能,因为密码不能证明账
户中的数据变化是由账户所有者发出的请求导致的。
有多种方法能实现DApp中的用户账户,最热门的方式是使用公钥-私钥对(public-
private k ey pair)来代表一个账户。公钥的哈希(hash)是账户的唯一身份。为了改变账户中
的数据,用户需要用私钥签名。我们假设用户会安全地存储私钥。如果用户丢失私钥,就永
远不能访问账户了。1.5 访问中心化应用
DApp不能依赖于中心化应用,原因是存在单点故障。但是在一些情况下,并无其他办
法。例如,如果DApp想读取一场足球比赛的成绩,它从哪里得到数据呢?尽管DApp可以依
赖另一个DApp,但是国际足联(FIF A)为什么要创建一个DApp呢?国际足联不会仅仅因为
其他DApp想要数据,就创建一个提供成绩却没有回报的DApp。
所以在一些情况下,DApp需要从中心化应用中抓取数据。但主要问题是DApp如何知道
从一个域名中抓取的数据有没有被中间人篡改,数据是否还是真实的响应?根据DApp架构
的不同,解决办法也有所不同。例如在以太坊中,智能合约不能直接发出HTTP请求,为了
访问中心化API,可以使用Oraclize服务作为中间人。Oraclize为从中心化服务智能合约中抓
取的数据提供TLSNotary验证。1.6 DApp中的内部货币
中心化应用的所有者需要有盈利才能长期维护应用的运行。DApp虽然没有所有者,但
是和中心化应用一样,DApp节点需要硬件和网络资源才能维持运行。DApp节点需要一些有
用的回报来维持运行,于是内部货币登场了。大多数DApp都有内置内部货币,或者可以说
最成功的DApp都有内置内部货币。
共识协议决定节点收取多少内部货币。根据共识协议,只有为维护DApp安全和运行做
出贡献的那些特定节点可以赚取货币,只进行数据读取的节点没有回报。例如在比特币中,只有矿工(miner)成功挖矿才能赚取比特币。
最大的问题是,这是一种数字货币,为什么人们觉得它有价值?根据经济学原理,有供
需差就有价值。
让用户用内部货币付费才能使用DApp解决了需求问题。随着越来越多的用户使用
DApp,且需求不断增长,内部货币的价值也升高了。
货币总量恒定会使货币变得稀缺,从而使其价值更高。
货币是不断供应的,而非一次性供应所有货币。正因如此,新进入网络、使网络安全运
行的节点也能赚取货币。
DApp中内部货币的缺点
DApp有内部货币的唯一缺点是,DApp不能再免费使用了。免费是中心化应用占上风的
原因之一,因为中心化应用可以用广告赚钱,为第三方应用提供优质API,所以可以对用户
免费。
在DApp中不能加入广告,因为没有人去检查广告尺度;客户端还可能不展示广告,因
为展示广告对他们没有好处。1.7 什么是授权的DApp
到目前为止,我们学习了完全开放的免权限DApp,即任何人都不需要建立身份就可以
参与。
另一方面,授权的DApp并不对所有人开放。授权的DApp继承了免权限DApp的全部属
性,但需要权限才能参与到网络中去。各种授权的DApp用到的权限系统不同。
要加入一个授权的Dapp就需要权限,免权限DApp的共识协议可能在授权的DApp中并不
好用,因此授权的Dapp与免权限Dapp的共识协议是不同的。授权的DApp没有内部货币。1.8 热门的DApp
现在我们已经掌握了一些关于DApp是什么、它与中心化应用有何区别等知识,让我们
探索一些热门的、有用的DApp。学习这些DApp时,我们只要达到理解其工作原理和它们如
何处理不同问题的程度就够了,不用学得太深。1.8.1 比特币
比特币(bitcoin)是一种去中心化的货币,是最热门的DApp。它的成功展示了Dapp有
多么强大,并鼓励人们创建其他DApp。在了解比特币的细节以及为什么人们认为它是一种
货币之前,需要账本和区块链的概念。
1.什么是账本
账本(ledger)本质上是一个交易列表。数据库与账本不同。在账本中,我们只能添加
新的交易;在数据库中,我们可以添加、修改和删除交易。数据库可以用来实现账本。
2.什么是区块链
区块链(block chain)是用于创建去中心化账本的数据结构。区块链中的区块按序号排
列。区块包含一系列交易、前一个区块的哈希(hash)、时间戳(timestamp,表明区块的创
建时间)、区块回报(block reward)、区块序号(block number)等。每一个区块包含前一
个区块的哈希,由此创建了区块彼此相连的链。网络中的每一个节点都保留区块链的一个备
份。
工作量证明(proof-of-work)和权益证明(proof-of-stak e)等是用于保障区块链安全性
的多种共识协议。由于共识协议不同,创建区块和添加区块到区块链中的方式也不同。在工
作量证明中,通过挖矿创建区块,这让区块链保持安全。在工作量证明协议中,挖矿涉及解
决复杂问题。我们将在后面学习更多关于区块链及其共识协议的内容。
比特币网络中的区块链包含比特币交易。网络向成功挖出区块的节点奖励新的比特币。
区块链数据结构的主要优点是,它自动进行审计,并使应用安全透明,可以防止
欺诈和贪污。根据实现和使用方式的不同,它还可以用来解决许多其他问题。
3.比特币合法吗
首先,比特币不是一种内部货币,而是一种去中心化的货币。内部货币大部分都是合法
的,因为它们有资产且用途明确。
主要问题在于纯货币DApp是否合法。简要回答就是,许多国家认为它是合法的,少数
国家认为它是非法的,大部分国家对此还没有做出决定。
为什么少数国家认定它是非法的,大部分国家对此还没有做出决定呢?原因如下:
·由于DApp中的标识问题,用户账户没有任何标识将它们与比特币挂钩,因此,它可用
于洗钱。
·这些虚拟货币不稳定,所以人们丢钱的风险很高。·用虚拟货币很容易逃税。
4.为什么使用比特币
比特币网络仅用于发送接收比特币,没有其他用途。所以你一定在奇怪,人们为什么对
比特币有需求?
使用比特币的原因如下:
·可以在世界上任何地方快速便捷地发送和接收支付。
·比特币交易费低于在线支付交易费。
·黑客可以从商户那里窃取支付信息,但是在使用比特币的情况下,窃取比特币地址是完
全没用的,因为为了让交易合法,必须用相关私钥签名,而用户在支付时不需要和任何人分
享私钥。1.8.2 以太坊
以太坊(ethereum)是一个去中心化平台,可以在其上运行使用智能合约编写的
DApp。一个或多个智能合约可以一起构建DApp。以太坊智能合约是在以太坊上运行的程
序。智能合约完全按照程序运行,杜绝了停机、中心化操控、欺诈和第三方干涉的可能性。
使用以太坊运行智能合约的主要优点是方便智能合约彼此交互,而且不需要担心整合共
识协议等事情,只需编写应用所需逻辑即可。当然,不能用以太坊创建所有种类的DApp,只能创建以太坊支持其功能的那些DApp。
以太坊有一种内部货币叫作以太币(ether)。部署智能合约或者执行智能合约函数需要
用到以太币。
本书将使用以太坊创建DApp,并深入介绍以太坊的相关知识。1.8.3 超级账本项目
超级账本(Hy perledger)项目致力于开发创建授权的DApp技术。Hy perledger
fabric(或称simply fabric)是Hy perledger项目的一个实现。其他Hy perledger实现还有Intel
Sawtooth和R3 Corda等。
fabric是一个去中心化的授权平台,它允许在其上运行授权的DApp(叫作chaincode,账
链代码)。用户需要部署自己的fabric实例,然后在其上部署授权的DApp。网络中的每一个
节点都运行一个fabric实例。fabric是即插即用系统,可以方便地即插即用多种共识协议和功
能。
Hyperledger使用区块链数据结构。以Hy perledger为基础的区块链目前可以选择没有共
识协议(即NoOps协议),或者使用实用拜占庭容错算法(Practical Byzantine Fault
Tolerance,PBFT)共识协议。它有一个特殊节点叫作证书颁发机构,该节点用于控制谁能
加入网络和它们能做什么。1.8.4 IPFS
星际文件存储系统(InterPlanetary File System,IPFS)是一个去中心化的文件系统。
IPFS使用分布式哈希表(Distributed Hash Table,DHT)和Merk le有向无环图(Directed
Acyclic Graph,DAG)数据结构。它使用类似于BitTorrent(比特流)的协议来决定如何在
网络中移动数据。IPFS的一个高级功能是它支持文件版本管理。为了实现文件版本管理,它
使用了类似于Git的数据结构。
尽管被称为去中心化的文件系统,IPFS并不遵循文件系统的主要属性,即在文件系统
中,所存储的内容会一直保留到被删除之前。IPFS的工作原理不同——每一个节点并不存储
全部文件,存储的是需要的文件。如果一个文件不那么受欢迎,许多节点就没有这个文件,那么该文件很有可能从网络中消失。因此,许多人更喜欢把IPFS称为去中心化的、点对点的
文件共享应用。或者可以把IPFS当作完全去中心化的BitTorrent,也就是说,它没有追踪器,但有一些高级功能。
1.工作原理
当在IPFS中存储一个文件时,它被分成很多小于256KB的数据块(chunk),并生成每个
数据块的哈希。网络中的节点在一个哈希表中存储它们需要的IPFS文件及其哈希。
IPFS文件有4种类型:blob、list、tree和commit。blob代表一个实际存储在IPFS中的文件
的数据块。list代表完整的文件,因为它包含blob列表和其他列表。由于列表可以包含其他列
表,因此它帮助网络进行数据压缩。tree(树)代表目录,因为它包含blob列表、列表、其他
树和commit。commit文件代表其他文件的版本历史中的快照。由于list、tree和commit与其
他IPFS文件有连接,于是形成了一个Merk le DAG。
所以,用户如果想要从网络中下载文件时,只需要IPFS列表文件的哈希。如果想下载目
录,则只需要IPFS树文件的哈希。
因为每个文件都由一个哈希进行标识,所以文件名不容易记住。如果更新文件,就需要
与想下载该文件的所有人分享新的哈希。为了解决这个问题,IPFS使用IPNS功能,允许用自
行认证的名字或者人性化的名字指向IPFS文件。
2.Filecoin
阻碍IPFS成为去中心化文件系统的主要原因是节点只存储了它们需要的文件。
Filecoin(文档币)是一个类似于IPFS的去中心化文件系统,其中有内部货币激励节点存储文
件,由此提高文件可用性,并使其更像一个文件系统。
网络中的节点通过赚取文档币来租用磁盘空间,在存储检索文件时,需要花费文档币。
与IPFS技术一样,Filecoin使用区块链数据结构和数据可检索证明(Proof-of-
Retrievability,PoR)共识协议。在写本书之时,Filecoin仍在开发阶段,因此许多事情尚不明确。1.8.5 Namecoin
Namecoin是一个去中心化的键-值数据库。它也有内部货币,叫作域名币
(Namecoin)。Namecoin使用区块链数据结构和工作量证明共识协议。
在Namecoin中,可以存储数据的键-值对。为了注册键-值对,需要花费域名币。注册之
后,需要每35999个区块更新一次,否则与密钥相关的数值将失效。更新也需要花费域名
币。不需要更新密钥,也就是说,在注册之后不需要花费任何域名币来存储密钥。
Namecoin有一个命名空间(namespace)功能,允许用户组织不同种类的密钥。任何人
都可以创建命名空间,或者使用现有命名空间组织密钥。
最受欢迎的命名空间有a(应用特定数据)、d(域名规范)、ds(安全域名)、id(标
识)、is(安全标识)、p(产品)等。
bit域名
如要访问网站,浏览器应先发现与域名相关的IP地址。这些域名和IP地址映射被存储在
DNS服务端中,受大机构控制。因此,域名易于审查。如果网站在做非法勾当,或者导致某
些损失,或者出于一些其他原因,大机构通常会关闭域名。
正因如此,就需要一个去中心化的域名数据库。因为Namecoin就像DNS服务端一样存储
键-值数据,所以Namecoin可用于实现去中心化的DNS,而且已经用于该用途。d和ds命名空
间包含以.bit结尾的密钥,代表.bit域名。从技术上看,命名空间对于密钥没有任何命名协
定,但是Namecoin的所有节点和客户端同意该命名协定。如果想在d和ds命名空间存储非法
的密钥,那么客户端会将其滤掉。
支持.bit域名的浏览器需要查看Namecoin的d和ds命名空间,以发现与.bit域名相关的IP地
址。
d和ds命名空间之间的区别是:ds存储支持TLS的域名,而d存储不支持TLS的域名。我们
已经使DNS去中心化了,也可以使发放TLS证书去中心化。
这是TLS在Namecoin中的工作原理。用户创建自签名的证书,并在Namecoin中存储证
书。当一个对.bit域名支持TLS的客户端试图访问一个安全的.bit域名时,它将服务端返回的证
书哈希和Namecoin中的哈希存储进行匹配,如果匹配,则继续与服务端进行更多通信。
使用Namecoin形成的去中心化DNS是第一个解决Zook o triangle的办法。Zooko
triangle定义了有三个属性的应用,即去中心化、身份和安全性。数字身份不仅可以用于代表
一个人,还可以代表一个域、一个公司或者其他事物。1.8.6 达世币
达世币(Dash)是一种类似于比特币的去中心化货币。它使用区块链数据结构和工作量
证明共识协议,并解决了比特币面临的一些主要问题。以下是比特币面临的一些问题:
·交易需要几分钟完成,但在目前的环境下通常需要交易马上完成。这是因为比特币网络
的挖矿难度不断调整,平均每10分钟创建一个区块。在本书中,我们将在后面学习更多关于
挖矿的内容。
·尽管账户没有与其相关的身份,但是在交易所里用比特币和真实货币进行兑换或者用比
特币买东西都是可以追溯的,因此交易所或者商户可以把用户的身份透露给监管机构。如果
在自己的节点上运行发送接收交易,则ISP可以看见比特币地址,还可以用IP地址追踪所有
者,因为在比特币网络中广播的信息不是加密的。
达世币的目标是通过使交易几乎瞬间完成并隐藏交易账户的信息来解决上述问题,还可
以防止他人用ISP追踪所有者。
比特币网络中有两种节点,即矿工节点和普通节点。但Dash中有三种节点,即矿工节点
(miner node)、主节点(master node)和普通节点(ordinary node)。主节点是使Dash与
众不同的原因。
1.去中心化的治理和预算编制
要建立一个主节点,用户需要拥有1000个达世币和一个静态IP地址。在Dash网络中,主
节点和矿工都赚取达世币。挖出一个区块,45%收益归矿工,45%收益归主节点,剩余的
10%留给系统预算。
主节点使去中心化的治理和预算编制成为可能。由于去中心化的治理和预算编制系统,Dash被称为DAO,因为这就是它的确切含义。
网络中的主节点就像股东,也就是说,它们有权利决定剩余10%的达世币归谁。这10%
的达世币通常用于资助其他项目。
每个主节点都有能力使用一次投票权(vote)批准项目。对项目的讨论在网络以外进
行,但投票是在网络中进行的。
主节点为在DApp中验证用户标识提供了一种可能的解决办法,也就是说,主节
点可以民主地选择节点来验证用户标识。该节点背后的人或者单位可以手动验证用户文档。
回报的一部分还可以回到这个节点。如果该节点不提供良好的服务,那么主节点可以投票给
另一个节点。对于解决去中心化的标识问题来说,这不失为一个好办法。
2.去中心化服务主节点还形成一个提供多种服务的服务层,而非仅仅批准或者拒绝一个提案。主节点提
供服务的原因是它们提供的服务越多,网络的功能就越多,从而增加用户和交易。这样能提
高达世币的价值,使区块回报变得更高,由此帮助主节点赚取更多利润。
主节点提供诸如PrivateSend(提供匿名的混合币服务)、InstantSend(提供几乎即时交
易的服务)、DAPI(供去中心化API的服务,这样用户不需要运行节点)等服务。
在某个特定时间,只有10个主节点被选中。选择算法将使用当前区块的哈希选择这10个
主节点。然后,从这些主节点发出服务请求。从大部分节点接收的结果被认为是正确的,这
就是对主节点提供的服务达成共识的办法。
服务证明(Proof of Service,PoS)共识协议用于确保主节点在线、应答和更新区块
链。1.8.7 BigChainDB
BigChainDB允许用户部署自己的、授权的或者免权限去中心化数据库。它使用区块链数
据结构以及其他多种特定数据库数据结构。在写本书之时,BigChainDB仍处于开发阶段,所
以许多事情尚不明确。
BigChainDB还提供了许多其他功能,例如丰富的权限、查询、线性扩展以及支持多资产
和federation共识协议等。1.8.8 OpenBazaar
OpenBazaar是一个去中心化的电子商务平台,可以在其上买卖物品。OpenBazaar中的用
户不是匿名的,因为其IP地址被记录了。节点可以是买方、卖方或者中间人。
OpenBazaar使用Kademlia分布式哈希表数据结构。为了使这些项在网络中可视,卖方必
须建立节点并维持其运行。
OpenBazaar使用工作量证明共识协议防止账户被篡改。它使用proof-of-burn、CHECKLOCKTIME验证和基于保证金的共识协议,防止评分和评价被篡改。
买方和卖方用比特币进行交易。买方在购买时可以添加一个中间人。如果买卖双方有争
端,由中间人负责解决。任何人都可以是网络中的中间人,中间人通过解决争端赚取手续
费。1.8.9 Ripple
Ripple(瑞波)是一个去中心化的转账平台。它允许兑现货币、数字货币和大宗商品。
它使用区块链数据结构,并且有自己的共识协议。在Ripple相关文档中,找不到“区块”和“区
块链”等词汇;而是用“账本”(ledger)来代替。
在Ripple中,通过信任链进行钱和商品交换,方式类似于hawala网络。Ripple中有两种节
点,即网关(gateway)和普通节点。网关支持一种或多种货币和或商品的存取。为了在
Ripple网络中变成网关,需要作为网关的权限形成一个信任链。网关通常是已经注册的金融
机构、交易所、商人等。
每个用户和网关都有一个账户地址。每个用户需要把他们信任的网关地址添加到信任列
表中,形成一个信任网关列表。对于发现谁值得信赖,并没有任何共识,这完全依赖用户
——用户自行承担信任一个网关的风险,即便网关可以添加它们信任的网关列表。
来看一个例子:住在印度的用户X如何能够向住在美国的用户Y发送500美元。假设在印
度有一个网关XX,收取现金(实物现金或者在网上用卡支付)且只用印度卢比给用户Ripple
余额。X将访问XX办公室或者网站,存入30000印度卢比,然后XX广播交易说“欠X30000印
度卢比”。现在假设在美国有一个网关YY,它只允许美元交易且Y信任YY网关。假设网关
XX和YY不信任彼此。由于X和Y不信任一个共同的网关,XX和YY不信任彼此,导致XX和
YY不支持同样的货币,因此,X为了转账给Y,就需要发现一个中间人网关形成一个信任
链。假设还有一个网关ZZ,XX和YY都信任ZZ,ZZ支持美元和印度卢比。现在X可以发送
交易,将50000印度卢比从XX转给ZZ,ZZ把钱兑成美元,然后把钱发送给YY,让YY把钱
给Y。现在X欠Y500,YY欠Y500,ZZ欠YY500,XX欠ZZ 30000印度卢比。但是这都没
什么,因为它们互相信任,而此前X和Y不互相信任。但是只要XX、YY和ZZ想,它们随时
可以在Ripple之外转账,或者翻转交易扣除款项。
Ripple也有内部货币,叫作XRP(或瑞波币)。发送至网络中的每一个交易会耗费一些
瑞波币。由于瑞波币是Ripple自有的货币,它可以不需要信任就被发送给网络中的任何人。
在形成信任链时,可以使用瑞波币。记住,每一个网关有自己的货币汇率。瑞波币不是由挖
矿生成的;相反,最初就有1000亿个瑞波币,它们最初由Ripple公司拥有。出于多种原因,瑞波币是手动供给的。
所有交易都被记录在去中心化的账本中,形成不可更改的历史。需要共识确保所有节点
在一个给定时间的账本都一致。在Ripple中,有第三种节点,叫作验证器(validator),它是
共识协议的一部分,验证器负责验证交易。任何人都可以成为验证器。但是其他节点维护一
个可以信任的验证器列表。该列表被称为唯一节点列表(Unique Node List,UNL)。验证器
也有UNL,即验证器信任的验证器,因为验证器也想达成共识。目前,由Ripple决定可以信
任的验证器列表,但是如果网络认为Ripple选择的验证器不值得信任,就可以在节点软件中
修改列表。
可以拿出一个以前的账本,把随后发生的全部交易都填上去,形成一个新账本。为了同
意当前账本,节点必须同意以前的账本和随后发生的全部交易。在创建一个新账本之后,节点(普通节点和验证器)启动一个计时器(几秒钟长,大概5s),并收集在创建以前的账本
时到达的新交易。当计时器停下时,它接收至少80%的UNL认为合法的交易,形成下一个账
本。验证器向网络广播一个提案(proposal,即它们认为合法的、用于形成下一个账本交易
的一系列交易)。如果它们决定根据UNL提案和其他因素改变合法交易的列表,验证器可以
对同一个账本用不同的交易集合,多次广播提案。所以用户仅需要等待5~10s,由网络确认
交易。
有人质疑,每个节点可能有不同的UNL,是否会使账本生成许多不同的版本?其实只要
UNL之间有最低程度的相互连接,就会迅速达成共识,这是因为每一个诚实节点的主要目标
就是达成共识。1.9 总结
在本章中,我们学习了DApp的概念,初步了解了DApp的工作原理以及其面临的一些挑
战和应对挑战的多种方法。最后,我们接触了一些广受欢迎的DApp,了解了它们的特别之
处和工作原理。第2章 以太坊的工作原理
在前一章中,我们了解了DApp的概念,还了解了一些热门DApp,其中之一便是以太
坊。目前,以太坊是继比特币之后最受欢迎的DApp。在本章中,我们将深入学习以太坊的
工作原理及其用途,还将看到重要的以太坊客户端和节点实现。
在本章中,我们将讲解以下内容:
·以太坊用户账户。
·智能合约及其工作原理。
·以太坊虚拟机(EVM)。
·在工作量证明共识协议中挖矿如何进行。
·学习如何使用geth命令。
·建立以太坊钱包和浏览器钱包(Mist)。
·Whisper和Swarm概览。
·以太坊的未来。2.1 以太坊概览
以太坊(Ethereum)是一个去中心化的平台,可以在其上部署DApp。DApp是用一个或
者更多个智能合约创建的,使用Solidity编程语言编写智能合约。智能合约完全按照程序运
行,而且防停机、防审查、防欺诈、防第三方干扰。在以太坊中,编写智能合约可以使用好
几种编程语言,包括Solidity、LLL和Serpent,其中Solidity最受欢迎。以太坊有一种内部货币
叫作以太币(Ether),部署智能合约或者调用其方法需要用到以太币。和任何其他DApp一
样,智能合约可以有多个实例,且每个实例都有自己专门的地址。用户账户和智能合约都可
以持有以太币。
以太坊使用区块链数据结构和工作量证明共识协议。智能合约可以通过发送交易调用或
者通过其他合约调用。有两种网络中的节点:普通节点和矿工。普通节点只备份区块链上的
数据,而矿工通过挖矿创建区块链。2.2 以太坊账户
要创建以太坊账户,只需要一个非对称加密密钥对——由不同的算法(例如RSA、ECC
等)生成。以太坊使用椭圆曲线加密算法(ECC),ECC有多个参数用来调节速度和安全
性,以太坊使用secp256k 1参数。深入学习ECC及其参数需要一定的数学知识,而使用以太坊
创建DApp不需要深入理解ECC及其参数。
以太坊使用256位加密。以太坊私钥公钥是一个256位数。因为处理器不能表示这么大的
数,所以它被编译成长度为64的十六进制字符串。
每个账户用一个地址表示。有了密钥之后,就需要生成地址。从公钥生成地址的过程如
下:
1)生成公钥的keccak-256哈希。它将给出一个256位的数字。
2)丢弃前面的96位,即12字节。现在得到160位二进制数据,即20字节。
3)把地址编译成十六进制的字符串。最后将得到一个40字符的字节串,就是账户地
址。
现在任何人都可以发送以太币到这个地址。2.3 交易
交易是一个签名数据包,用于从一个账户向另一个账户或者向一个合约转以太币、调用
合约方法或者部署一个新合约。交易使用椭圆曲线数字签名算法(ECDSA) 签名,ECDSA
是一种基于ECC的数字签名算法。交易包含信息接收者、识别发起人及其意愿的签名、要转
账的以太币数量、交易执行允许进行的计算资源最大值(叫作gas上限)以及交易发起人愿意
为单位计算资源支付的费用(叫作gas价格)。如果交易目的是调用合约方法,则还包含输入
数据;如果其目的是部署合约,则可以包含初始化代码。用交易所消耗的gas乘以gas价格计
算得到交易费。为了发送以太币或者执行合约方法,需要向网络广播交易。发起人需要用私
钥签署交易。
如果确定交易将永久地出现在区块链中,则称为交易已确认。推荐在假设交易已
确认之前,等待15个确认(15个区块产生在交易所在的区块后面)。2.4 共识
以太坊网络中的每个节点包含区块链的一个备份。用户需要确保节点不能够篡改区块
链,还需要一个机制检查区块是否合法,如果遇到两个不同的合法区块链,需要有办法确定
选择哪个。
以太坊使用工作量证明共识协议防止区块链被篡改。工作量证明系统需要解决一个复杂
问题以创建一个新的区块。解决问题需要大量算力,这就使创建区块很困难了。在工作量证
明系统中,创建区块的过程称为挖矿。矿工(miner)是网络中挖区块的节点。使用工作量
证明的所有DApp并不一定都使用同样的算法。使用什么算法取决于矿工需要解决的问题、问题难度值、需要多长时间解决等。我们将学习与以太坊有关的工作量证明。
任何人都可以成为网络中的矿工。每个矿工独自解决问题,第一个解决问题的矿工是胜
利者,它得到的回报是5个以太币和该区块中全部交易的交易费。如果你的处理器比网络中
的其他节点更强大,也并不意味着你总会成功,因为所有矿工要解决的问题的参数并不完全
相同。但是如果你有一台比网络中的其他节点都强大的处理器,成功的概率会比较大。网络
安全不是用矿工总数衡量的,而是用网络的全部算力衡量的。
区块链中有多少个区块没有限制,可以生成的以太币总数也没有限制。矿工一旦成功挖
到区块,就向网络中的所有其他节点广播该区块。区块有一个区块头(header)和一系列交
易。每一个区块存储前一个区块的哈希值,由此创建一个相连的链。
让我们来看矿工需要解决的问题是什么以及如何在高水平解决问题。为了挖区块,矿工
首先从收到的广播中收集新的、未挖出的交易,然后滤掉不合法的交易。合法的交易必须满
足正确地使用私钥签名、账户有足够的余额进行交易等条件。现在矿工创建一个有区块头和
内容的区块。内容(content)是区块包含的交易列表。区块头包含前一个区块的哈希、区块
序号、随机数(nonce)、目标值(target)、时间戳(timestamp)、难度值(difficulty)、矿工地址(address)等内容。时间戳表示区块初始时间。随机数是一个没有意义的值,纯粹
是为了设置一个小于或等于目标值的区块哈希。以太坊使用ethash哈希算法。发现随机数的
唯一方法是穷尽所有可能。目标值是一个256位的数字,根据不同的因素计算。区块头的难
度值是目标值的一种不同表述方法。目标值越低,发现随机数需要的时间越多;目标值越
高,需要的时间越少。计算问题难度值的公式如下:
网络中的任何节点都可以检查区块链是否合法,首先检查交易在区块链中是否合法以及
时间戳的验证情况,然后检查区块的目标值和随机数是否合法、矿工是否得到合法的回报
等。 如果网络中的节点接收到两个不同的合法区块链,那么所有区块的整体难度值较
高的那个区块链被视为合法的区块链。
例如,假设网络中的一个节点想改变一个区块中的一些交易,就需要重新计算该块以及
该块后面所有区块的随机数。可是在该节点计算的同时,网络其他节点已经又挖出了许多新
的区块,因此当它重新计算到最新区块时会因整体难度值较低而被系统拒绝。可见,私自篡
改账本的难度是非常大的。2.5 时间戳
计算区块目标值的公式需要用到当前时间戳,且每个区块在区块头附加了当前时间戳。
没有什么机制可以阻止矿工在挖新区块时使用其他时间戳(而非当前时间戳),但是它们一
般不会那么做,因为时间戳验证会失败,其他节点不会接受该区块,这样就浪费了矿工的资
源。当一个矿工广播一个新挖出的区块时,其他节点对其时间戳的验证取决于其时间戳是否
大于前一个区块的时间戳。如果一个矿工使用的时间戳大于当前时间戳,则难度值较低,因
为难度值与当前时间戳成反比,因此网络将接受区块时间戳是当前时间戳的矿工,因为它的
难度值比较高。如果一个矿工使用的时间戳大于前一个区块时间戳,且小于当前时间戳,难
度值会高一些,因此要花费更多时间挖区块。等到区块被挖出的时候,网络可能产生了更多
区块,因此该区块会被拒绝,因为往往恶意矿工的区块链难度值会低于网络中的区块链难度
值。出于以上原因,矿工总是使用准确的时间戳,否则他们会一无所获。2.6 随机数
随机数是一个64位未签名证书。随机数是一个问题的解决办法,矿工不断地尝试随机
数,直到发现目标值。有人也许会好奇,如果某个矿工拥有的算力比网络中的任何其他矿工
都大,他是否总会第一个发现随机数?答案是不会。
每个矿工挖的区块的哈希是不同的,因为哈希取决于如时间戳、矿工地址等内容,而且
对于所有矿工来说这些内容很可能是不一样的。因此,解决问题并不是一场比赛,而更像是
一件碰运气的事。当然,矿工可能因为算力大而走运,但那并不意味着该矿工总会发现下一
个区块。2.7 区块时间
我们看到的区块难度值公式使用了一个长达10s的阈值,以确保挖出父区块和子区块的时
间差在10s和20s之间。但为什么是10~20s,而非其他数值呢?为什么时间差是恒定的,而非
难度值是恒定的?
假设有一个恒定的难度值,矿工只需要发现一个随机数使得区块的哈希小于等于该难度
值即可。假设该难度值大,且在此情况下,用户又无法估算用户间发送以太币的时间延迟。
如果网络算力不足,计算随机数需要较长时间,那么用户需要等待很长时间来确定交易。有
时网络算力充足,可能很幸运,很快就发现了随机数,用户交易确认就比较快。这类系统延
迟不确定的特点自然很难受到用户青睐,因为用户总想知道需要多长时间完成交易,就像我
们从一个银行账户向另一个银行账户汇款,银行会告诉我们在多长时间之内会完成汇款。如
果设定的难度值小,它将影响区块链的安全,因为大矿工可以比小矿工更快挖出区块,网络
中最大的矿工就会拥有控制DApp的能力。不可能发现一个可以使网络稳定的恒定难度值,因为网络算力并非恒定值。
现在我们知道了,为什么总是需要有一个相对稳定的生成区块的平均时间(即区块时
间)。问题是最合适的平均时间是多长。它可以短至1s,长至几乎无限多秒。降低难度值可
以使平均时间较短,反之增加难度值可以使平均时间较长。但是,平均时间的长短各有什么
优缺点呢?在讨论之前,首先需要知道无效无效块(stale block)是什么。
如果两个矿工用几乎相同的时间挖下一个区块,会发生什么呢?两个区块肯定都是合法
的,但是区块链不能包含区块序号相同的两个区块,而且两个矿工都得不到回报。尽管这是
个常见问题,解决方法却很简单,最后难度值较高的区块链将被网络接受。所以最后被忽略
的合法区块叫作无效无效块。
网络中生成的无效无效块总数与生成新区块所需的平均时间成反比。更短的区块生成时
间意味着新挖出来的区块向整个网络广播的时间更短,矿工发现问题解决办法的概率更大,所以当区块向整个网络广播时,其他一些矿工可能也解决了问题并进行了广播,由此产生了
无效块。但是如果生成区块的平均时间长一点,多个矿工能解决问题的概率就小一点,而且
即使它们都解决了问题,也很可能存在时间差,在这个时间差里,第一个被解决的区块就可
以进行广播,另一个矿工就可以停止挖那个区块并继续挖下一个区块。如果无效块在网络中
经常出现,就会出现大问题;如果仅是偶尔出现,就对网络没有损害。
但是无效块有什么问题呢?它们延迟了交易确认。当两个矿工几乎同时挖一个区块时,它们可能有不同的交易,因此如果交易出现在其中,就不能说交易已经确认了,因为交易中
出现的区块可能是无效块。我们应该等待再挖出几个区块。无效块导致平均确认时间不等于
生成区块的平均时间。
无效块会影响区块链安全吗?答案是肯定的。我们知道网络安全由网络中矿工的全部算
力衡量。当算力增长时,难度值也要增加,以确保区块不是在平均时间之前生成的。所以更
高的难度值意味着更安全的区块链——节点想篡改区块链将需要更多算力,使篡改区块链更
困难,因此区块链被认为是更安全的。当几乎同时挖出两个区块时,我们将把网络分成两部
分,在两个不同的区块链上工作,但是其中一个将成为最终区块链。所以在无效块上工作的网络是在无效块上挖下一个区块,结果是网络算力损失,因为算力用在了没有用的事情上。
网络的两个部分很可能用比平均时间更长的时间去挖下一个区块,因为它们损失了算力。所
以,在挖出下一个区块之后,难度值将降低,原因是用于挖区块的时间比平均时间更长。难
度值降低会影响整体区块链安全。如果无效块率太高,将在很大程度上影响区块链安全。
以太坊用ghost协议解决无效块带来的安全问题。以太坊使用这个真实ghost协议的一个修
正版本。ghost协议仅仅把无效块添加到母链上,掩盖了安全问题,由此增加了区块链的整体
难度值,因为区块链的整体难度值还包括无效块的难度值之和。但是如何才能在不产生交易
冲突的情况下把无效块添加到母链中呢?事实上,任何区块链都可以接纳零个或者多个无效
块。为了激励矿工接纳无效块,矿工接纳无效块将得到回报。此外,发现无效块的矿工也将
得到回报。无效块中的交易不用于计算确认,无效块矿工也不向无效块接纳的交易收取交易
费。注意,在以太坊中,无效块称为“叔块(uncle block)”。
矿工接纳无效块得到的回报计算公式如下。其余回报归侄块(nephew block),即包含
孤块(orphan block)的区块:
你肯定在奇怪为什么要给无效块矿工回报。即便不给它们任何回报,也不会影响安全。
这是因为无效块经常出现在网络中会导致另一个问题,而这个问题可以通过给无效块矿工回
报解决。矿工应当得到一定比例的回报,大致相当于它为网络贡献的算力比例。如果两个不
同的矿工几乎同时挖出一个区块,则算力比较大的矿工挖出的区块更有可能被添加到最终区
块链中,因为该矿工挖下一个区块的效率会比较高,所以小矿工将失去回报。如果无效块比
例低,就不是大问题,因为大矿工增加回报的概率不大。但是,如果无效块比例高,就会产
生大问题,也就是说,大矿工在网络中最终将得到比它应得的更多的回报。ghost协议通过回
报无效块矿工找到平衡。由于大矿工不拿走全部回报,但是仍比它们应得的多,我们不能对
无效块矿工和侄块给予同等回报,而是给得少一点。前面的公式很好地解决了问题。
ghost协议会限制一个侄块可以指向的无效块总数,这样矿工不会只挖无效块并使区块链
生成速度变慢。
所以一旦无效块出现在网络中,它会或多或少地影响网络。无效块出现得越频繁,网络
受到的影响越大。2.8 分叉
在节点验证区块链发生冲突时,会发生分叉(fork ing),也就是说,在网络中有多于一
个区块链,且每个区块链由一些矿工验证。分叉共有三种:普通分叉、软分叉和硬分叉。
普通分叉是由于两个或者多个矿工几乎同时发现了一个区块引起的暂时冲突。如果一个
难度值高于另一个,冲突就解决了。
更改源代码可能引起冲突。根据冲突类型,可能要求有50%以上算力的矿工升级,也可
能要求所有矿工升级,以解决冲突。要求有50%以上算力的矿工升级以解决冲突,叫作软分
叉;而要求所有矿工升级以解决冲突,叫作硬分叉。软分叉的一个例子是,如果更新源代码
使旧区块交易的一部分失效,则有50%以上算力的矿工升级后可以解决,这样新的区块链将
有更大难度值,最后被整个网络接受。硬分叉的一个例子是,如果更新源代码是为了更改对
矿工的回报,则全部矿工需要升级以解决冲突。
以太坊自发布以来经历了多次硬分叉和软分叉。2.9 创世区块
创世区块(genesis block)是区块链中的第一个区块,其区块序号是0。它是区块链中唯
一一个不指向前一个区块的区块,因为没有前一个区块。它也不包含交易,因为还没产生任
何以太币。
只有网络中的两个节点有相同的创世区块,它们才会彼此配对,也就是说,如果两个对
等节点有相同的创世区块才会进行同步区块,否则它们将彼此拒绝。不同的创世区块有较高
难度值也不能替代难度值较低的。每一个节点生成自己的创世区块。对于不同的网络,创世
区块被硬编码到客户端里。2.10 以太币面值
和其他货币一样,以太币也有多种面值。其面值如下:
·1以太币=1000000000000000000 wei。
·1以太币=1000000000000000 Kwei。
·1以太币=1000000000000 Mwei。
·1以太币=1000000000 Gwei。
·1以太币=1000000 Szabo。
·1以太币=1000 Finney。
·1以太币=0.001 Kether。
·1以太币=0.000001 Mether。
·1以太币=0.000000001 Gether。
·1以太币=0.000000000001 Tether。2.11 以太坊虚拟机
以太坊虚拟机(Ethereum Virtual Machine,EVM)是以太坊智能合约字节码(by te-
code)的执行环境。网络中的每个节点都运行EVM。所有节点执行使用EVM指向智能合约
的全部交易,因此它们进行同样的计算,并存储同样的数值。只进行以太币转账(查询该地
址是否有余额并相应地扣款)的交易也需要进行一些计算。
出于各种原因,每个节点执行并存储最终状态。例如,如果有一个智能合约存储参加派
对的每个人的姓名和细节,只要增加新的人,就向网络广播新的交易。网络中的任何节点想
要展示参加派对的每个人的细节,只需读取合约的最终状态即可。
每个交易需要在网络中进行一些计算和存储。因此需要有交易费,否则整个网络里将充
斥着垃圾交易,而且没有交易费用矿工就没有理由在区块中接纳交易,它们将开始挖空区
块。每个交易需要的计算和存储量有所不同,因此每一个交易的交易成本不同。
有两种EVM实现,即字节码VM和JIT-VM。在写本书时,JIT-VM已交付使用,但其开发仍未结束。在两种情况下,Solidity代码都被编译成字节码。在JIT-VM中,字节码编
译更充分。JIT-VM比字节码VM更高效。2.12 gas
gas(燃料)是计算资源的计量单位。每一个交易都需要包含gas上限和为每个gas支付费
用的单价(即每次计算的价格)。矿工可以选择接纳交易和收取费用。如果交易使用的gas少
于或等于gas上限,交易继续进行。如果gas总数超过gas上限,则撤销所有修改,除了仍然合
法且矿工仍然能够收到费用(费用计算方法是可以消耗的gas最大值和gas价格相乘)的交
易。
矿工决定gas价格(即每次计算的价格)。如果交易gas价格低于矿工决定的gas价格,矿
工将拒绝挖交易。gas价格以wei为单位。所以如果gas价格低于期望,矿工可以拒绝将交易接
纳入区块。
EVM的每个操作都被分配了一个数字,用以表示它可以消耗的gas。
交易成本影响一个账户可以转账给另一个账户的以太币上限。例如,如果某个账户里共
有5个以太币,它不能把全部5个以太币转入其他账户,因为如果把所有以太币都汇走,账户
就没有余额支付交易费了。
如果交易调用一个合约方法,且该方法发送一些以太币或者调用一些其他合约方法,就
从调用合约方法的账户扣除交易费。2.13 发现对等节点
节点是网络的一部分,它需要连接到网络中的一些其他节点,这样它可以广播交易区
块,并监听新的交易区块。节点不需要连接到网络中的每一个节点;相反,它只连接到几个
其他节点,这些节点再连接到另外几个节点。按照这个方式,整个网络彼此连接。
但是节点如何发现网络中的一些其他节点呢?没有每个节点都可以连接到的中央服务
器,怎么交换信息呢?以太坊有自己的节点发现协议可用于解决这个问题,该协议以
Kadelima协议为基础。在节点发现协议中有一种特殊的节点,叫作Bootstrap(初始启动)节
点。Bootstrap节点保存了一段时间内与它们连接的所有节点的列表,但其本身不保存区块
链。当对等节点连接到以太坊网络时,它们首先连接到Bootstrap节点,Bootstrap节点分享在
刚才事先定义的时间里连接到它们的对等节点列表。然后对等节点与对等节点连接并同步。
可以有多种以太坊实例,也就是说,不同的网络每个都有自己的网络ID。两种主要的以
太坊网络是主网和测试网。以太币在主网上交易,而测试网供开发人员进行测试。到目前为
止,我们已经学习了关于主网区块链的所有知识。
bootnode是以太坊Bootstrap节点最热门的实现。如果用户想使用自己的Bootstrap
节点,可以使用bootnode。2.14 Whisper和Swarm
Whisper和Swarm分别是去中心化的通信协议和存储平台,它们都由以太坊开发人员开
发的。Whisper是一个去中心化的通信协议,Swarm则是一个去中心化的文件系统。
Whisper允许网络中的节点彼此通信。它支持广播、用户到用户、加密信息等,但不用
于传输大数据。想更深入学习Whisper,请访问https:github.comethereumwikiwikiWhisper
,在https:github.comethereumwik iwikiWhisper-Overview 可以看到代码示例概述。
Swarm类似于Filecoin,二者最大的区别是技术细节和激励机制。Filecoin不惩罚存储;
而Swarm惩罚存储。因此,这进一步提高了文件可用性。那么,Swarm中的激励机制如何工
作?它有内部货币吗?事实上,Swarm没有内部货币,而是用以太币进行激励。在以太坊中
有智能合约,智能合约记录激励情况。显然,智能合约不能与Swarm通信,但Swarm能与智
能合约通信。所以用户基本上通过智能合约向存储付款,该支付在失效后被释放给存储。用
户还可以向智能合约报失文件,在此情况下它可以惩罚存储。可以访问
https:github.cometherspherego-ethereumwik iIPFS--SWARM 了解Swarm和IPFSFilecoin之
间的区别,访问https:github.cometherspherego-ethereumblobbzz-
configbzzbzzcontractswarm.sol 查看智能合约代码。
在写本书时,Whisper和Swarm仍处于开发阶段,许多事情仍不明确。2.15 geth
geth(或称为go-ethereum)是以太坊、Whisper和Swarm节点的一个实现。geth可以成为
全部实现或者一些选定实现的一部分。合并它们的目的是让它们看起来像单一的DApp,通
过一个节点客户端就可以访问三个DApp。
geth是一种CLI应用,它用Go语言编写,在主要的操作系统中都可使用。geth的当前版本
还不支持Swarm,但支持Whisper的一些功能。在写本书时,geth的最新版本是1.3.5。2.15.1 安装geth
geth可用于OS X、Linux和Windows操作系统。它支持两种类型的安装:二进制安装和脚
本安装。在写本书时,geth的最新版本是1.4.13。让我们看看如何使用二进制安装方法在不同
操作系统中进行安装。如果用户不得不修改geth源代码并安装,请使用脚本安装方法。我们
不想改变源代码,因此将采用二进制安装。
1.OS X
推荐在OS X中使用brew安装geth。在终端运行下面两个命令安装geth:
2.Ubuntu
推荐在Ubuntu中使用apt-get安装geth。在Ubuntu终端中运行如下命令安装geth:
3.Windows
对于Windows来说,geth是一个可执行文件。从https:github.comethereumgo-
ethereumwikiInstallation-instructions-for-Windows 下载zip文件,并解压缩。压缩包中有
geth.exe文件。
想更多地了解在不同操作系统上安装geth的方法,请访问
https:github.comethereumgo-ethereumwikiBuilding-Ethereum 。2.15.2 JSON-RPC和JavaScript操作台
geth为其他应用提供了与其进行通信的JSON-RPC API。geth使用HTTP、WebSock et和其
他协议服务于JSON-RPC API。JSONRPC提供的API分成:admin、debug、eth、miner、net、personal、shh、txpool和web3等类型。访问https:github.comethereumgo-
ethereumwikiJavaScript-Console 可以了解更多信息。
geth还提供了一个交互JavaScript操作台,可以使用JavaScript API进行程序交互。该交互
操作台使用JSON-RPC与geth进行通信。在后面的章节中,我们将学习更多关于JSON-RPC和
JavaScript API的内容。2.15.3 子命令和选项
让我们通过例子学习geth命令的一些重要的子命令和选项。用户可以使用help子命令发
现所有子命令和选项的列表。我们将在下面学习更多关于geth及其命令的知识。
1.连接至主网网络
以太坊网络中的节点默认用30303端口通信。但是节点还可以收听一些端口。
为了连接到主网网络,只需要运行geth命令即可。如下是一个例子,展示如何明确指定
网络ID和指定将存储下载区块链的自定义目录:
其中,--datadir选项用于指定在哪里存储区块链。如果没有提供,默认路径
是“HOME.ethereum”;--networkid用于指定网络ID。1代表主网网络ID。如果没提供网络ID,默认值是1。2代
表测试网络ID。
2.创建私有网络
要创建私有网络,只需给出一个随机网络ID即可。通常创建私有网络的目的是进行开
发。geth还提供了多个与日志和调试相关的标记(flag),这对于开发很有益处。可以简单使
用--dev标记运行一个私有网络,该网络允许多个与日志和调试相关的标记,而不用给出一个
随机网络ID并放上多个与日志和调试相关的标记。2.15.4 创建账户
geth还允许创建账户,即生成密钥和相关地址。为了创建账户,可以使用下面的命令:
当运行上述命令时,需要输入密码以加密账户。如果忘记密码,就无法访问账户了。
为了在本地钱包获得所有账户的列表,可以使用下面的命令:
执行上述命令将打印账户中所有地址的列表。密钥默认存储在--datadir路径中,但用户
可以使用--k ey store选项指定一个不同的目录。
1.挖矿
默认geth不启动挖矿。为了指示geth开始挖矿,只需要提供--mine选项。还有一些与挖矿
相关的选项:
除了--mine选项之外,这里还给出了其他选项。--minerthreads选项用于指定哈希过程中
使用的线程总数,默认使用8个线程。etherbase是挖矿赚取的回报存入的地址。账户默认是
加密的。所以要访问账户中的以太币,就需要解锁,即解码账户。解密用于解码账户相关私
钥。为了开始挖矿,不需要解锁它,因为只需要地址就能存入挖矿回报。可以使用-unlock选
项解锁一个或者多个账户。使用逗号分隔地址可以提供多个地址。--minergpus用于指定挖矿使用的GPU。为了得到GPU列表,可以使用geth gpuinfo命
令。每个GPU需要1~2GB的RAM。默认只使用CPU,而不使用GPU。
2.快速同步
在写本书时,区块链大小大约为30GB。如果用户的网速慢,则下载需要花费几个小时甚
至几天。以太坊实现了一种快速同步算法,可以更快地下载区块链。
快速同步(fast synchronization)不下载整个区块,而只下载区块头、交易凭证和最新的
状态数据库。因此用户不需要下载和重播全部交易。为了检查区块链的真实性,该算法在每
一个已定义的区块序号之后下载一个完整的区块。要更深入地学习快速同步算法,请访问
https:github.comethereumgo-ethereumpull1889 。为了在下载区块链过程中使用fast sy nc,用户需要在运行geth的过程中使用--fast。
出于安全原因,fast sy nc只在初始同步时运行(即该节点自身的区块链为空时)。在节
点成功与网络同步后,fast sync就永远禁用了。作为一项额外的安全功能,如果在枢轴点
(pivot point)附近或者之后快速同步失败,就会禁用fast sync,然后节点返回到完整的、以
区块处理为基础的同步。2.16 以太坊钱包
以太坊钱包是一个以太坊UI客户端,它允许用户进行创建账户、发送以太币、部署合
约、调用合约方法等操作。
以太坊钱包与geth捆绑在一起。运行以太坊时,它会尝试发现一个本地geth实例并与之
连接;如果它不能发现geth正在运行,它就启动自己的geth节点。以太坊钱包使用IPC与geth
通信。geth支持以文件为基础的IPC。
如果在运行geth时更改数据目录,就是也在更改IPC文件路径。所以为了让以太
坊钱包发现并连接到geth实例,需要使用--ipcpath选项指定IPC文件位置为其默认位置,这样
以太坊钱包可以发现它;否则,以太坊钱包就不能发现它,将启动自己的geth实例。为了发
现默认IPC文件路径,运行geth help,它会显示--ipcpath选项的默认路径。
请访问https:github.comethereummistreleases 下载以太坊钱包。它适用于Linux、OS X
和Windows操作系统。与geth一样,它有两种安装方式:二进制安装和脚本安装。
以太坊钱包的示意图如图2-1所示。2.17 浏览器钱包
浏览器钱包(Mist)是以太坊、Whisper和Swarm的一个客户端,它允许用户发送交易、发送Whisper信息、检查区块链等。
Mist和geth之间的关系类似于以太坊钱包和geth。
Mist最热门的功能是它带有浏览器。目前,浏览器中运行的前端JavaScript可以使用
web3.js库(该库为其他应用提供以太坊操作台的JavaScript API与geth通信)访问geth节点的
web3 API。
Mist的基本思想是创建第三代Web(Web 3.0),即使用以太坊、Whisper和Swarm替代
中心化服务器端,这样就不需要服务器端了。图2-1 以太坊钱包的示意图
Mist的示意图如图2-2所示。2.18 以太坊的缺点
每个系统都有一些缺点,同理以太坊也有一些缺点。显然,像其他应用一样,以太坊源
代码可能有bug。它也像其他以网络为基础的应用一样面临着DoS攻击。让我们看看以太坊独
有的且最重要的缺点。
1.Sybil攻击
攻击者可能试图用他控制的普通节点占满整个网络,那么用户很有可能只连接到攻击者
节点。一旦连接到攻击者节点,攻击者可以拒绝从所有节点转播区块和交易,从而将用户从
网络中断开。攻击者只能转播他创建的区块,从而会将用户放到分开的网络上。
图2-2 Mist的示意图
2.51%攻击
如果攻击者掌握了网络中一半以上的算力,他就可以比网络中其他人更快地生成区块。
攻击者可以保留他的私有分叉,直到分叉比诚实网络创造得更长,然后广播自己的分叉。
拥有50%以上的算力,矿工就可以重写交易,阻止全部一些交易被挖出,并阻止其他矿工挖出的区块被添加到区块链中。2.19 serenity
serenity是以太坊下一个主要更新的名字。在写本书之时,serenity仍处于开发阶段。这
个更新将要求硬分叉。serenity把共识协议改为casper,并将整合状态通道和分片。在写本书
时,完整细节尚不明确。
1.支付和状态通道
在学习状态通道以前,我们需要了解支付通道的概念。支付通道功能允许将两个以上向
另一个账户发送以太币的交易合并成两个交易。其工作原理为:假设X是一个视频网站老
板,Y是个用户。X每分钟收费1个以太币。现在X想让Y看视频期间每分钟交一次钱。当
然,Y可以每分钟广播交易,但是这里还有些问题,例如X不得不等待确认,所以视频就会
中断一会。支付通道可以解决这个问题。使用支付通道,Y可以广播一个锁定交易,为X把
一些以太币(比如100个以太币)锁定一段时间(比如24小时)。现在每看完一分钟视频,Y
将发送一个签名记录表示可以解锁,一个以太币就进入X的账户,其余的进入Y的账户。再
过一分钟,Y将发送一个签名记录表示可以解锁,两个以太币就进入X的账户,其余的进入Y
的账户。Y观看X网站的视频过程中,该过程将持续。现在假设Y看完了100小时视频或者24
小时时间到了,X将向网络广播最后的签名记录,以把钱收到自己的账户里。如果X没有在
24小时内提款,全款会返还给Y。所以在区块链中,我们将看到lock和unlock两种交易。
支付通道是与发送以太币相关的交易。类似地,状态通道允许合并与智能合约相关的交
易。
2.权益证明和casper
在学习casper共识协议之前,我们需要理解权益证明(Proof-of-Stake,PoS)共识协议
的工作原理。
权益证明是工作量证明最常见的替代共识。工作量证明会浪费大量算力。PoW和PoS之
间的区别就是:在PoS中,矿工不需要解决问题;而在Pow中,矿工需要证明挖矿权益的所
有权。在PoS系统中,账户中的以太币被当作权益,矿工挖矿的概率与矿工持有的权益成正
比。所以如果矿工拥有网络中10%的权益,它将挖到10%的区块。
但问题是怎样才能知道谁将挖下一个区块。我们不能简单地让持有最多权益的矿工总能
挖出下一个区块,因为这将导致中心化。对于下一个区块的选择,存在不同的算法,例如随
机区块选择和基于币龄的选择。
casper是PoS的一个修订版本,它解决了PoS中的一些问题。
3.分片
目前,每个节点都需要下载全部交易,数量庞大。按照现在区块链发展的速度,未来用
不了几年,下载整个区块链并同步将是非常困难的。
如果用户熟悉分布式数据库架构,那么肯定熟悉分片(sharding)。简言之,分片就是在多个计算机分布数据的方法。以太坊将实现分片,以分割区块链并跨节点分布区块链。
读者可以在https:github.comethereumwikiwikiSharding-F AQ 学习将区块链分片的知
识。2.20 总结
在本章中,我们具体学习了以太坊的工作原理、区块时间如何影响安全以及以太坊的缺
点;还学习了Mist和以太坊钱包的概念及其安装方法,以及geth的一些重要命令;最后学习
了以太坊serenity更新中的新内容。
在下一章中,我们将学习存储和保护以太币的不同方法。第3章 编写智能合约
在前一章中,我们学习了以太坊区块链的工作原理以及PoW共识协议保障其安全性的原
理。现在我们已经掌握了以太坊的工作原理,所以是时候开始编写智能合约了。有好几种语
言可以用于编写以太坊智能合约,不过Solidity是最热门的语言。在本章中,我们将首先学习
Solidity编程语言。然后创建一个DApp,用于证明在特定时间的存在、真实性和所有权,即
证明一个文件在一个特定时间属于一个特定所有者。
在本章中,我们将讲解以下内容:
·Solidity源文件的布局。
·理解Solidity的数据类型。
·合约的特殊变量和函数。
·控制结构。
·合约的结构和功能。
·编译和部署合约。3.1 Solidity源文件
Solidity源文件使用的扩展名为.sol。与其他编程语言一样,Solidity有多种版本。在写本
书时,其最新版本是0.4.2。
在源文件中,可以使用pragma Solidity说明编写代码时用的编译器版本。例如,现在,源文件不会用低于0.4.2的编译器版本,也不会用高于0.5.0的编译器版本进行编译
(第二个条件使用^添加)。0.4.2和0.5.0之间的编译器版本最有可能包括bug修复。
可以为编译器版本指定更复杂的规则;使用与npm一样的表达式。3.2 智能合约的结构
合约就像一个类(class),其中包含状态变量(state variable)、函数(function)、函
数修改器(function modifier)、事件(event)、结构(structure)和枚举(enum)。合约
还支持继承,通过在编译时备份代码来实现。最后,合约还支持多态。
下面来看一个智能合约的例子:上述代码的工作原理如下:
1)使用contract关键字声明一个合约。
2)声明两个状态变量data和owner。data包含一些数据,owner包含所有者的以太坊钱包
地址,即部署合约者的以太坊地址。
3)定义一个事件(event)。事件用于通知客户端。一旦data发生变化,将触发这个事
件。所有事件都保存在区块链中。
4)定义一个函数修改器(function modifier)。修改器用于在执行一个函数之前自动检
测条件。这里,修改器检测合约所有者是否在调用函数。如果不是,就抛出异常。
5)得到合约构造函数(constructor)。在部署合约时,调用构造函数。构造函数用于初
始化状态变量。6)定义两个方法。第一个方法用于得到data状态变量的值,第二个方法用于改变data的
值。
在更深入地学习智能合约的函数之前,我们先来学习一些与Solidity有关的其他知识,然
后再回到合约。3.3 数据位置
截至目前,我们学过的所有编程语言可能都把变量存储在内存中。但是在Solidity中,根
据情况的不同,变量可能不存储在内存和文件系统中。
根据情况的不同,数据总有一个默认位置。但是对于复杂数据类型,例如字符串
(string)、数组(array)和结构类型(struct),可以用向类型添加storage或者memory进
行重写。函数参数(包括返回参数)默认用memory,本地变量默认用storage。显然,对于
状态变量来说,位置强制用storage。
数据位置很重要,因为它们会改变分配的行为:
·storage变量和memory变量之间的分配总是创建一个独立的备份。但如果分配是从
memory存储的一种复杂类型到另一种复杂类型,则不创建备份。
·到一个状态变量的分配(即使是来自其他状态变量)总是创建一个独立的备份。
·不能把memory中存储的复杂类型分配给本地存储变量。
·在分配状态变量给本地存储变量的情况下,本地存储变量指向状态变量,也就是说,本
地存储变量变为指针。3.4 什么是不同的数据类型
Solidity是一种静态类型语言,变量存储的数据类型需要预先定义。所有变量默认值都是
0。在Solidity中,变量是有函数作用范围的,也就是说,在函数中任何地方声明的变量将对
整个函数存在适用范围,无论它是在哪里声明的。
现在让我们看看Solidity提供的不同数据类型:
·最简单的数据类型是布尔值,可以是true或者false。
·uint8,uint16,uint24,…,uint256分别用于存储无符号的8位,16位,24位,…,256位
整数。同理,int8,int16,…,int256分别用于存储8位,16位,24位,…,256位整数。uint
和int是uint256和int256的别名。类似于uint和int,ufixed和fixed代表分数。ufixed0x8,ufixed0x16,…,ufixed0x256分别用于存储未签名的8位,16位,24位,…,256位分数。同
理,fixed0x8,fixed0x16,…,fixed0x256分别用于存储8位,16位,24位,…,256位分数。
如果一个数字超过256位,则使用256位数据类型存储该数字的近似值。
·address可以用于存储最大20字节的值(十六进制表示)。它用于存储以太坊地址。
address类型有两个属性:balance和send。balance用于检测地址余额,send用于向地址发送以
太币。send方法拿出需要转账那些数量的wei,并根据转账是否成功返回true或者false。wei
从调用send方法的合约中扣除。用户可以在Solidity中使用0x前缀给变量分配一个十六进制的
数值。3.4.1 数组类型
Solidity支持generic和byte两种数组类型。它们支持固定长度和动态长度两种数组,也支
持多维数组。
by tes1,by tes 2,by tes3,……,bytes32是字节数组的类型。by te是by tes 1的别名。
下面给出了generic数组语法的一个示例:关于数组的重要内容如下:
·数组还有length属性,用于发现数组的长度。用户还可以给length属性分配一个值,以改
变数组大小,但不可以在内存中改变数组大小,也不可以改变非动态数组大小。
·如果想访问动态数组的未设置索引(unset index),会抛出异常。
记住:array、structs和map都不可以用作函数参数,也不可以用作函数返回值。3.4.2 字符串类型
在Solidity中,有两种方法创建字符串:使用bytes和string。bytes用于创建原始字符串,而string用于创建UTF-8字符串。字符串长度总是动态的。
下面给出了字符串语法的一个示例:3.4.3 结构类型
Solidity还支持结构类型(struct)。下面给出了struct语法的一个示例:
注意:函数参数不可以是结构类型,且函数不可以返回结构类型。3.4.4 枚举类型
Solidity还支持枚举类型(enum)。下面给出了enum语法的一个示例:3.4.5 mapping类型
mapping数据类型是一个哈希表。mapping类型只可以存在于storage中,不存在于
memory中,因此它们是作为状态变量声明的。可以认为mapping类型包含k ey value对,不是
实际存储key,而是存储key的k eccak 256哈希,用于查询value。mapping类型没有长度。
mapping不可以被分配给另一个mapping。
下面给出了一个创建和使用mapping的示例:
记住:如果想访问mapping中不存在的k ey,返回的value均为0。3.4.6 delete操作符
delete操作符可以用于任何变量,将其设置成默认值。默认值均为0。
如果对动态数组使用delete操作符,则删除所有元素,其长度变为0。如果对静态数组使
用delete操作符,则重置所有索引。还可以通过对特定索引位置使用delete来重置索引。
如果对map类型使用delete操作符,什么都不会发生。但是如果对map类型的一个键使用
delete操作符,则会删除与该键相关的值。
下面给出了delete操作符的一个示例:3.4.7 基本类型之间的转换
除了数组类型、字符串类型、结构类型、枚举类型和map类型外,其他类型均称为基本
类型。
如果把一个操作符应用于不同的类型,编译器将尝试把一个操作数隐式转换为另一种类
型。通常来说,如果没有语义信息丢失,值和类型之间可以进行隐式转换:uint8可转换为
uint16,int128可转换为int256,但是int8不可转换为uint256(因为uint256不能存储,例
如-1)。此外,无符号整数可以转换成同等大小或者更大的字节,但是反之则不然。任何可
以转换成uint160的类型都可以转换成地址。
Solidity也支持显式转换。所以,如果编译器不允许在两种数据类型之间隐式转换,则可
以进行显式转换。建议尽量避免显式转换,因为可能返回难以预料的结果。
来看一个例子:
这里是将uint32类型显式转换为uint16,也就是说,把较大类型转换为较小类型,因此高
位被砍掉了。3.4.8 使用var
Solidity提供了用于声明变量的var关键字。变量类型根据分配给它的第一个值来动态确
定。一旦分配了值,类型就固定了,所以如果给它指定另一个类型,将引起类型转换。示例
如下:
记住:在定义数组array和map时不能使用var。var也不能用于定义函数参数和状
态变量。3.5 控制结构
Solidity支持if、else、while、for、break、continue、return、?:等控制结构。
下面给出了控制结构的一个示例:3.6 用new操作符创建合约
一个合约可以使用new关键字来创建一个新合约,但前提是必须知道新创建的合约的完
整代码。示例如下:3.7 异常
在一些情况下,异常会被自动抛出。也可以使用throw手动抛出异常。抛出异常会停止回
滚目前执行的调用(也就是说,撤销对状态和余额的所有改变)。捕获异常是不可能的:3.8 外部函数调用
在Solidity中,有两种函数调用:内部函数调用和外部函数调用。内部函数调用是指一个
函数在同一个合约中调用另一个函数。
外部函数调用是指一个函数调用另一个合约的函数。示例如下: 使用this关键字进行的调用称为外部调用。在函数中,this关键字代表当前合约实
例。3.9 合约功能
现在是时候深入学习合约了。我们将看看一些新的功能,还将深入学习已经见过的一些
功能。3.9.1 可见性
函数或者状态变量的可见性定义了谁可以看到它。函数和状态变量有四种可见性:
external、public、internal和private。
函数可见性默认为public,状态变量可见性默认为internal。各可见性函数的含义如下:
·external。外部函数只能由其他合约调用,或者通过交易调用。外部函数f不能被内部函
数调用,也就是说,f没有用,但是this.f有用。不能把external可见性应用到状态变
量。
·public。公共函数和状态变量可以用所有可行办法访问。编译器生成的存取器
(accessor)函数都是公共状态变量。用户不能创建自己的存取器。事实上,它只生成
getters,而不生成setters。
·internal。内部函数和状态变量只可以内部访问,也就是说,从当前合约内和继承它的
合约访问。不可以使用this访问它。
·private。私有函数和状态变量类似于内部函数,但是继承合约不可以访问它们。
下面给出了可见性和存取器(accessor)的一个示例:3.9.2 函数修改器
我们之前看到了函数修改器(function modifier)的概念,还编写了一个基本的函数修改
器,现在来深入学习修改器。
修改器由子合约(child contract)继承,且子合约可以对其重写。可以通过用空格分隔
的列表指定修改器将多个修改器应用到一个函数,并将多个修改器按顺序估值;还可以向修
改器传送实参。
在修改器中,无论下一个修改器体或者函数体二者哪个先到达,会被插入到“_”;出现
的地方。
让我们来看一个函数修改器的复杂代码例子:myFunction的执行代码如下:
在上述代码中调用myFunction方法时,将返回0。但是之后访问状态变量a时,将得
到8。
修改器或者函数体中的return(返回)立即离开整个函数,返回值被分配成它需要成为
的任何变量。
就函数来说,return之后的代码在调用者的代码完成运行后再执行。就修改器来说,上
述修改器中的“_;”之后的代码在调用者的代码完成运行后再执行。在上面的例子中,第5、6和7行从未执行过。在第4行之后,执行从第8~10行开始。
修改器中的return不可以有相关值,它总是返回全0。3.9.3 回退函数
一个合约可以有唯一的未命名函数,称为回退函数(fallback function)。该函数不能有
实参,不能返回任何值。如果其他函数都不能匹配给定的函数标识符,就在合约调用上执行
回退函数。
当合约不用任何函数调用就接收以太币(即交易发送以太币给合约却不调用任何方法)
时,也执行该函数。在此情况下,用于函数调用的gas通常很少(准确地说是2300 gas),所
以使回退函数尽可能便宜很重要。
接收以太币但是却不定义回退函数的合约会抛出异常,把以太币发送回去。所以如果你
想让你的合约接收以太币,就必须要实现回退函数。
下面给出了回退函数的一个示例:3.9.4 继承
Solidity通过代码备份(包括多态)支持多重继承(multiple inheritance)。即使一个合约
继承自其他多个合约,在区块链上也只创建一个合约,来自父合约(parent contract)的代码
总是被复制到最终合约里。示例如下:1.super关键字
super关键字用于引用最终继承链中的下一个合约,示例如下:其中,引用sample6合约的最终继承链是sample6、sample5、sample4、sample2、sample3和sample1。继承链始于衍生最充分的合约,终于衍生最不充分的合约。
2.抽象合约
仅包含函数原型而不包含函数实现的合约称为抽象合约(abstract contract)。这些合约
不能被编译(即使包含实现函数和非实现函数)。如果一个合约继承自抽象合约且不重写并
实现所有非实现函数,那么它自己也是抽象的。
抽象合约仅在创建编译器已知的接口时提供。这在引用已部署的合约和调用其函数时是
很有用的。示例如下:3.10 库
库类似于合约,但其目的是在一个特定地址只部署一次,且其代码由不同合约反复使
用。这意味着如果调用库函数,其代码在调用合约(calling contract)中执行,也就是说,this指向调用合约,特别是来自调用合约的storage可以被访问。由于库是源代码中独立的一部
分,它只能访问调用合约的状态变量,如果这些变量是显式的(否则无法命名这些变量)。
库没有状态变量——它们不支持继承,也不能接收以太币。库可以包含结构类型
(struct)和枚举类型(enum)。
一旦在区块链中部署Solidity库,任何知道其地址和源代码(只知道原型或者知道完整实
现)的人都可以使用它。Solidity编译器需要有源代码,这样能确保所欲访问的方法在库中真
实存在。示例如下:
不能在合约源代码中添加库地址,而是需要在编译时向编译器提供库地址。
库有许多使用示例。两个主要的示例如下:
·如果有许多合约,它们有一些共同代码,则可以把共同代码部署成一个库。这将节省
gas,因为gas也依赖于合约的规模。因此,可以把库想象成使用其合约的基础合约。使用基
础合约(而非库)切分共同代码不会节省gas,因为在Solidity中,继承通过复制代码工作。
由于库被当作基础合约,库里面带有内部可视性的函数被复制给使用它的合约;否则,库里
面带有内部可视性的函数不能被使用这个库的合约调用,因为这需要外部调用,而带有内部
可视性的函数不能通过外部调用被调用。此外,库里的structs和enums被复制给使用这个库的
合约。
·库可用于给数据类型添加成员函数。 如果一个库里只包含内部函数和或structsenums,则不需要部署库,因为库里面
的所有内容都被复制给使用它的合约。
using for
using A for B这条指令可用于连接库函数(从库A到任意类型B)。这些函数将被调用的
对象作为它们的第一个参数接收。
using A for的结果表示来自库A的函数被连接到所有类型。示例如下:3.11 返回多值
Solidity允许函数返回多值(multiple values),示例如下:3.12 导入其他Solidity源文件
Solidity允许一个源文件导入其他源文件,示例如下:3.13 全局可用变量
有些特殊变量和函数永远存在于全局中。3.13.1 区块和交易属性
区块和交易属性有如下几项:
·block.block hash(uint blockNumber)returns(bytes32)。给定区块的哈希值,只支持最
近256个区块。
·block.coinbase(address)。当前区块矿工的地址。
·block.difficulty(uint)。当前区块的难度值。
·block.gaslimit(uint)。当前区块的gas上限。它定义了整个区块中的所有交易一起最多
可以消耗多少gas。其目的是使区块的传播和处理时间保持在较低水平,这样才能有足够去中
心化的网络。矿工有权利将当前区块的gas上限设置为上一个区块的gas上限~0.0975%(11,024)以内的数值,所以gas上限的结果应当是矿工偏好的中间值。
·block.number(uint)。当前区块的序号。
·block.timestamp(uint)。当前区块的时间戳。
·msg.data(by tes)。完整的调用数据里存储的函数及其实参。
·msg.gas(uint)。当前剩余的gas。
·msg.sender(address)。当前调用发起人的地址。
·msg.sig(by tes4)。调用数据的前四个字节(函数标识符)。
·msg.value(uint)。这个消息所附带的货币量,单位为wei。
·now(uint)。当前区块的时间戳,等同于block.timestamp。
·tx.gasprice(uint)。交易的gas价格。
·tx.origin(address)。交易的发起人(完整的调用链)。3.13.2 地址类型相关
地址类型相关变量如下:
·.balance(uint256)。地址余额,单位为wei。
·.send(uint256 amount)returns(bool)。发送指定数量的wei到地址,失败时
返回false。3.13.3 合约相关
合约相关变量如下:
·this。当前合约,可显式转换成地址类型。
·selfdestruct(address recipient)。销毁当前合约,把其中的资金发送到指定地址。3.14 以太币单位
一个数字可以用wei、finney、szabo或者Ether等单位转换不同面值的以太币。以太币如
果不标明货币单位,就默认以wei为单位,例如,2Ether可转换成2000finney。3.15 存在、真实性和所有权合约的证明
本节将编写一个不用出示实际文件就可以证明文件所有权的Solidity合约。它可以证明该
文件在某个特定时间存在,并最终检查文件真实性(integrity)。
将成对存储文件哈希和所有者名字以实现所有权证明(Proof of Owernership,PoO),成对存储文件哈希和区块时间戳以实现存在证明(Proof of Existence,PoE)。最后,存储
哈希自身证明文件真实性,也就是说,如果文件被修改了,则它的哈希会随之改变,合约就
不能发现任何这样的文件了,由此证明文件被修改了。
相关智能合约的代码如下:3.16 编译和部署合约
以太坊提供了solc编译器,其中提供一个命令行界面编译.sol文件,请访问如下网
址:http:solidity.readthedocs.ioendevelopinstalling-solidity.htmlbinary -packages 找到安装指
南,并访问https:Solidity.readthedocs.ioendevelopusing-the-compiler.html 找到使用指南。我
们不会直接使用solc编译器;而是使用solcjs和Browser Solidity。Solcjs允许在node.js中以编程
方式编译Solidity,而Browser Solidity是一个适用于小型合约的IDE(集成开发环境)。
现在使用以太坊提供的浏览器编译前面的合约。如需深入相关知识,请访问
https:Ethereum.github.iobrowser-Solidity 。用户还可以下载Browser Solidity源代码,并离线
使用。请访问https:github.comEthereumbrowser-Soliditytreegh-pages 进行下载。
使用browser Solidity的主要优点是,它提供了一个编辑器(editor),并生成代码以部署
合约。
在编辑器中,复制粘贴前面的合约代码。将看到它编译并提供web3.js代码,以使用geth
交互操作台进行部署。
输出如下:
data代表EVM理解的字节码(by tecode)。源代码首先转换成opcode,然后再转换成字
节码。每个opcode都有相关gas。web3.eth.contract的第一个实参是ABI定义。在创建交易时使用ABI定义,因为它包含所
有方法的原型。
现在在开发者模式下启用挖矿,运行geth。运行如下命令:
现在打开另一个命令行窗口,在其中输入下面的命令,以打开geth的交互JavaScript操作
台:
这将使JS操作台连接到在另一个窗口运行的geth实例上。
在browser Solidity的右侧复制web3部署文本框的全部内容,并将其粘贴到交互操作台
上。现在按键,将首先得到交易哈希值,待交易被挖出来之后,将得到合约地址。
交易哈希值是该交易的,每个交易的哈希都不一样。每个被部署的合约都有一个独特的合约
地址,以便在区块链中标识合约。
合约地址是确定的,它由生成器(creator)的地址(from address)和生成器发送的交
易数量(交易随机数)计算得到。这二者用RLP编码,然后使用k eccak -256 hashing算法进行
哈希计算。我们在后面还将深入学习交易随机数。若要更深入地学习RLP,请访问
https:github.comEthereumwikiwik iRLP 。
下面存储文件细节并检索。
用如下代码广播交易以存储文件细节:
这里用得到的合约地址代替合约地址。proofContract.at方法的第一个实参是合约地址。
这里并没有提供gas,它是自动计算的。下面发现文件细节。为了发现文件细节,运行如下代码:
会得到这样的输出:
call方法用于在EVM当前状态上调用一个合约的方法。它不广播交易。若要读取数据,则不需要广播,因为会有自己的区块链复制。
我们将在后面的几章中更多地学习web3.js。3.17 总结
在本章中,我们学习了Solidity编程语言以及数据位置、数据类型和合约的高级功能,还
学习了编译和部署智能合约最快速、最简便的方法,接下来应该放心地编写智能合约了。
在下一章中,我们将创建智能合约前端,这有利于部署智能合约和运行交易。第4章 开始使用web3.js
在前一章中,我们学习了编写智能合约的方法以及使用web3.js在geth交互接口上部署和
广播交易。在本章中,我们将学习web3.js的相关内容,包括如何导入、如何连接到geth以及
如何在node.js或者客户端JavaScript中使用它,还将学习如何使用web3.js为前一章中的智能
合约创建web客户端。
在本章中,我们将讲解以下内容:
·在node.js和客户端JavaScript中导入web3.js。
·将web3.js连接到geth。
·探索用web3.js可以做的各种事。
·探索web3.js最常用的几个API。
·为所有权合约创建node.js应用。4.1 web3.js概述
web3.js提供了用于和geth通信的JavaScript API。它内部使用JSON-RPC与geth通信。
web3.js还可以与所有种类的、支持JSON-RPC的以太坊节点通信。它把所有JSON-RPC API
当作JavaScript API,也就是说,它不仅支持所有与以太坊相关的API,还支持与Whisper和
Swarm相关的API。
随着不同项目的创建,我们会越来越了解web3.js。目前我们先来看一些最常用的
web3.js API,然后使用web3.js创建一个所有权智能合约前端。
在写本书时,web3.js的最新版本是0.16.0。本章所述内容也是这个版本。
web3.js托管在https:github.comethereumweb3.js ,完整文档
在https:github.comethereumwik iwik iJavaScript-API 。4.1.1 导入web3.js
为了在node.js中使用web3.js,可以在项目目录中运行npm install web3,且在源代码中可
以使用“require(web3);”导入它。
为了在客户端JavaScript使用web3.js,可以使web3.js文件入队,该文件可以在项目源代
码的dist目录中找到。现在,Web3对象对全局可用。4.1.2 连接至节点
web3.js可以与使用HTTP或者IPC的节点通信。我们将使用HTTP与节点建立通信。
web3.js允许与多个节点建立连接。一个web3实例代表与节点的一个连接。该实例公开了
API。
当在Mist中运行一个App时,它自动使一个连接到mist节点的web3实例可用。实例变量
名是web3。
为了连接到节点所使用的基础代码如下:
首先,通过检查web3是否是undefined,来确定代码是否在Mist中运行。如果web3被定义
了,则使用已经可用的实例;否则,通过连接至自定义节点创建一个实例。如果无论App是
否在Mist中运行都连接到自定义节点,则从程序代码中删除if。这里假设自定义节点在8545端
口本地运行。
Web3.providers对象使用多种协议显示构造函数(在此称为providers),以建立连接和传
输信息。Web3.providers.HttpProvider允许建立HTTP连接,Web3.providers.IpcProvider允许
建立IPC连接。
web3.currentProvider属性被自动分配给当前的provider实例。在创建web3实例之后,可
使用web3.setProvider方法改变provider。它有一个实参,即新provider的实例。
记住:geth默认禁用HTTP-RPC。所以在运行geth时通过--rpc选项以使用HTTP-
RPC。HTTP-RPC默认在8545端口运行。
web3显示isConnected方法,可用于查询是否已经与节点连接。根据连接状态的不
同,返回true或者false。4.1.3 API结构
web3包含一个eth对象(web3.eth),专门用于以太坊区块链交互;还包含一个shh对象
(web3.shh),用于whisper交互。web3.js的大部分API都在这两个对象中。
所有API都是默认同步的。如果想发出异步请求,可以把一个可选回调函数作为最后的
参数传送给大多数函数。所有回调函数都采用错误优先(error-first)回调方式。
一些API对于异步请求采用别名。例如web3.eth.coinbase是同步的,web3.eth.getCoinbase是异步的。示例如下:
getBlock使用区块序号或者哈希值获取区块信息。或者,它可以使用一个字符串,例
如earliest(创世区块)、latest(区块链最上面的区块)或者pending(正在挖的区
块)。如果不传送实参,则默认是web3.eth.defaultBlock,默认分配latest。
所有需要区块身份证明作为输入的API可以用序号、哈希值或者一个可读字符串作为输
入。如果值未通过,则这些API默认使用web3.eth.defaultBlock。4.1.4 BigNumber .js
JavaScript本质上对于正确处理大数字不在行。因此,需要处理大数字和进行完美计算的
应用会使用BigNumber.js库。
web3.js还依赖于BigNumber.js,且自动进行加载。web3.js总是对序号值返回BigNumber
对象。它可以用JavaScript数字、数字字符串和BigNumber实例作为输入,示例如下:
这里使用web3.eth.getBalance方法获取地址余额,该方法返回一个BigNumber对
象。需要在BigNumber对象上调用toString,把它转换成数字字符串。
BigNumber.js不能正确处理有超过20个浮点数位的大数字,因此推荐以wei为单位存储余
额,在显示时再转换成其他单位。web3.js自身总是以wei为单位返回和调取余额。例如,getBalance方法以wei为单位返回该地址的余额。4.1.5 单位转换
web3.js提供了把wei余额转换成任何其他单位和把任何其他单位余额转换成wei的API。
web3.fromWei方法用于将wei转换成其他单位,而web3.toWei方法用于将以其
他单位表示的数字转化成以wei为单位的数字。示例如下:
第一行代码将wei转换为ether;第二行代码将ether转换为wei。方法中的第二个实参可以
是以下字符串之一:
·kweiada
·mweibabbage
·gweishannon
·szabo
·finney
·ether
·k ethergrandeinstein
·mether
·gether
·tether4.1.6 检索gas价格、余额和交易细节
让我们看看API如何检索gas价格、地址余额和交易信息:
输出如下:
上述方法的执行过程如下:
·web3.eth.gasPrice。由x个最新区块的gas价格中位数决定gas价格。
·web3.ethgetBalance。返回任何给定地址的余额。所有web3.js API哈希地址应当是
十六进制的字符串,而不是十六进制的文字。solidity地址类型的输入也应当是十六进制的字
符串。
·web3.eth.getTransactionReceipt。用于获取交易使用其哈希的细节。如果在区块链
中发现交易,则返回交易收据对象;否则,返回null。交易收据对象包含下列属性:
·blockHash。该交易所在区块的哈希地址。
·blockNumber。该交易所在区块的序号。
·transactionHash。交易哈希。·transactionIndex。区块中交易索引位置的整数部分。
·from。发起人地址。
·to。接收者地址;如果是合约创建交易,则为null。
·cumulativeGasUsed。在区块中执行该交易时使用的gas总量。
·gasUsed。这个特定交易独自使用的gas量。
·contractAddress。如果交易是合约创建,表示被创建的合约地址;否则,为null。
·logs。该交易生成的日志对象数组。4.1.7 发送以太币
让我们看看如何向任意地址发送以太币。为了发送以太币,需要使用
web3.eth.sendTransaction方法。该方法可用于发送任意种类的交易,但主要用于发送以
太币,原因是使用这种方法部署合约或者调用合约方法比较麻烦——它要求生成交易数据而
不是自动生成交易数据。该方法的交易对象包含下列属性:
·from。发送账户的地址。如未标明,使用web3.eth.defaultAccount属性。
·to。可选项。信息目的地的地址,对于合约创建交易,该项未定义。
·value。可选项。通常在转账中单位为wei(在合约创建交易情况下,作为合约的资金注
入,单位也是wei)。
·gas。可选项。交易使用的gas量(未使用的gas被退回)。如果不提供,则自动决定该
项。
·gasPrice。可选项。交易中以wei为单位的gas价格,默认为网络平均gas价格。
·data。可选项。它或者是包含信息相关数据的字节字符串,或者是初始代码(在合约创
建交易情况下)。
·nonce。可选项。它是个整数。每一个交易都有一个相关计数nonce。该数字表示交易发
起人发送的交易数量。如果未提供nonce,则自动确定。它的作用是防止重播攻击。nonce不
是与挖区块相关的那个随机数。如果使用的nonce大于交易应当有的nonce,则交易被放入一
个队列直到其他交易数量到达。例如,如果下一个交易的nonce应该是4,而nonce被设为
10,则geth在广播这个交易之前将等待之间的6个交易。nonce为10的交易称为排队交易,而
不是待定交易。
向一个地址发送以太币的示例如下:
这里从账户0向账户1发送一个以太币。在运行geth时,确保使用unlock选项解锁两个账
户。在geth交互接口上,提示输入密码,但是如果账户被锁定,交互接口以外的web3.js API
将返回error。这个方法返回交易哈希。然后可以使用getTransactionReceipt方法检查是否
挖出了交易。
还可以用web3.personal.listAccounts、web3.personal.unlockAccount(addr,pwd)和web3.personal.newAccount(pwd)实时管理账户。4.1.8 处理合约
让我们学习如何部署一个新合约、如何使用一个已部署合约的地址获取其引用、如何向
合约发送以太币、如何发送交易以调用合约的函数(方法),以及如何估算一个函数调用的
gas。
若要部署一个新合约或者获取一个已部署合约的引用,首先需要使用
web3.eth.contract方法创建一个合约对象。该方法以合约ABI作为一个实参,并返回合约
对象。
创建合约对象的代码如下:
有了合约之后,可以使用合约对象的新方法部署它,或者使用at方法获取与ABI匹配
的、一个已部署合约的引用。
部署合约的示例如下:其中,new方法的调用是异步的,所以如果成功创建和广播交易,回调函数将被调用两
次。第一次,广播交易之后调用它;第二次,挖出交易之后调用它。如果不提供回调函数,则proof变量的address属性被设成undefined。挖出交易之后,address属性将被设置。
在proof合约中,没有构造函数,但是如果有构造函数,则构造函数实参应当放在new方
法的开头。传送的对象包含from地址、合约字节码和使用的gas上限。这三个属性必须存
在,否则无法创建交易。该对象可以有被传送给sendTransaction方法的对象所展示的属性,但是这里,data是合约字节码,且to属性被忽略。
可以用at方法引用一个已经部署的合约。相关代码如下:
现在让我们看看如何发送交易以调用合约方法。相关代码如下:这里调用方法同名对象的sendTransaction方法。被传送给这个sendTransaction方法的对
象属性与web3.eth.sendTransaction相同,只是data和to属性被忽略了。
如果想调用节点本地的方法,而非创建交易并广播,则可使用call而非sendTransaction。
示例如下:
有时必须发现找到调用方法所需的gas,这样可以决定是否调用。web3.eth.estimateGas
可用于此目的。然而,直接使用web3.eth.estimateGas要求生成交易,因此可以使用方法
同名对象的estimateGas方法。示例如下:
如果只想发送一些以太币到合约,而不调用任何方法,则可以使用
web3.eth.sendTransaction方法。4.1.9 检索和监听合约事件
现在让我们看看如何监听一个合约事件。监听事件很重要,因为通过交易调用方法的结
果通常是以触发事件的形式返回的。
在了解如何检索和监听事件之前,我们需要学习事件的索引参数。一个事件最多
有三个参数可以有被索引(indexed)属性。该属性用于提示节点对它进行索引,这样应用客
户端可以用匹配返回值来检索事件。如果不使用indexed属性,则必须检索所有事件,并筛选
出需要的那些事件。例如,可以这样编写logFileAddedStatus事件:
下面是给出了监听合约事件的一个示例:上述代码的执行过程如下:
1)调用一个合约实例的事件同名的方法获取事件对象。该方法用两个对象作为实参,用于筛选事件:
·第一个对象用索引返回数值筛选事件。例如,{'valueA':1,'valueB':
[myFirstAddress,mySecondAddress]}。所有筛选数值都默认设置为null。这意味着它们将匹
配该合约发出的任意类型事件。
·第二个对象可以包含三个属性,即fromBlock(搜索起始区块,默认为latest)、toBlock(搜索截至区块,默认为latest)和address(仅获取日志的地址列表;默认为合约地
址)。
2)事件对象显示三种方法:get、watch和stopWatching。get用于获取区块范围内的所有
事件。watch与get类似,但是它在获取事件后还监听变化。stopWatching可以用于停止监听变
化。
3)合约实例的allEvents方法用于检索合约的所有事件。
4)每一个事件由一个包含下列属性的对象代表。
·args。一个带有来自事件的实参的对象。
·event。用一个字符串表示事件名。
·logIndex。用一个整数表示区块中的日志索引位置。
·transactionIndex。用一个整数表示日志最初的交易索引位置。
·transactionHash。用一个字符串表示日志最初的交易哈希。
·address。用一个字符串表示日志最初的地址。
·blockHash。用一个字符串表示日志所在区块的哈希。如待定,则为null。
·blockNumber。日志所在区块的序号。如待定,则为null。
web3.js提供web3.eth.filter API以检索和监听事件。用户可以使用这个API,但是
处理事件的Event方法更简便。要想学习更多内容,请访问
https:github.comethereumwikiwikiJavaScript-APIweb3ethfilter 。4.2 为所有权合约创建客户端
在前一章中,我们为所有权合约编写了Solidity代码;在前一章和本章中,我们学习了
web3.js的有关知识和使用web3.js调用合约的方法。现在是时候为智能合约创建客户端了,这样方便用户使用。
创建一个客户端,用户从中选择一个文件,输入所有者细节,然后按下Submit按钮广播
交易,用文件哈希和所有者的细节调用合约的set方法。一旦交易被成功广播,将显示交易哈
希。用户还能够选择一个文件,并从智能合约中得到所有者的细节。客户端还将实时显示最
新挖出的set交易。
我们将在前端使用sha1.js获取文件哈希,使用jQuery进行DOM操纵,并使用Bootstrap 4
创建一个反应层(responsive layout)。在后端使用express.js和web3.js。我们将使用
socket.io,这样不需要前端间隔相等的时间请求数据,后端就把最近挖出的交易推到前端。
web3.js可以在前端使用,但对于应用是个安全漏洞。也就是说,我们在使用存储
在geth中的账户,并把geth节点URL显示给前端,这将使存储在那些账户中的以太币面临风
险。4.2.1 项目结构
在本章的练习文件中,将发现两个目录:Final和Initial。Final包含项目的最终源代码,而
Initial包含可以用于迅速创建应用的空的源代码文件和库。
为了测试Final目录,需要在其中运行npm install,并把app.js中硬编码的合约地址
替换为在部署合约之后得到的合约地址。然后,使用Final目录中的node app.js命令运行该应
用。
在Initial目录中,将发现一个public目录和两个文件(app.js和pack age.json)。
package.json包含应用的后端相关内容,app.js包含应用的后端源代码。
public目录包含与前端相关的文件。在publiccss中会发现bootstrap.min.css,它是
Bootstrap库;在publichtml中会发现index.html,所应用的HTML代码放在这里;在publicjs目
录中将发现jQuery、sha1和sock et.io的JS文件。在publicjs中还会发现一个main.js文件,应用
的前端JS代码放在这里。4.2.2 创建后端
先创建App后端。首先,在initial目录中运行npm install,为后端安装所需相关内容。其
次,在进行后端编码之前,确保geth运行时启用rpc。如果是在私有网络上运行geth,要确保
启用mining。最后,确保账户0存在并被解锁。可以在私有网络上运行geth,这时需要启用
rpc和mining,并解锁账户0:
编码开始前最后需要做的一件事是,使用在前一章中见到的代码部署所有权合约,并复
制合约地址。
现在创建一个单独的服务端,它将为浏览器提供HTML,并接收socket.io连接:
这里把运行在端口8080上的两个服务端express和socket.io合并成一个服务端。
现在创建路径以用于静态文件和App主页。相关代码如下:
这里使用了express.static中间件,用于在公共目录中发现静态文件。
现在连接到geth节点,并获取已部署合约的引用,这样可以发送交易并监听事件。相关
代码如下:上述代码就是用得到的合约地址替换原有的合约地址。
现在创建广播交易和获取文件信息的路径。相关代码如下:
其中,“submit”路径用于创建和广播交易。获取交易哈希之后,把它发送给客户端。然
后等待挖出交易。“getInfo”路径用于调用节点自身的合约get方法,而非创建交易。它仅仅发送回所得到的回应。
现在监听来自于合约的事件,并向所有客户端广播。相关代码如下:
这里需要检查一下状态是否为true,如果为true,才能向所有连接的socket.io客户端广播
事件。4.2.3 创建前端
让我们从应用的HTML开始创建前端。把下面的代码放入index.html文件:上述代码的执行过程如下:
1)显示Bootstrap的文件输入框,这样用户可以选择一个文件。
2)显示一个文本框,用户可以输入所有者的细节。
3)得到两个按钮。一个用于存储文件哈希和合约中的所有者细节,另一个用于从合约
中获取文件信息。单击Submit按钮触发submit方法,单击Get Info按钮触发getInfo方
法。
4)得到一个显示信息的报警框。
5)显示一个有序列表,以显示用户在该页面上时被挖出的合约交易。
接下来为getInfo和submit方法编写实现,与服务端建立socket.io连接,并从服务
端监听sock et.io信息。
相关代码如下。把该代码放入main.js文件:上述代码的执行过程如下:
1)定义submit方法。在submit方法中,确保选择一个文件,且文本框不为空,然后
读取文件内容作为数组缓存,并传送数组缓存给sha1.js显示的sha1方法,以获取数组缓
存中的内容哈希。得到哈希之后,使用jQuery发出一个AJAX请求给“submit”路径,然后在
报警框中显示交易哈希。2)定义getInfo方法。该方法首先确定选中一个文件,然后就像之前一样生成哈
希,并发出请求到“getInfo”端点,以得到关于那个文件的信息。
3)使用sock et.io库显示的io方法建立socket.io连接,然后等待事件连接到触发器——
这表示连接已经建立。在连接建立之后,监听来自服务端的信息,并向用户显示交易细节。
之所以不在以太坊区块链中存储文件,是因为存储文件很昂贵——它需要大量
gas。对于本节的示例子,其实不需要存储文件,因为网络中的节点将可以看见文件。因此,如果用户希望文件内容是秘密的,其实是做不到的。这里的应用是想证明一个文件的所有
权,而不是像云服务那样存储和服务文件。4.2.4 测试客户端
运行app.js节点,以运行应用服务端。打开浏览器,访问http:localhost:8080 ,可以看
到图4-1所示的界面。图 4-1
现在选择一个文件,输入所有者姓名,单击Submit按钮,界面将变为图4-2所示的样子。图 4-2
在这里可以看到显示交易哈希。现在等待,直到交易被挖出。一旦挖出,就可以在当前
交易列表中看到交易,如图4-3所示。图 4-3
现在再次选择同一个文件,单击Get Info按钮,界面如图4-4所示。图 4-4
在这里可以看到时间戳和所有者的细节。至此,为第一个DApp创建客户端的工作就完
成了。4.3 总结
在本章中,我们首先通过示例学习了web3.js的基础知识,包括如何连接至节点、基础
API、发送不同种类的交易以及监听事件,最后为所有权合约建立了一个适合生产用途的客
户端。现在可以编写智能合约和创建UI客户端了。
在下一章中,我们将创建钱包服务,可供用户在其中方便地创建和管理以太坊钱包,这
也是离线的。我们将专门使用LightWallet库实现上述目的。第5章 创建钱包服务
钱包服务用于发送和接收钱款。创建钱包服务面临的主要挑战是安全和信任。用户必须
觉得他的钱是安全的,并相信钱包服务管理员不会偷他的钱。本章所涉及的钱包服务将处理
这些问题。
本章将讲解以下内容:
·在线钱包和离线钱包的区别。
·用Hook ed-Web3-Provider和EthereumJS-tx库使创建和签署那些没有被以太坊节点管理的
账户交易变得容易。
·理解HD钱包的概念及其使用方法。
·使用LightWallet.js创建HD钱包和交易签名者。
·创建钱包服务。5.1 在线钱包和离线钱包的区别
钱包是多个账户的集合,账户是一个地址及其相关私钥的集合。
如果一个钱包与互联网相联,则称其为在线钱包。例如,在geth中存储的钱包、任何网
站数据库等都称为在线钱包。在线钱包也称为热钱包、Web钱包、托管钱包等。不推荐使用
在线钱包,至少在存储大量以太币或者长期存储以太币时不推荐使用,因为有风险。而且根
据钱包存储位置的不同,它还可能要求信任第三方。
例如,最热门的钱包服务本身存储钱包私钥,并允许用户通过e-mail和密码访问钱包,所以用户基本上不会实质性地访问钱包,如果有人想偷,就能偷钱包里的钱。
如果一个钱包不与互联网相联,则称其为离线钱包。例如,存储在闪存盘、纸张、文本
文件等中的钱包。离线钱包也称为冷钱包。离线钱包比在线钱包更安全,因为要偷钱的人必
须能够访问物理内存。离线存储的问题是,用户需要找到一个不会意外删除或者忘记的位
置,或者让其他任何人都不能访问的位置。如果想长期安全地保管钱款,许多人会在纸上存
储钱包,然后把纸放入保险箱。如果想从账户频繁地发送钱款,则可以存在带有密码保护的
闪存盘和保险箱里。用数字设备存储钱包有点危险,因为数字设备可能随时坏掉,那样就无
法访问钱包了。这就是为什么既要存在闪存盘中,还应当存在保险箱里。根据需求的不同,用户还可以找到更好的解决方法,但是必须确保方法安全,且不会意外地丢失对钱包的访问
路径。5.2 Hooked-Web3-Provider和EthereumJS-tx库
到目前为止,Web3.js库的sendTransaction方法的所有例子都使用以太坊节点出现的
from地址,因此以太坊节点能够在广播之前签署交易。但是如果用户把钱包的私钥存储在其
他地方,geth就发现不了它。因此在这种情况下,需要使用web3.eth.sendRawTransaction
方法广播交易。
web3.eth.sendRawTransaction用于广播原始交易,也就是说,用户不得不编写代码
来创建和签署原始交易。以太坊节点将直接广播,而不对交易做任何其他操作。但是使用
web3.eth.sendRawTransaction编写代码以广播交易并非易事,因为它要求生成数据部
分、创建原始交易并签署交易。
Hooked-Web3-Provider库提供自定义程序提供方(custom provider),它使用HTTP与
geth通信。这个提供方的独特之处在于,它允许使用密钥签署合约实例的
sendTransaction调用,因此不再需要创建交易的数据部分了。自定义程序提供方事实上
重写了web3.eth.sendTransaction方法的实现,所以基本上它允许签署合约实例的
sendTransaction调用以及web3.eth.sendTransaction调用。合约实例的
sendTransaction方法在内部生成交易数据,并调用web3.eth.sendTransaction广播交
易。
EthereumJS是一系列与以太坊相关的库。EthereumJS-tx是其中之一,它提供了多种与交
易相关的API,例如,允许创建原始交易、签署原始交易、检查交易是否正确使用密钥进行
了签名,等等。
这两个库对node.js和客户端JavaScript可用。访问https:www.npmjs.compack agehooked-
web3-provider 可下载Hook ed-Web3-Provider,访问
https:www.npmjs.compack ageethereumjs-tx 可下载EthereumJS-tx。
在写本书时,Hooked-Web3-Provider的最新版本是1.0.0,EthereumJS-tx的最新版本是
1.1.4。
下面来看如何使用这些库从一个不由geth管理的账户发送交易。上述代码的执行过程如下:
1)创建一个Hook edWeb3Provider实例(由Hook ed-Web3-Provider库提供)。该构造函
数有一个对象,这个对象有两个必须提供的属性host和transaction_signer。host是节点的HTTP
URL,transaction_signer是自定义服务提供方用于签署交易的通信对象。
2)transaction_signer对象有两个属性hasAddress和signTransaction。调用hasAddress检查
交易是否可以签署,即检查交易签署者是否有from地址账户的私钥。该方法接收地址和一个
回调函数。如果找不到地址私钥,回调函数的第一个实参应当是错误信息,第二个实参应当
是false;如果找到地址私钥,第一个实参应当是null,第二个实参应当是true。3)如果发现地址私钥,则自定义程序提供方调用signTransaction方法得到交易签名。该
方法有两个参数,即交易参数和回调函数。在这个方法中,首先将交易参数转换为原始交易
参数,也就是说,将原始交易参数数值编译为十六进制的字符串。然后创建一个缓存存储私
钥。缓存使用EthJS.Util.toBuffer方法创建,该方法是EthereumJS-util库的一部分。
EthereumJS-util库由EthereumJS-tx库导入。接下来创建一个原始交易并签名,之后编序号,并转换成十六进制字符串。最后需要用回调函数为自定义服务提供方提供签名原始交易的十
六进制字符串。该方法发生错误时,回调函数的第一个实参应当是一个错误信息。
4)自定义服务提供方进行原始交易,并用web3.eth.sendRawTransaction进行广播。
5)调用web3.eth.sendTransaction函数向另一个账户发送若干以太币。这里需要提供
nonce以外的所有交易参数,因为自定义服务提供方可以计算nonce。此前,许多参数都是可
选项,因为把它们留给了以太坊节点进行计算,但是由于这里要自己签署,就需要提供所有
交易参数。如果交易没有任何相关数据,则gas总是21000。
公钥的情况呢?
在上述代码中,并未提及签署地址的公钥。如果矿工没有公钥,该如何验证交易的真实
性?矿工使用了ECDSA算法的一个独特属性,该属性允许矿工通过信息和签名计算公钥。在
交易中,信息表示交易意向,签名用于发现签署信息时是否使用了正确的私钥。这是ECDSA
算法的独特之处。EthereumJS-tx提供一个API用于验证交易。5.3 分层确定性钱包
分层确定性钱包(Hierarchical Deterministic wallet,HD钱包)是由一个单独的起点(称
为seed,即种子)衍生的地址和密钥的集成系统。确定性表明对于相同的seed生成相同的地
址和密钥,分层表明地址和密钥以相同顺序生成。它使备份和存储多个账户变得容易,因为
用户只需要存储seed,而不用存储单个密钥和地址。
为什么用户需要多个账户?
为什么用户需要多个账户?因为要隐藏财产。账户余额在区块链中公开可用,所以,如
果用户A与用户B分享地址以接收一些以太币,则用户B可以查看该地址中有多少以太币。因
此,用户通常在多个账户中存有财产。
HD钱包有多种类型,它们的种子格式以及生成地址和密钥的算法不同,例如BIP32、Armory、Coink ite、Coinb.in等。
什么是BIP32、BIP44和BIP39?
比特币改进提议(Bitcoin Improvement Proposal,BIP)是一个为比特币社区提供信息的
设计文档,它描述比特币的一个新功能或者其过程或者环境。BIP应当为该功能提供一份简
明技术规范和功能基本原理。在写本书时,有152个BIP。BIP32和BIP39分别提供关于实现
HD钱包和助记种子规范(mnemonic seed specification)的算法信息。
如要学习更多BIP的相关知识,请访问https:github.combitcoinbips 。5.4 密钥衍生函数
不对称的加密算法定义密钥的性质以及生成密钥的方法,因为密钥需要相互关联。例
如,RSA密钥生成算法是确定性的。
对称的加密算法只定义密钥长度。密钥由用户来生成。有多种算法生成密钥,其中一种
是KDF。
密钥衍生函数(KDF) 是用于从一些密值(例如主密钥、密码)中衍生对称密钥的确定
性算法。有多种类型的KDF,例如bcry pt、crypt、PBKDF2、scrypt、HKDF等。如要学习更
多KDF的相关知识,请访问https:en.wik ipedia.orgwik iKey _derivation_function 。
为了从一个密值生成多个密钥,可以对一个数执行拼接(concatenate)和递增
(increment)运算。
基于密码的密钥衍生函数用一个密码生成一个对称密钥。由于在实践中,用户通常使用
较弱的密码,基于密码的密钥衍生函数比较慢,占用了大量内存,使其难以启动强力攻击和
其他攻击。基于密码的密钥衍生函数使用广泛,因为难以记住密钥,且存储也有风险——有
可能被盗。PBKDF2是一个基于密码的密钥衍生函数的例子。
主密钥或者密码难以使用强力攻击破解;因此,如果你想从主密钥或者密码生成一个对
称密钥,可以使用不以密码为基础的密钥衍生函数,例如HKDF。HKDF比PBKDF2快得多。
为什么使用KDF,而不使用哈希函数?
哈希功能的输出可以当作对称密钥加密技术使用。所以你肯定在奇怪为什么需要KDF。
如果使用的是主密钥、密码或者强密码,那么使用一个哈希函数就行了。例如,HKDF仅使
用哈希函数生成密钥。但是如果不能保证用户使用强密码,最好使用哈希函数衍生的密码。5.5 LightWallet
LightWallet是一个实现BIP32、BIP39和BIP44的HD钱包。LightWallet提供API来创建和
签署交易,或者使用LightWallet生成的地址和密钥加密和解密数据。
LightWallet API被分成4个命名空间,即k eystore、signing、encry ption和txutils。signing、encrpy tion和txutils分别用来提供API以签名交易、非对称的密码和创建交易,而keystore命名
空间用于创建keystore、生成种子等。k eystore是一个存储加密种子和密钥的对象。如果使用
Hooked-Web3-Provider,keystore命名空间实现交易签名者方法,该方法要求签署
we3.eth.sendTransaction调用。因此k eystore命名空间对于在其中发现的地址可以自动创
建和签署交易。实际上,LightWallet的主要目的是成为Hooked-Web3-Provider的一个签名提
供方。
可以配置密钥存储实例,来创建和签署交易或者加密和解密数据。签署交易用secp256k1
参数,加密和解密用curve25519参数。
LightWallet的种子是一个12词的助记符,容易记住但不容易进行破解。它不是任意12个
词,而是LightWallet生成的种子。LightWallet生成的种子在选择词和其他东西方面有特定的
属性。
HD衍生路径
HD衍生路径是一个字符串,可以使多个加密货币(假设它们使用相同的签名算法)、多个区块链和多个账户等的处理变得容易。
HD衍生路径需要多少参数就可以有多少参数,还可以对参数使用不同的数值,可以产
生不同的地址群和相关密钥。
LightWallet默认使用m0' 0' 0'衍生路径。这里,n'是参数,n是参数值。
每个HD衍生路径都有curve和purpose。purpose可以是sign或者asymEncry pt。sign表示该
路径用于签署交易,asyEncrypt表示该路径用于加密和解码。curve表示ECC的参数。为了签
名,参数必须是secp256k 1;对于不对称加密,curve必须是curve25591,因为LightWallet出于
自身利益强迫我们使用这些参数。5.6 创建钱包服务
我们已经学习了关于LightWallet的理论,现在是时候用LightWallet和Hooked-Web3-
Provider创建钱包服务了。钱包服务将允许用户生成独一无二的种子,显示地址和相关余
额,最后将允许用户发送以太币给其他账户。所有操作都在客户端上进行,这样比较容易取
得用户的信任。用户必须记住种子或者把它存储在某个地方。5.6.1 必要条件
在开始创建钱包服务之前,应确保运行geth开发实例(即挖矿),它已启动了HTTP-
RPC服务器,允许来自任何域名的客户端请求,最后解锁账户0。运行下面的代码:
其中,--rpccorsdomain用于允许一些特定域与geth通信。需要提供一个以空格分隔的域
名列表,例如“http:localhost:8080 https:mySite.com ”。它还支持通配符。--rpcaddr表示
geth服务器可以到达哪个IP地址。默认的是127.0.0.1,所以如果它是一个托管服务器,就不
能使用服务器的公共IP地址到达它。因此,将它的值改为0.0.0.0,这表示该服务器可以使用
任何IP地址到达。5.6.2 项目结构
在本章的练习文件中,你将发现Final和Initial两个目录。Final包含项目的最终源代码,而
Initial包含可以用于迅速创建应用的空的源代码文件和库。
为了测试Final目录,需要在其中运行npm install,然后使用Final目录中的node
app.js命令运行该应用。
在Initial目录中,你将发现一个public目录和两个文件(app.js和package.json)。
package.json包含应用的后端相关内容,把后端源代码放在app.js里。
public目录包含与前端相关的文件。在publiccss中会发现bootstrap.min.css,它是bootstrap
库;在publichtml中会发现index.html,把应用的HTML代码放在这里;在publicjs目录中将发
现Hooked-Web3-Provider、web3js和LightWallet的.js文件。在publicjs中还会发现一个main.js
文件,把应用的前端JS代码放在这里。5.6.3 创建后端
先来创建App后端。首先,在Initial目录中运行npm install,为后端安装所需相关内容。
运行快捷服务并用于index.html文件和静态文件的完整后端代码如下:
上述代码无须解释。5.6.4 创建前端
现在开始创建App前端。前端所包括的主要功能有生成种子、显示种子地址和发送以太
币。
编写应用的HTML代码。把如下代码放入index.html文件中:上述代码的执行过程如下:
1)把Bootstrap 4样式表排入队列。
2)显示一个信息框,上面将显示多个信息。
3)得到一个表单,上面有一个输入框和两个按钮。输入框用于输入seed或者在生成新的
seed时显示seed。
4)Generate Details按钮用于显示地址,Generate NewSeed按钮用于生成一个新的、独
一无二的seed。用户单击Generate Details ......
区块链项目开发指南
Building Blockchain Projects:Develop Real-time Practical DApps Using
Ethereum and JavaScript
(印度)纳拉扬·普鲁斯蒂(Narayan Prusty) 著
朱轩彤 闫莺 董宁 译
ISBN:978-7-111-58400-1
本书纸版由机械工业出版社于2017年出版,电子版由华章分社(北京华章图文信息有限公
司,北京奥维博世图书发行有限公司)全球范围内制作与发行。
版权所有,侵权必究
客服热线:+ 86-10-68995265
客服信箱:service@bbbvip.com
官方网址:www.hzmedia.com.cn
新浪微博 @华章数媒
微信公众号 华章电子书(微信号:hzebook)目录 译者序
前言
第1章 去中心化应用
1.1 什么是DApp
1.1.1 去中心化应用的优点
1.1.2 去中心化应用的缺点
1.2 去中心化自治组织
1.3 DApp中的用户身份
1.4 DApp中的用户账户
1.5 访问中心化应用
1.6 DApp中的内部货币
1.7 什么是授权的DApp
1.8 热门的DApp
1.8.1 比特币
1.8.2 以太坊
1.8.3 超级账本项目
1.8.4 IPFS
1.8.5 Namecoin
1.8.6 达世币
1.8.7 BigChainDB
1.8.8 OpenBazaar
1.8.9 Ripple
1.9 总结
第2章 以太坊的工作原理
2.1 以太坊概览
2.2 以太坊账户
2.3 交易
2.4 共识
2.5 时间戳
2.6 随机数
2.7 区块时间
2.8 分叉
2.9 创世区块
2.10 以太币面值
2.11 以太坊虚拟机
2.12 gas
2.13 发现对等节点
2.14 Whisper和Swarm
2.15 geth
2.15.1 安装geth
2.15.2 JSON-RPC和JavaScript操作台2.15.3 子命令和选项
2.15.4 创建账户
2.16 以太坊钱包
2.17 浏览器钱包
2.18 以太坊的缺点
2.19 serenity
2.20 总结
第3章 编写智能合约
3.1 Solidity源文件
3.2 智能合约的结构
3.3 数据位置
3.4 什么是不同的数据类型
3.4.1 数组类型
3.4.2 字符串类型
3.4.3 结构类型
3.4.4 枚举类型
3.4.5 mapping类型
3.4.6 delete操作符
3.4.7 基本类型之间的转换
3.4.8 使用var
3.5 控制结构
3.6 用new操作符创建合约
3.7 异常
3.8 外部函数调用
3.9 合约功能
3.9.1 可见性
3.9.2 函数修改器
3.9.3 回退函数
3.9.4 继承
3.10 库
3.11 返回多值
3.12 导入其他Solidity源文件
3.13 全局可用变量
3.13.1 区块和交易属性
3.13.2 地址类型相关
3.13.3 合约相关
3.14 以太币单位
3.15 存在、真实性和所有权合约的证明
3.16 编译和部署合约
3.17 总结
第4章 开始使用web3.js
4.1 web3.js概述
4.1.1 导入web3.js4.1.2 连接至节点
4.1.3 API结构
4.1.4 BigNumber.js
4.1.5 单位转换
4.1.6 检索gas价格、余额和交易细节
4.1.7 发送以太币
4.1.8 处理合约
4.1.9 检索和监听合约事件
4.2 为所有权合约创建客户端
4.2.1 项目结构
4.2.2 创建后端
4.2.3 创建前端
4.2.4 测试客户端
4.3 总结
第5章 创建钱包服务
5.1 在线钱包和离线钱包的区别
5.2 Hook ed-Web3-Provider和EthereumJS-tx库
5.3 分层确定性钱包
5.4 密钥衍生函数
5.5 LightWallet
5.6 创建钱包服务
5.6.1 必要条件
5.6.2 项目结构
5.6.3 创建后端
5.6.4 创建前端
5.6.5 测试
5.7 总结
第6章 创建智能合约部署平台
6.1 计算一个地址的交易nonce
6.2 solcjs概述
6.2.1 安装solcjs
6.2.2 solcjs API
6.3 创建合约部署平台
6.3.1 项目结构
6.3.2 创建后端
6.3.3 创建前端
6.3.4 测试
6.4 总结
第7章 创建投注App
7.1 Oraclize概述
7.1.1 Oraclize的工作原理
7.1.2 数据源
7.1.3 真实性证明7.1.4 定价
7.1.5 开始使用Oraclize API
7.1.6 加密查询
7.1.7 Oraclize Web IDE
7.2 处理字符串
7.3 创建投注合约
7.4 为投注合约创建客户端
7.4.1 项目结构
7.4.2 创建后端
7.4.3 创建前端
7.4.4 测试客户端
7.5 总结
第8章 创建企业级智能合约
8.1 探索ethereumjs-testrpc
8.1.1 安装和使用
8.1.2 可用RPC方法
8.2 什么是事件主题
8.3 开始使用truffle-contract
8.3.1 安装和导入truffle-contract
8.3.2 建立测试环境
8.3.3 truffle-contract API
8.4 truffle概述
8.4.1 安装truffle
8.4.2 初始化truffle
8.4.3 编译合约
8.4.4 配置文件
8.4.5 部署合约
8.4.6 单元测试合约
8.4.7 包管理
8.4.8 使用truffle的操作台
8.4.9 在truffle环境中运行外部脚本
8.4.10 truffle的创建管线
8.4.11 truffle的服务器端
8.5 总结
第9章 创建联盟区块链
9.1 什么是联盟区块链
9.2 什么是权威证明共识
9.3 parity概述
9.3.1 Aura的工作原理
9.3.2 运行parity
9.3.3 创建私有网络
9.3.4 许可和隐私
9.4 总结译者序
从去年开始,“区块链”成了一个高热度的关键字,受到各行各业的关注。越来越多的人
渴望用区块链这一变革性技术来解决商业中的关键问题。自然,有更多的人渴望深入了解和
运用区块链技术甚至开发自己的区块链应用。最近一年多,总是有很多朋友和学生问
我:“如何学习以太坊?有什么资料推荐吗?”通常我的回答就是:“从以太坊白皮书和黄皮
书看起吧。”显然,仅仅精读两篇文章是不够的,要想对区块链有进一步的认识,还需要更
多的知识储备。但是目前国内很难找到一本系统介绍区块链技术和开发平台的书籍。作为在
区块链领域具有较高知名度和丰富从业经验的专家团队,我们非常希望能给大家提供一套系
统的学习资料。
作为这场区块链技术热潮的弄潮儿,以太坊是最先进区块链技术的代表。以太坊的社区
和开发工具都相对比较完善和活跃。正因如此,很多企业级区块链解决方案都在积极地拥抱
以太坊。但是很遗憾,系统介绍以太坊的中文资料非常匮乏。首次接触到《Building
Blockchain Projects》这本英文书后,我们便感觉它是关于以太坊在广度上难得的技术资料,于是想尽快呈现给国内的读者。在翻译过程中,我们在保证表述流畅的同时,对原著的内容
进行了验证,并对其中的错误进行了修正。因此,本书应该比英文原版书更加易懂和准确。
在本书中,读者将了解如何编写智能合约、如何用JavaScript开发以太坊程序,以及如何
为区块链创建端到端应用。
本书具有如下特点:
·“学生导向”,跟着这本书可以由浅及深地学习以太坊技术应用。
·给出了多个真实的以太坊智能合约编写示例,可帮助初学者迅速上手编写代码。
·通俗易懂,讲解细致,方便自学。
在翻译本书的同时,我们的团队没有停止前进的脚步。我们不断努力,以求在技术深度
上更进一步。读者掌握本书的内容后,可以阅读我们即将于近期出版的其他关于以太坊和
Hyperledger的书,以加深对区块链的关键技术的认识。详情请见我们的微信公众号“智链
ChainNova”。前言
区块链是一个防篡改的去中心化账本,其中包含不断增长的数据记录列表。每个用户都
可以连接到网络,发送新的交易、验证交易和创建新的区块。
本书将阐释区块链的概念,讲述其如何保证数据真实性,以及如何使用以太坊创建现实
世界的区块链项目。通过有趣的现实世界案例,读者将了解如何编写完全按照程序运行、没
有欺诈、没有中心机构或者第三方干预的智能合约,并学习如何创建端到端的区块链应用。
本书还将介绍加密货币中的密码学、以太币安全、挖矿、智能合约和Solidity等概念。
区块链是比特币中最有创造性的技术,是记录比特币交易的公共账本。
本书内容
第1章阐释DApp的概念,并简述其工作原理。
第2章阐释以太坊的工作原理。
第3章阐释如何编写智能合约和使用geth交互接口来部署合约,以及使用web3.js广播交
易。
第4章介绍web.3js的概念及其导入方法、连接到geth的方法,并阐释了如何在node.js或
者客户端JavaScript使用它。
第5章阐释如何创建钱包服务,以方便用户创建和管理以太坊钱包,甚至离线创建和管
理钱包。我们将专门使用LightWallet库实现。
第6章展示如何使用web3.js编译智能合约,以及使用web3.js和EthereumJS部署智能合
约。
第7章阐释如何使用Oraclize从以太坊智能合约发出HTTP请求,以访问万维网中的数
据。我们还将学习访问存储在IPFS中的文件、使用字符串库处理字符串等方法。
第8章阐释如何使用truffle。truffle将使创建企业级DApp变得容易。我们将通过创建代币
来学习truffle。
第9章阐释创建联盟区块链的方法。
设备环境
Windows 7 SP1+、Windows 8、Windows 10或者Mac OS X 10.8+。
读者对象
本书适合想使用区块链和以太坊创建防篡改数据(和交易)应用的JavaScript开发人员阅读,也适合对密码学及其逻辑以及相关数据库感兴趣的人阅读。
下载实例代码
可以从http:www.packtpub.com 下载本书的实例代码文件。如果您在其他地方购买了本
书,可以访问http:www.hzbook.com 注册并下载。第1章 去中心化应用
我们以前用过的所有互联网应用几乎都是中心化的,即每个应用的服务端由一个特定企
业或个人所有。长期以来,开发人员创建中心化应用,用户使用中心化应用。但是中心化应
用存在一些问题,包括不透明、有单点故障、不能防止网络审查等,导致几乎不可能创建某
些特定类型的应用。为了解决这些问题,一项新的技术诞生了,它创建以网络为基础的去中
心化应用(DApp) 。在本章中,我们将学习去中心化应用。
在本章中,我们将讲解以下内容:
·什么是DApp。
·去中心化、中心化和分布式应用之间的区别。
·中心化和去中心化应用的优点和缺点。
·概述一些最热门的DApp所使用的数据结构、算法和协议。
·学习一些创建在其他DApp之上的流行DApp。1.1 什么是DApp
DApp是一种互联网应用,其后端在去中心化的点对点网络上运行,且其源代码是开源
的。网络中不存在能够完全控制DApp的节点。
根据DApp的功能不同,使用不同的数据结构来存储应用数据。例如,比特币DApp使用
区块链数据结构。
这些对等节点(peer)可以是网络中的任何计算节点,因此,发现和防止节点对应用数
据进行非法篡改或者与其他人分享错误信息是一个重要挑战,所以需要对等节点之间有一些
关于某个节点发布的数据是否正确的共识。在DApp中,没有一个中心服务器来协调节点,或者决定什么是对、什么是错,因此应对这个挑战确实不容易。一致性协议(concensus
protocol)可用于解决这个问题。不同的DApp通常使用不同数据结构类型的共识协议,例如
比特币使用工作量证明协议(PoW)来达成共识。
为了让用户使用DApp,每一个DApp都需要一个客户端(client)。使用DApp时,用户首先需要运行DApp中自己的节点服务端,然后将客户端连接至节点服务端。DApp的节
点只提供应用程序编程接口(Application Programming Interface,API),并允许开发者社
区使用API开发多种客户端。一些DApp开发人员会提供一个官方的客户端。DApp客户端应
该是开源的,并可以被下载使用,否则整个去中心化的想法就失败了。
但是建立客户端架构比较麻烦,如果用户不是开发人员,就更麻烦。因此,客户端通常
作为服务和或节点形式出现,以便让使用DApp的过程更容易。
什么是分布式应用?
分布式应用是指应用分布在多个服务端上,而非只有一个服务端。当应用数据和通信量
变得巨大,且应用的停机时间难以承受时,分布式是必要的。在分布式应用中,数据在多个
服务端中备份,以具有较高可用性。中心化应用可能是分布式的,也可能不是分布式的,但
去中心化应用肯定是分布式的。例如Google、Facebook、Slack、DropBox等是分布式的,而
简单的投资组合网站或者个人微博通常不是分布式的,除非通信量很大。1.1.1 去中心化应用的优点
中心化应用的一些优点如下:
·DApp能容错,没有单点故障,因为它们默认是分布式的。
·防止某单一机构的干扰。因为没有一个中心机构,任何第三方机构无法向中心机构施压
逼迫其删除一些内容。甚至没有单一机构能关闭应用的域名或者IP地址,因为DApp不是通
过一个特定的IP地址或者域名访问的。或许某些机构可以通过IP地址追踪网络中的单个节点
并关闭它,但是如果网络很庞大,则几乎不可能关闭应用。
·用户容易相信该应用。因为它不是由某个通过欺骗用户来牟利的机构所控制的。1.1.2 去中心化应用的缺点
显然,每个系统都不是完美的。去中心化应用的一些缺点如下:
·修改bug或者更新DApp很困难,因为网络中的每一个节点都需要更新其节点软件。
·一些应用要求验证用户身份(即KYC),却没有中心化的机构来验证用户身份,开发
应用时会遇到问题。
·创建去中心化应用比较困难,因为它们应用复杂的协议达成共识,且必须从最开始就自
行创建并扩大规模。所以我们不能仅仅实现一个想法,然后不断添加功能,使其规模扩大。
·应用通常独立于第三方API,以获取或者存储数据。DApp不能依赖中心化应用API,但
是可以依赖其他DApp。因为目前DApp的生态圈还不太大,所以创建起来比较困难。尽管
DApp理论上可以依赖其他DApp,但在实践中紧密融合DApp仍比较困难。1.2 去中心化自治组织
一般来说,被签署的文件可以代表组织,而且政府能对它们产生影响。根据组织类型的
不同,组织可能有股东,也可能没有股东。
去中心化自治组织 (Decentralized Autonomous Organization,DAO)是由计算机程序代
表的组织(即组织根据程序中写明的规则运行),完全透明,完全由股东控制,不受政府影
响。
为了达到这些目标,我们需要把DAO作为DApp来开发。因此,我们可以说DAO是
DApp的一个子类。
Dash和DAC是DAO的一些例子。
什么是去中心化自治公司(DAC)?
DAC和DAO尚无很明显的差别。很多人认为它们是一样的,有些人则在DAO为股东谋
取利益时将DAC定义为DAO。1.3 DApp中的用户身份
DApp的主要优点之一是它一般能保证用户的匿名性,但是许多应用要求用户必须经过
身份验证这个过程才能使用应用。因为DApp中没有中央机构,验证用户身份成了一个挑
战。
在中心化应用中,人们要求用户提交特定的扫描文件、OTP验证等,再验证用户身份。
该过程称为Know Your Customer(KYC)。但是由于DApp中没有人负责验证用户身份,所
以DApp不得不自己验证用户身份。DApp显然不能理解和验证扫描文档,也不能发送短信,因此需要用户提供那些它们可以理解和验证的数字标识。主要问题是几乎没有DApp有数字
身份,只有少数人知道如何得到数字身份。
数字身份有多种形式。目前最受推崇、最热门的形式就是数字证书。数字证书(也称为
公钥证书或者标识证书)是一个用来证明公钥所有权的电子文档。基本上,一个用户拥有私
钥(private k ey)、公钥(public key)和数字证书(digital certificate)。私钥是秘密的,用
户不应当与其他人分享;公钥可以与其他人分享;数字证书包含公钥和谁拥有公钥的信息。
显然,生产这种证书并不难,因此数字证书总是由用户可以信任的授权机关颁发。数字证书
有一个加密部分是用证书颁发机构(certificate authority)的私钥加密的。为了验证证书的真
实性,我们只需要使用证书颁发机构的公钥解码该部分,如果成功解码,那么证书就是合法
的。
即使用户成功获得了数字身份并得到DApp验证,还是有一个关键问题,那就是有各种
各样的数字证书颁发机构。为了验证一个数字证书,我们需要该证书颁发机构的公钥。掌握
所有证书颁发机构的公钥、更新添加新的证书颁发机构的公钥是很困难的。因此,数字身份
验证程序通常在客户端里,这样可以方便更新。但仅把验证程序转移到客户端并不能彻底解
决问题,因为有很多颁发数字证书的机构,跟踪所有机构并把它们加到客户端是很麻烦的。
为什么用户不验证彼此的身份?
在现实生活中,用户做交易时,通常会自己验证对方的身份或者可以请一个机构来验证
身份,这个想法也可以应用到DApp中。在进行交易之前,用户可以手动验证彼此的身份,这个想法适用于人们彼此进行交易的DApp。假设有一个DApp是去中心化的社交网络,显然
可以通过这种方式验证身份信息。假设一个DApp是用来买卖商品的,在支付之前买卖双方
可以验证彼此的身份,尽管这个想法看起来是可行的,但是在实践中很难实现,因为你可能
并不想每次进行交易的时候都验证身份,也不是每个人都知道如何验证身份。例如,假设有
一个DApp是打车软件,用户显然不想每次叫出租车之前都进行身份验证。但如果只是偶尔
交易,也知道怎样验证身份,就可以按程序验证身份。
由于这些原因,我们目前剩下的可选择方案是,由提供客户端的公司派人手动验证用户
身份。例如,创建一个比特币账户不需要身份证明,但是当提取比特币并兑换成货币时,交
易所会要求提供身份证明。客户端可以忽略未经验证的用户,不让他们使用,也可以对已经
身份验证的用户开放使用。这个解决办法也会产生一些小问题,即如果转换客户端,会发现交互的用户不一样了,因为不同的客户端有不同的验证用户集。因此,所有用户可能决定只
使用一个特定客户端。但这不是一个很大的问题,因为如果客户端不能有效验证用户,用户
就可以方便地转向另一个客户端,而且不丢失关键数据,因为关键数据的存储是去中心化
的。
在应用中验证用户身份的想法是,使用户在进行一些欺诈行为之后难以逃脱,防
止有欺诈犯罪背景的用户使用应用,以及为网络中的其他用户提供相信某个用户就是他自称
的人的方法。用什么过程来验证用户身份并不重要,用户总有办法伪装成其他人;用数字身
份或者用扫描文件进行验证并不重要,因为二者都可能被盗或者被重复使用。重要的是让用
户难以伪装成其他人,并收集足够的数据追踪用户,证明该用户进行了一些欺诈行为。1.4 DApp中的用户账户
许多应用需要用户账户功能。与账户相关的数据只能由账户所有者进行修改。DApp和
中心化应用不一样,DApp没有以用户名和以密码为基础的账户功能,因为密码不能证明账
户中的数据变化是由账户所有者发出的请求导致的。
有多种方法能实现DApp中的用户账户,最热门的方式是使用公钥-私钥对(public-
private k ey pair)来代表一个账户。公钥的哈希(hash)是账户的唯一身份。为了改变账户中
的数据,用户需要用私钥签名。我们假设用户会安全地存储私钥。如果用户丢失私钥,就永
远不能访问账户了。1.5 访问中心化应用
DApp不能依赖于中心化应用,原因是存在单点故障。但是在一些情况下,并无其他办
法。例如,如果DApp想读取一场足球比赛的成绩,它从哪里得到数据呢?尽管DApp可以依
赖另一个DApp,但是国际足联(FIF A)为什么要创建一个DApp呢?国际足联不会仅仅因为
其他DApp想要数据,就创建一个提供成绩却没有回报的DApp。
所以在一些情况下,DApp需要从中心化应用中抓取数据。但主要问题是DApp如何知道
从一个域名中抓取的数据有没有被中间人篡改,数据是否还是真实的响应?根据DApp架构
的不同,解决办法也有所不同。例如在以太坊中,智能合约不能直接发出HTTP请求,为了
访问中心化API,可以使用Oraclize服务作为中间人。Oraclize为从中心化服务智能合约中抓
取的数据提供TLSNotary验证。1.6 DApp中的内部货币
中心化应用的所有者需要有盈利才能长期维护应用的运行。DApp虽然没有所有者,但
是和中心化应用一样,DApp节点需要硬件和网络资源才能维持运行。DApp节点需要一些有
用的回报来维持运行,于是内部货币登场了。大多数DApp都有内置内部货币,或者可以说
最成功的DApp都有内置内部货币。
共识协议决定节点收取多少内部货币。根据共识协议,只有为维护DApp安全和运行做
出贡献的那些特定节点可以赚取货币,只进行数据读取的节点没有回报。例如在比特币中,只有矿工(miner)成功挖矿才能赚取比特币。
最大的问题是,这是一种数字货币,为什么人们觉得它有价值?根据经济学原理,有供
需差就有价值。
让用户用内部货币付费才能使用DApp解决了需求问题。随着越来越多的用户使用
DApp,且需求不断增长,内部货币的价值也升高了。
货币总量恒定会使货币变得稀缺,从而使其价值更高。
货币是不断供应的,而非一次性供应所有货币。正因如此,新进入网络、使网络安全运
行的节点也能赚取货币。
DApp中内部货币的缺点
DApp有内部货币的唯一缺点是,DApp不能再免费使用了。免费是中心化应用占上风的
原因之一,因为中心化应用可以用广告赚钱,为第三方应用提供优质API,所以可以对用户
免费。
在DApp中不能加入广告,因为没有人去检查广告尺度;客户端还可能不展示广告,因
为展示广告对他们没有好处。1.7 什么是授权的DApp
到目前为止,我们学习了完全开放的免权限DApp,即任何人都不需要建立身份就可以
参与。
另一方面,授权的DApp并不对所有人开放。授权的DApp继承了免权限DApp的全部属
性,但需要权限才能参与到网络中去。各种授权的DApp用到的权限系统不同。
要加入一个授权的Dapp就需要权限,免权限DApp的共识协议可能在授权的DApp中并不
好用,因此授权的Dapp与免权限Dapp的共识协议是不同的。授权的DApp没有内部货币。1.8 热门的DApp
现在我们已经掌握了一些关于DApp是什么、它与中心化应用有何区别等知识,让我们
探索一些热门的、有用的DApp。学习这些DApp时,我们只要达到理解其工作原理和它们如
何处理不同问题的程度就够了,不用学得太深。1.8.1 比特币
比特币(bitcoin)是一种去中心化的货币,是最热门的DApp。它的成功展示了Dapp有
多么强大,并鼓励人们创建其他DApp。在了解比特币的细节以及为什么人们认为它是一种
货币之前,需要账本和区块链的概念。
1.什么是账本
账本(ledger)本质上是一个交易列表。数据库与账本不同。在账本中,我们只能添加
新的交易;在数据库中,我们可以添加、修改和删除交易。数据库可以用来实现账本。
2.什么是区块链
区块链(block chain)是用于创建去中心化账本的数据结构。区块链中的区块按序号排
列。区块包含一系列交易、前一个区块的哈希(hash)、时间戳(timestamp,表明区块的创
建时间)、区块回报(block reward)、区块序号(block number)等。每一个区块包含前一
个区块的哈希,由此创建了区块彼此相连的链。网络中的每一个节点都保留区块链的一个备
份。
工作量证明(proof-of-work)和权益证明(proof-of-stak e)等是用于保障区块链安全性
的多种共识协议。由于共识协议不同,创建区块和添加区块到区块链中的方式也不同。在工
作量证明中,通过挖矿创建区块,这让区块链保持安全。在工作量证明协议中,挖矿涉及解
决复杂问题。我们将在后面学习更多关于区块链及其共识协议的内容。
比特币网络中的区块链包含比特币交易。网络向成功挖出区块的节点奖励新的比特币。
区块链数据结构的主要优点是,它自动进行审计,并使应用安全透明,可以防止
欺诈和贪污。根据实现和使用方式的不同,它还可以用来解决许多其他问题。
3.比特币合法吗
首先,比特币不是一种内部货币,而是一种去中心化的货币。内部货币大部分都是合法
的,因为它们有资产且用途明确。
主要问题在于纯货币DApp是否合法。简要回答就是,许多国家认为它是合法的,少数
国家认为它是非法的,大部分国家对此还没有做出决定。
为什么少数国家认定它是非法的,大部分国家对此还没有做出决定呢?原因如下:
·由于DApp中的标识问题,用户账户没有任何标识将它们与比特币挂钩,因此,它可用
于洗钱。
·这些虚拟货币不稳定,所以人们丢钱的风险很高。·用虚拟货币很容易逃税。
4.为什么使用比特币
比特币网络仅用于发送接收比特币,没有其他用途。所以你一定在奇怪,人们为什么对
比特币有需求?
使用比特币的原因如下:
·可以在世界上任何地方快速便捷地发送和接收支付。
·比特币交易费低于在线支付交易费。
·黑客可以从商户那里窃取支付信息,但是在使用比特币的情况下,窃取比特币地址是完
全没用的,因为为了让交易合法,必须用相关私钥签名,而用户在支付时不需要和任何人分
享私钥。1.8.2 以太坊
以太坊(ethereum)是一个去中心化平台,可以在其上运行使用智能合约编写的
DApp。一个或多个智能合约可以一起构建DApp。以太坊智能合约是在以太坊上运行的程
序。智能合约完全按照程序运行,杜绝了停机、中心化操控、欺诈和第三方干涉的可能性。
使用以太坊运行智能合约的主要优点是方便智能合约彼此交互,而且不需要担心整合共
识协议等事情,只需编写应用所需逻辑即可。当然,不能用以太坊创建所有种类的DApp,只能创建以太坊支持其功能的那些DApp。
以太坊有一种内部货币叫作以太币(ether)。部署智能合约或者执行智能合约函数需要
用到以太币。
本书将使用以太坊创建DApp,并深入介绍以太坊的相关知识。1.8.3 超级账本项目
超级账本(Hy perledger)项目致力于开发创建授权的DApp技术。Hy perledger
fabric(或称simply fabric)是Hy perledger项目的一个实现。其他Hy perledger实现还有Intel
Sawtooth和R3 Corda等。
fabric是一个去中心化的授权平台,它允许在其上运行授权的DApp(叫作chaincode,账
链代码)。用户需要部署自己的fabric实例,然后在其上部署授权的DApp。网络中的每一个
节点都运行一个fabric实例。fabric是即插即用系统,可以方便地即插即用多种共识协议和功
能。
Hyperledger使用区块链数据结构。以Hy perledger为基础的区块链目前可以选择没有共
识协议(即NoOps协议),或者使用实用拜占庭容错算法(Practical Byzantine Fault
Tolerance,PBFT)共识协议。它有一个特殊节点叫作证书颁发机构,该节点用于控制谁能
加入网络和它们能做什么。1.8.4 IPFS
星际文件存储系统(InterPlanetary File System,IPFS)是一个去中心化的文件系统。
IPFS使用分布式哈希表(Distributed Hash Table,DHT)和Merk le有向无环图(Directed
Acyclic Graph,DAG)数据结构。它使用类似于BitTorrent(比特流)的协议来决定如何在
网络中移动数据。IPFS的一个高级功能是它支持文件版本管理。为了实现文件版本管理,它
使用了类似于Git的数据结构。
尽管被称为去中心化的文件系统,IPFS并不遵循文件系统的主要属性,即在文件系统
中,所存储的内容会一直保留到被删除之前。IPFS的工作原理不同——每一个节点并不存储
全部文件,存储的是需要的文件。如果一个文件不那么受欢迎,许多节点就没有这个文件,那么该文件很有可能从网络中消失。因此,许多人更喜欢把IPFS称为去中心化的、点对点的
文件共享应用。或者可以把IPFS当作完全去中心化的BitTorrent,也就是说,它没有追踪器,但有一些高级功能。
1.工作原理
当在IPFS中存储一个文件时,它被分成很多小于256KB的数据块(chunk),并生成每个
数据块的哈希。网络中的节点在一个哈希表中存储它们需要的IPFS文件及其哈希。
IPFS文件有4种类型:blob、list、tree和commit。blob代表一个实际存储在IPFS中的文件
的数据块。list代表完整的文件,因为它包含blob列表和其他列表。由于列表可以包含其他列
表,因此它帮助网络进行数据压缩。tree(树)代表目录,因为它包含blob列表、列表、其他
树和commit。commit文件代表其他文件的版本历史中的快照。由于list、tree和commit与其
他IPFS文件有连接,于是形成了一个Merk le DAG。
所以,用户如果想要从网络中下载文件时,只需要IPFS列表文件的哈希。如果想下载目
录,则只需要IPFS树文件的哈希。
因为每个文件都由一个哈希进行标识,所以文件名不容易记住。如果更新文件,就需要
与想下载该文件的所有人分享新的哈希。为了解决这个问题,IPFS使用IPNS功能,允许用自
行认证的名字或者人性化的名字指向IPFS文件。
2.Filecoin
阻碍IPFS成为去中心化文件系统的主要原因是节点只存储了它们需要的文件。
Filecoin(文档币)是一个类似于IPFS的去中心化文件系统,其中有内部货币激励节点存储文
件,由此提高文件可用性,并使其更像一个文件系统。
网络中的节点通过赚取文档币来租用磁盘空间,在存储检索文件时,需要花费文档币。
与IPFS技术一样,Filecoin使用区块链数据结构和数据可检索证明(Proof-of-
Retrievability,PoR)共识协议。在写本书之时,Filecoin仍在开发阶段,因此许多事情尚不明确。1.8.5 Namecoin
Namecoin是一个去中心化的键-值数据库。它也有内部货币,叫作域名币
(Namecoin)。Namecoin使用区块链数据结构和工作量证明共识协议。
在Namecoin中,可以存储数据的键-值对。为了注册键-值对,需要花费域名币。注册之
后,需要每35999个区块更新一次,否则与密钥相关的数值将失效。更新也需要花费域名
币。不需要更新密钥,也就是说,在注册之后不需要花费任何域名币来存储密钥。
Namecoin有一个命名空间(namespace)功能,允许用户组织不同种类的密钥。任何人
都可以创建命名空间,或者使用现有命名空间组织密钥。
最受欢迎的命名空间有a(应用特定数据)、d(域名规范)、ds(安全域名)、id(标
识)、is(安全标识)、p(产品)等。
bit域名
如要访问网站,浏览器应先发现与域名相关的IP地址。这些域名和IP地址映射被存储在
DNS服务端中,受大机构控制。因此,域名易于审查。如果网站在做非法勾当,或者导致某
些损失,或者出于一些其他原因,大机构通常会关闭域名。
正因如此,就需要一个去中心化的域名数据库。因为Namecoin就像DNS服务端一样存储
键-值数据,所以Namecoin可用于实现去中心化的DNS,而且已经用于该用途。d和ds命名空
间包含以.bit结尾的密钥,代表.bit域名。从技术上看,命名空间对于密钥没有任何命名协
定,但是Namecoin的所有节点和客户端同意该命名协定。如果想在d和ds命名空间存储非法
的密钥,那么客户端会将其滤掉。
支持.bit域名的浏览器需要查看Namecoin的d和ds命名空间,以发现与.bit域名相关的IP地
址。
d和ds命名空间之间的区别是:ds存储支持TLS的域名,而d存储不支持TLS的域名。我们
已经使DNS去中心化了,也可以使发放TLS证书去中心化。
这是TLS在Namecoin中的工作原理。用户创建自签名的证书,并在Namecoin中存储证
书。当一个对.bit域名支持TLS的客户端试图访问一个安全的.bit域名时,它将服务端返回的证
书哈希和Namecoin中的哈希存储进行匹配,如果匹配,则继续与服务端进行更多通信。
使用Namecoin形成的去中心化DNS是第一个解决Zook o triangle的办法。Zooko
triangle定义了有三个属性的应用,即去中心化、身份和安全性。数字身份不仅可以用于代表
一个人,还可以代表一个域、一个公司或者其他事物。1.8.6 达世币
达世币(Dash)是一种类似于比特币的去中心化货币。它使用区块链数据结构和工作量
证明共识协议,并解决了比特币面临的一些主要问题。以下是比特币面临的一些问题:
·交易需要几分钟完成,但在目前的环境下通常需要交易马上完成。这是因为比特币网络
的挖矿难度不断调整,平均每10分钟创建一个区块。在本书中,我们将在后面学习更多关于
挖矿的内容。
·尽管账户没有与其相关的身份,但是在交易所里用比特币和真实货币进行兑换或者用比
特币买东西都是可以追溯的,因此交易所或者商户可以把用户的身份透露给监管机构。如果
在自己的节点上运行发送接收交易,则ISP可以看见比特币地址,还可以用IP地址追踪所有
者,因为在比特币网络中广播的信息不是加密的。
达世币的目标是通过使交易几乎瞬间完成并隐藏交易账户的信息来解决上述问题,还可
以防止他人用ISP追踪所有者。
比特币网络中有两种节点,即矿工节点和普通节点。但Dash中有三种节点,即矿工节点
(miner node)、主节点(master node)和普通节点(ordinary node)。主节点是使Dash与
众不同的原因。
1.去中心化的治理和预算编制
要建立一个主节点,用户需要拥有1000个达世币和一个静态IP地址。在Dash网络中,主
节点和矿工都赚取达世币。挖出一个区块,45%收益归矿工,45%收益归主节点,剩余的
10%留给系统预算。
主节点使去中心化的治理和预算编制成为可能。由于去中心化的治理和预算编制系统,Dash被称为DAO,因为这就是它的确切含义。
网络中的主节点就像股东,也就是说,它们有权利决定剩余10%的达世币归谁。这10%
的达世币通常用于资助其他项目。
每个主节点都有能力使用一次投票权(vote)批准项目。对项目的讨论在网络以外进
行,但投票是在网络中进行的。
主节点为在DApp中验证用户标识提供了一种可能的解决办法,也就是说,主节
点可以民主地选择节点来验证用户标识。该节点背后的人或者单位可以手动验证用户文档。
回报的一部分还可以回到这个节点。如果该节点不提供良好的服务,那么主节点可以投票给
另一个节点。对于解决去中心化的标识问题来说,这不失为一个好办法。
2.去中心化服务主节点还形成一个提供多种服务的服务层,而非仅仅批准或者拒绝一个提案。主节点提
供服务的原因是它们提供的服务越多,网络的功能就越多,从而增加用户和交易。这样能提
高达世币的价值,使区块回报变得更高,由此帮助主节点赚取更多利润。
主节点提供诸如PrivateSend(提供匿名的混合币服务)、InstantSend(提供几乎即时交
易的服务)、DAPI(供去中心化API的服务,这样用户不需要运行节点)等服务。
在某个特定时间,只有10个主节点被选中。选择算法将使用当前区块的哈希选择这10个
主节点。然后,从这些主节点发出服务请求。从大部分节点接收的结果被认为是正确的,这
就是对主节点提供的服务达成共识的办法。
服务证明(Proof of Service,PoS)共识协议用于确保主节点在线、应答和更新区块
链。1.8.7 BigChainDB
BigChainDB允许用户部署自己的、授权的或者免权限去中心化数据库。它使用区块链数
据结构以及其他多种特定数据库数据结构。在写本书之时,BigChainDB仍处于开发阶段,所
以许多事情尚不明确。
BigChainDB还提供了许多其他功能,例如丰富的权限、查询、线性扩展以及支持多资产
和federation共识协议等。1.8.8 OpenBazaar
OpenBazaar是一个去中心化的电子商务平台,可以在其上买卖物品。OpenBazaar中的用
户不是匿名的,因为其IP地址被记录了。节点可以是买方、卖方或者中间人。
OpenBazaar使用Kademlia分布式哈希表数据结构。为了使这些项在网络中可视,卖方必
须建立节点并维持其运行。
OpenBazaar使用工作量证明共识协议防止账户被篡改。它使用proof-of-burn、CHECKLOCKTIME验证和基于保证金的共识协议,防止评分和评价被篡改。
买方和卖方用比特币进行交易。买方在购买时可以添加一个中间人。如果买卖双方有争
端,由中间人负责解决。任何人都可以是网络中的中间人,中间人通过解决争端赚取手续
费。1.8.9 Ripple
Ripple(瑞波)是一个去中心化的转账平台。它允许兑现货币、数字货币和大宗商品。
它使用区块链数据结构,并且有自己的共识协议。在Ripple相关文档中,找不到“区块”和“区
块链”等词汇;而是用“账本”(ledger)来代替。
在Ripple中,通过信任链进行钱和商品交换,方式类似于hawala网络。Ripple中有两种节
点,即网关(gateway)和普通节点。网关支持一种或多种货币和或商品的存取。为了在
Ripple网络中变成网关,需要作为网关的权限形成一个信任链。网关通常是已经注册的金融
机构、交易所、商人等。
每个用户和网关都有一个账户地址。每个用户需要把他们信任的网关地址添加到信任列
表中,形成一个信任网关列表。对于发现谁值得信赖,并没有任何共识,这完全依赖用户
——用户自行承担信任一个网关的风险,即便网关可以添加它们信任的网关列表。
来看一个例子:住在印度的用户X如何能够向住在美国的用户Y发送500美元。假设在印
度有一个网关XX,收取现金(实物现金或者在网上用卡支付)且只用印度卢比给用户Ripple
余额。X将访问XX办公室或者网站,存入30000印度卢比,然后XX广播交易说“欠X30000印
度卢比”。现在假设在美国有一个网关YY,它只允许美元交易且Y信任YY网关。假设网关
XX和YY不信任彼此。由于X和Y不信任一个共同的网关,XX和YY不信任彼此,导致XX和
YY不支持同样的货币,因此,X为了转账给Y,就需要发现一个中间人网关形成一个信任
链。假设还有一个网关ZZ,XX和YY都信任ZZ,ZZ支持美元和印度卢比。现在X可以发送
交易,将50000印度卢比从XX转给ZZ,ZZ把钱兑成美元,然后把钱发送给YY,让YY把钱
给Y。现在X欠Y500,YY欠Y500,ZZ欠YY500,XX欠ZZ 30000印度卢比。但是这都没
什么,因为它们互相信任,而此前X和Y不互相信任。但是只要XX、YY和ZZ想,它们随时
可以在Ripple之外转账,或者翻转交易扣除款项。
Ripple也有内部货币,叫作XRP(或瑞波币)。发送至网络中的每一个交易会耗费一些
瑞波币。由于瑞波币是Ripple自有的货币,它可以不需要信任就被发送给网络中的任何人。
在形成信任链时,可以使用瑞波币。记住,每一个网关有自己的货币汇率。瑞波币不是由挖
矿生成的;相反,最初就有1000亿个瑞波币,它们最初由Ripple公司拥有。出于多种原因,瑞波币是手动供给的。
所有交易都被记录在去中心化的账本中,形成不可更改的历史。需要共识确保所有节点
在一个给定时间的账本都一致。在Ripple中,有第三种节点,叫作验证器(validator),它是
共识协议的一部分,验证器负责验证交易。任何人都可以成为验证器。但是其他节点维护一
个可以信任的验证器列表。该列表被称为唯一节点列表(Unique Node List,UNL)。验证器
也有UNL,即验证器信任的验证器,因为验证器也想达成共识。目前,由Ripple决定可以信
任的验证器列表,但是如果网络认为Ripple选择的验证器不值得信任,就可以在节点软件中
修改列表。
可以拿出一个以前的账本,把随后发生的全部交易都填上去,形成一个新账本。为了同
意当前账本,节点必须同意以前的账本和随后发生的全部交易。在创建一个新账本之后,节点(普通节点和验证器)启动一个计时器(几秒钟长,大概5s),并收集在创建以前的账本
时到达的新交易。当计时器停下时,它接收至少80%的UNL认为合法的交易,形成下一个账
本。验证器向网络广播一个提案(proposal,即它们认为合法的、用于形成下一个账本交易
的一系列交易)。如果它们决定根据UNL提案和其他因素改变合法交易的列表,验证器可以
对同一个账本用不同的交易集合,多次广播提案。所以用户仅需要等待5~10s,由网络确认
交易。
有人质疑,每个节点可能有不同的UNL,是否会使账本生成许多不同的版本?其实只要
UNL之间有最低程度的相互连接,就会迅速达成共识,这是因为每一个诚实节点的主要目标
就是达成共识。1.9 总结
在本章中,我们学习了DApp的概念,初步了解了DApp的工作原理以及其面临的一些挑
战和应对挑战的多种方法。最后,我们接触了一些广受欢迎的DApp,了解了它们的特别之
处和工作原理。第2章 以太坊的工作原理
在前一章中,我们了解了DApp的概念,还了解了一些热门DApp,其中之一便是以太
坊。目前,以太坊是继比特币之后最受欢迎的DApp。在本章中,我们将深入学习以太坊的
工作原理及其用途,还将看到重要的以太坊客户端和节点实现。
在本章中,我们将讲解以下内容:
·以太坊用户账户。
·智能合约及其工作原理。
·以太坊虚拟机(EVM)。
·在工作量证明共识协议中挖矿如何进行。
·学习如何使用geth命令。
·建立以太坊钱包和浏览器钱包(Mist)。
·Whisper和Swarm概览。
·以太坊的未来。2.1 以太坊概览
以太坊(Ethereum)是一个去中心化的平台,可以在其上部署DApp。DApp是用一个或
者更多个智能合约创建的,使用Solidity编程语言编写智能合约。智能合约完全按照程序运
行,而且防停机、防审查、防欺诈、防第三方干扰。在以太坊中,编写智能合约可以使用好
几种编程语言,包括Solidity、LLL和Serpent,其中Solidity最受欢迎。以太坊有一种内部货币
叫作以太币(Ether),部署智能合约或者调用其方法需要用到以太币。和任何其他DApp一
样,智能合约可以有多个实例,且每个实例都有自己专门的地址。用户账户和智能合约都可
以持有以太币。
以太坊使用区块链数据结构和工作量证明共识协议。智能合约可以通过发送交易调用或
者通过其他合约调用。有两种网络中的节点:普通节点和矿工。普通节点只备份区块链上的
数据,而矿工通过挖矿创建区块链。2.2 以太坊账户
要创建以太坊账户,只需要一个非对称加密密钥对——由不同的算法(例如RSA、ECC
等)生成。以太坊使用椭圆曲线加密算法(ECC),ECC有多个参数用来调节速度和安全
性,以太坊使用secp256k 1参数。深入学习ECC及其参数需要一定的数学知识,而使用以太坊
创建DApp不需要深入理解ECC及其参数。
以太坊使用256位加密。以太坊私钥公钥是一个256位数。因为处理器不能表示这么大的
数,所以它被编译成长度为64的十六进制字符串。
每个账户用一个地址表示。有了密钥之后,就需要生成地址。从公钥生成地址的过程如
下:
1)生成公钥的keccak-256哈希。它将给出一个256位的数字。
2)丢弃前面的96位,即12字节。现在得到160位二进制数据,即20字节。
3)把地址编译成十六进制的字符串。最后将得到一个40字符的字节串,就是账户地
址。
现在任何人都可以发送以太币到这个地址。2.3 交易
交易是一个签名数据包,用于从一个账户向另一个账户或者向一个合约转以太币、调用
合约方法或者部署一个新合约。交易使用椭圆曲线数字签名算法(ECDSA) 签名,ECDSA
是一种基于ECC的数字签名算法。交易包含信息接收者、识别发起人及其意愿的签名、要转
账的以太币数量、交易执行允许进行的计算资源最大值(叫作gas上限)以及交易发起人愿意
为单位计算资源支付的费用(叫作gas价格)。如果交易目的是调用合约方法,则还包含输入
数据;如果其目的是部署合约,则可以包含初始化代码。用交易所消耗的gas乘以gas价格计
算得到交易费。为了发送以太币或者执行合约方法,需要向网络广播交易。发起人需要用私
钥签署交易。
如果确定交易将永久地出现在区块链中,则称为交易已确认。推荐在假设交易已
确认之前,等待15个确认(15个区块产生在交易所在的区块后面)。2.4 共识
以太坊网络中的每个节点包含区块链的一个备份。用户需要确保节点不能够篡改区块
链,还需要一个机制检查区块是否合法,如果遇到两个不同的合法区块链,需要有办法确定
选择哪个。
以太坊使用工作量证明共识协议防止区块链被篡改。工作量证明系统需要解决一个复杂
问题以创建一个新的区块。解决问题需要大量算力,这就使创建区块很困难了。在工作量证
明系统中,创建区块的过程称为挖矿。矿工(miner)是网络中挖区块的节点。使用工作量
证明的所有DApp并不一定都使用同样的算法。使用什么算法取决于矿工需要解决的问题、问题难度值、需要多长时间解决等。我们将学习与以太坊有关的工作量证明。
任何人都可以成为网络中的矿工。每个矿工独自解决问题,第一个解决问题的矿工是胜
利者,它得到的回报是5个以太币和该区块中全部交易的交易费。如果你的处理器比网络中
的其他节点更强大,也并不意味着你总会成功,因为所有矿工要解决的问题的参数并不完全
相同。但是如果你有一台比网络中的其他节点都强大的处理器,成功的概率会比较大。网络
安全不是用矿工总数衡量的,而是用网络的全部算力衡量的。
区块链中有多少个区块没有限制,可以生成的以太币总数也没有限制。矿工一旦成功挖
到区块,就向网络中的所有其他节点广播该区块。区块有一个区块头(header)和一系列交
易。每一个区块存储前一个区块的哈希值,由此创建一个相连的链。
让我们来看矿工需要解决的问题是什么以及如何在高水平解决问题。为了挖区块,矿工
首先从收到的广播中收集新的、未挖出的交易,然后滤掉不合法的交易。合法的交易必须满
足正确地使用私钥签名、账户有足够的余额进行交易等条件。现在矿工创建一个有区块头和
内容的区块。内容(content)是区块包含的交易列表。区块头包含前一个区块的哈希、区块
序号、随机数(nonce)、目标值(target)、时间戳(timestamp)、难度值(difficulty)、矿工地址(address)等内容。时间戳表示区块初始时间。随机数是一个没有意义的值,纯粹
是为了设置一个小于或等于目标值的区块哈希。以太坊使用ethash哈希算法。发现随机数的
唯一方法是穷尽所有可能。目标值是一个256位的数字,根据不同的因素计算。区块头的难
度值是目标值的一种不同表述方法。目标值越低,发现随机数需要的时间越多;目标值越
高,需要的时间越少。计算问题难度值的公式如下:
网络中的任何节点都可以检查区块链是否合法,首先检查交易在区块链中是否合法以及
时间戳的验证情况,然后检查区块的目标值和随机数是否合法、矿工是否得到合法的回报
等。 如果网络中的节点接收到两个不同的合法区块链,那么所有区块的整体难度值较
高的那个区块链被视为合法的区块链。
例如,假设网络中的一个节点想改变一个区块中的一些交易,就需要重新计算该块以及
该块后面所有区块的随机数。可是在该节点计算的同时,网络其他节点已经又挖出了许多新
的区块,因此当它重新计算到最新区块时会因整体难度值较低而被系统拒绝。可见,私自篡
改账本的难度是非常大的。2.5 时间戳
计算区块目标值的公式需要用到当前时间戳,且每个区块在区块头附加了当前时间戳。
没有什么机制可以阻止矿工在挖新区块时使用其他时间戳(而非当前时间戳),但是它们一
般不会那么做,因为时间戳验证会失败,其他节点不会接受该区块,这样就浪费了矿工的资
源。当一个矿工广播一个新挖出的区块时,其他节点对其时间戳的验证取决于其时间戳是否
大于前一个区块的时间戳。如果一个矿工使用的时间戳大于当前时间戳,则难度值较低,因
为难度值与当前时间戳成反比,因此网络将接受区块时间戳是当前时间戳的矿工,因为它的
难度值比较高。如果一个矿工使用的时间戳大于前一个区块时间戳,且小于当前时间戳,难
度值会高一些,因此要花费更多时间挖区块。等到区块被挖出的时候,网络可能产生了更多
区块,因此该区块会被拒绝,因为往往恶意矿工的区块链难度值会低于网络中的区块链难度
值。出于以上原因,矿工总是使用准确的时间戳,否则他们会一无所获。2.6 随机数
随机数是一个64位未签名证书。随机数是一个问题的解决办法,矿工不断地尝试随机
数,直到发现目标值。有人也许会好奇,如果某个矿工拥有的算力比网络中的任何其他矿工
都大,他是否总会第一个发现随机数?答案是不会。
每个矿工挖的区块的哈希是不同的,因为哈希取决于如时间戳、矿工地址等内容,而且
对于所有矿工来说这些内容很可能是不一样的。因此,解决问题并不是一场比赛,而更像是
一件碰运气的事。当然,矿工可能因为算力大而走运,但那并不意味着该矿工总会发现下一
个区块。2.7 区块时间
我们看到的区块难度值公式使用了一个长达10s的阈值,以确保挖出父区块和子区块的时
间差在10s和20s之间。但为什么是10~20s,而非其他数值呢?为什么时间差是恒定的,而非
难度值是恒定的?
假设有一个恒定的难度值,矿工只需要发现一个随机数使得区块的哈希小于等于该难度
值即可。假设该难度值大,且在此情况下,用户又无法估算用户间发送以太币的时间延迟。
如果网络算力不足,计算随机数需要较长时间,那么用户需要等待很长时间来确定交易。有
时网络算力充足,可能很幸运,很快就发现了随机数,用户交易确认就比较快。这类系统延
迟不确定的特点自然很难受到用户青睐,因为用户总想知道需要多长时间完成交易,就像我
们从一个银行账户向另一个银行账户汇款,银行会告诉我们在多长时间之内会完成汇款。如
果设定的难度值小,它将影响区块链的安全,因为大矿工可以比小矿工更快挖出区块,网络
中最大的矿工就会拥有控制DApp的能力。不可能发现一个可以使网络稳定的恒定难度值,因为网络算力并非恒定值。
现在我们知道了,为什么总是需要有一个相对稳定的生成区块的平均时间(即区块时
间)。问题是最合适的平均时间是多长。它可以短至1s,长至几乎无限多秒。降低难度值可
以使平均时间较短,反之增加难度值可以使平均时间较长。但是,平均时间的长短各有什么
优缺点呢?在讨论之前,首先需要知道无效无效块(stale block)是什么。
如果两个矿工用几乎相同的时间挖下一个区块,会发生什么呢?两个区块肯定都是合法
的,但是区块链不能包含区块序号相同的两个区块,而且两个矿工都得不到回报。尽管这是
个常见问题,解决方法却很简单,最后难度值较高的区块链将被网络接受。所以最后被忽略
的合法区块叫作无效无效块。
网络中生成的无效无效块总数与生成新区块所需的平均时间成反比。更短的区块生成时
间意味着新挖出来的区块向整个网络广播的时间更短,矿工发现问题解决办法的概率更大,所以当区块向整个网络广播时,其他一些矿工可能也解决了问题并进行了广播,由此产生了
无效块。但是如果生成区块的平均时间长一点,多个矿工能解决问题的概率就小一点,而且
即使它们都解决了问题,也很可能存在时间差,在这个时间差里,第一个被解决的区块就可
以进行广播,另一个矿工就可以停止挖那个区块并继续挖下一个区块。如果无效块在网络中
经常出现,就会出现大问题;如果仅是偶尔出现,就对网络没有损害。
但是无效块有什么问题呢?它们延迟了交易确认。当两个矿工几乎同时挖一个区块时,它们可能有不同的交易,因此如果交易出现在其中,就不能说交易已经确认了,因为交易中
出现的区块可能是无效块。我们应该等待再挖出几个区块。无效块导致平均确认时间不等于
生成区块的平均时间。
无效块会影响区块链安全吗?答案是肯定的。我们知道网络安全由网络中矿工的全部算
力衡量。当算力增长时,难度值也要增加,以确保区块不是在平均时间之前生成的。所以更
高的难度值意味着更安全的区块链——节点想篡改区块链将需要更多算力,使篡改区块链更
困难,因此区块链被认为是更安全的。当几乎同时挖出两个区块时,我们将把网络分成两部
分,在两个不同的区块链上工作,但是其中一个将成为最终区块链。所以在无效块上工作的网络是在无效块上挖下一个区块,结果是网络算力损失,因为算力用在了没有用的事情上。
网络的两个部分很可能用比平均时间更长的时间去挖下一个区块,因为它们损失了算力。所
以,在挖出下一个区块之后,难度值将降低,原因是用于挖区块的时间比平均时间更长。难
度值降低会影响整体区块链安全。如果无效块率太高,将在很大程度上影响区块链安全。
以太坊用ghost协议解决无效块带来的安全问题。以太坊使用这个真实ghost协议的一个修
正版本。ghost协议仅仅把无效块添加到母链上,掩盖了安全问题,由此增加了区块链的整体
难度值,因为区块链的整体难度值还包括无效块的难度值之和。但是如何才能在不产生交易
冲突的情况下把无效块添加到母链中呢?事实上,任何区块链都可以接纳零个或者多个无效
块。为了激励矿工接纳无效块,矿工接纳无效块将得到回报。此外,发现无效块的矿工也将
得到回报。无效块中的交易不用于计算确认,无效块矿工也不向无效块接纳的交易收取交易
费。注意,在以太坊中,无效块称为“叔块(uncle block)”。
矿工接纳无效块得到的回报计算公式如下。其余回报归侄块(nephew block),即包含
孤块(orphan block)的区块:
你肯定在奇怪为什么要给无效块矿工回报。即便不给它们任何回报,也不会影响安全。
这是因为无效块经常出现在网络中会导致另一个问题,而这个问题可以通过给无效块矿工回
报解决。矿工应当得到一定比例的回报,大致相当于它为网络贡献的算力比例。如果两个不
同的矿工几乎同时挖出一个区块,则算力比较大的矿工挖出的区块更有可能被添加到最终区
块链中,因为该矿工挖下一个区块的效率会比较高,所以小矿工将失去回报。如果无效块比
例低,就不是大问题,因为大矿工增加回报的概率不大。但是,如果无效块比例高,就会产
生大问题,也就是说,大矿工在网络中最终将得到比它应得的更多的回报。ghost协议通过回
报无效块矿工找到平衡。由于大矿工不拿走全部回报,但是仍比它们应得的多,我们不能对
无效块矿工和侄块给予同等回报,而是给得少一点。前面的公式很好地解决了问题。
ghost协议会限制一个侄块可以指向的无效块总数,这样矿工不会只挖无效块并使区块链
生成速度变慢。
所以一旦无效块出现在网络中,它会或多或少地影响网络。无效块出现得越频繁,网络
受到的影响越大。2.8 分叉
在节点验证区块链发生冲突时,会发生分叉(fork ing),也就是说,在网络中有多于一
个区块链,且每个区块链由一些矿工验证。分叉共有三种:普通分叉、软分叉和硬分叉。
普通分叉是由于两个或者多个矿工几乎同时发现了一个区块引起的暂时冲突。如果一个
难度值高于另一个,冲突就解决了。
更改源代码可能引起冲突。根据冲突类型,可能要求有50%以上算力的矿工升级,也可
能要求所有矿工升级,以解决冲突。要求有50%以上算力的矿工升级以解决冲突,叫作软分
叉;而要求所有矿工升级以解决冲突,叫作硬分叉。软分叉的一个例子是,如果更新源代码
使旧区块交易的一部分失效,则有50%以上算力的矿工升级后可以解决,这样新的区块链将
有更大难度值,最后被整个网络接受。硬分叉的一个例子是,如果更新源代码是为了更改对
矿工的回报,则全部矿工需要升级以解决冲突。
以太坊自发布以来经历了多次硬分叉和软分叉。2.9 创世区块
创世区块(genesis block)是区块链中的第一个区块,其区块序号是0。它是区块链中唯
一一个不指向前一个区块的区块,因为没有前一个区块。它也不包含交易,因为还没产生任
何以太币。
只有网络中的两个节点有相同的创世区块,它们才会彼此配对,也就是说,如果两个对
等节点有相同的创世区块才会进行同步区块,否则它们将彼此拒绝。不同的创世区块有较高
难度值也不能替代难度值较低的。每一个节点生成自己的创世区块。对于不同的网络,创世
区块被硬编码到客户端里。2.10 以太币面值
和其他货币一样,以太币也有多种面值。其面值如下:
·1以太币=1000000000000000000 wei。
·1以太币=1000000000000000 Kwei。
·1以太币=1000000000000 Mwei。
·1以太币=1000000000 Gwei。
·1以太币=1000000 Szabo。
·1以太币=1000 Finney。
·1以太币=0.001 Kether。
·1以太币=0.000001 Mether。
·1以太币=0.000000001 Gether。
·1以太币=0.000000000001 Tether。2.11 以太坊虚拟机
以太坊虚拟机(Ethereum Virtual Machine,EVM)是以太坊智能合约字节码(by te-
code)的执行环境。网络中的每个节点都运行EVM。所有节点执行使用EVM指向智能合约
的全部交易,因此它们进行同样的计算,并存储同样的数值。只进行以太币转账(查询该地
址是否有余额并相应地扣款)的交易也需要进行一些计算。
出于各种原因,每个节点执行并存储最终状态。例如,如果有一个智能合约存储参加派
对的每个人的姓名和细节,只要增加新的人,就向网络广播新的交易。网络中的任何节点想
要展示参加派对的每个人的细节,只需读取合约的最终状态即可。
每个交易需要在网络中进行一些计算和存储。因此需要有交易费,否则整个网络里将充
斥着垃圾交易,而且没有交易费用矿工就没有理由在区块中接纳交易,它们将开始挖空区
块。每个交易需要的计算和存储量有所不同,因此每一个交易的交易成本不同。
有两种EVM实现,即字节码VM和JIT-VM。在写本书时,JIT-VM已交付使用,但其开发仍未结束。在两种情况下,Solidity代码都被编译成字节码。在JIT-VM中,字节码编
译更充分。JIT-VM比字节码VM更高效。2.12 gas
gas(燃料)是计算资源的计量单位。每一个交易都需要包含gas上限和为每个gas支付费
用的单价(即每次计算的价格)。矿工可以选择接纳交易和收取费用。如果交易使用的gas少
于或等于gas上限,交易继续进行。如果gas总数超过gas上限,则撤销所有修改,除了仍然合
法且矿工仍然能够收到费用(费用计算方法是可以消耗的gas最大值和gas价格相乘)的交
易。
矿工决定gas价格(即每次计算的价格)。如果交易gas价格低于矿工决定的gas价格,矿
工将拒绝挖交易。gas价格以wei为单位。所以如果gas价格低于期望,矿工可以拒绝将交易接
纳入区块。
EVM的每个操作都被分配了一个数字,用以表示它可以消耗的gas。
交易成本影响一个账户可以转账给另一个账户的以太币上限。例如,如果某个账户里共
有5个以太币,它不能把全部5个以太币转入其他账户,因为如果把所有以太币都汇走,账户
就没有余额支付交易费了。
如果交易调用一个合约方法,且该方法发送一些以太币或者调用一些其他合约方法,就
从调用合约方法的账户扣除交易费。2.13 发现对等节点
节点是网络的一部分,它需要连接到网络中的一些其他节点,这样它可以广播交易区
块,并监听新的交易区块。节点不需要连接到网络中的每一个节点;相反,它只连接到几个
其他节点,这些节点再连接到另外几个节点。按照这个方式,整个网络彼此连接。
但是节点如何发现网络中的一些其他节点呢?没有每个节点都可以连接到的中央服务
器,怎么交换信息呢?以太坊有自己的节点发现协议可用于解决这个问题,该协议以
Kadelima协议为基础。在节点发现协议中有一种特殊的节点,叫作Bootstrap(初始启动)节
点。Bootstrap节点保存了一段时间内与它们连接的所有节点的列表,但其本身不保存区块
链。当对等节点连接到以太坊网络时,它们首先连接到Bootstrap节点,Bootstrap节点分享在
刚才事先定义的时间里连接到它们的对等节点列表。然后对等节点与对等节点连接并同步。
可以有多种以太坊实例,也就是说,不同的网络每个都有自己的网络ID。两种主要的以
太坊网络是主网和测试网。以太币在主网上交易,而测试网供开发人员进行测试。到目前为
止,我们已经学习了关于主网区块链的所有知识。
bootnode是以太坊Bootstrap节点最热门的实现。如果用户想使用自己的Bootstrap
节点,可以使用bootnode。2.14 Whisper和Swarm
Whisper和Swarm分别是去中心化的通信协议和存储平台,它们都由以太坊开发人员开
发的。Whisper是一个去中心化的通信协议,Swarm则是一个去中心化的文件系统。
Whisper允许网络中的节点彼此通信。它支持广播、用户到用户、加密信息等,但不用
于传输大数据。想更深入学习Whisper,请访问https:github.comethereumwikiwikiWhisper
,在https:github.comethereumwik iwikiWhisper-Overview 可以看到代码示例概述。
Swarm类似于Filecoin,二者最大的区别是技术细节和激励机制。Filecoin不惩罚存储;
而Swarm惩罚存储。因此,这进一步提高了文件可用性。那么,Swarm中的激励机制如何工
作?它有内部货币吗?事实上,Swarm没有内部货币,而是用以太币进行激励。在以太坊中
有智能合约,智能合约记录激励情况。显然,智能合约不能与Swarm通信,但Swarm能与智
能合约通信。所以用户基本上通过智能合约向存储付款,该支付在失效后被释放给存储。用
户还可以向智能合约报失文件,在此情况下它可以惩罚存储。可以访问
https:github.cometherspherego-ethereumwik iIPFS--SWARM 了解Swarm和IPFSFilecoin之
间的区别,访问https:github.cometherspherego-ethereumblobbzz-
configbzzbzzcontractswarm.sol 查看智能合约代码。
在写本书时,Whisper和Swarm仍处于开发阶段,许多事情仍不明确。2.15 geth
geth(或称为go-ethereum)是以太坊、Whisper和Swarm节点的一个实现。geth可以成为
全部实现或者一些选定实现的一部分。合并它们的目的是让它们看起来像单一的DApp,通
过一个节点客户端就可以访问三个DApp。
geth是一种CLI应用,它用Go语言编写,在主要的操作系统中都可使用。geth的当前版本
还不支持Swarm,但支持Whisper的一些功能。在写本书时,geth的最新版本是1.3.5。2.15.1 安装geth
geth可用于OS X、Linux和Windows操作系统。它支持两种类型的安装:二进制安装和脚
本安装。在写本书时,geth的最新版本是1.4.13。让我们看看如何使用二进制安装方法在不同
操作系统中进行安装。如果用户不得不修改geth源代码并安装,请使用脚本安装方法。我们
不想改变源代码,因此将采用二进制安装。
1.OS X
推荐在OS X中使用brew安装geth。在终端运行下面两个命令安装geth:
2.Ubuntu
推荐在Ubuntu中使用apt-get安装geth。在Ubuntu终端中运行如下命令安装geth:
3.Windows
对于Windows来说,geth是一个可执行文件。从https:github.comethereumgo-
ethereumwikiInstallation-instructions-for-Windows 下载zip文件,并解压缩。压缩包中有
geth.exe文件。
想更多地了解在不同操作系统上安装geth的方法,请访问
https:github.comethereumgo-ethereumwikiBuilding-Ethereum 。2.15.2 JSON-RPC和JavaScript操作台
geth为其他应用提供了与其进行通信的JSON-RPC API。geth使用HTTP、WebSock et和其
他协议服务于JSON-RPC API。JSONRPC提供的API分成:admin、debug、eth、miner、net、personal、shh、txpool和web3等类型。访问https:github.comethereumgo-
ethereumwikiJavaScript-Console 可以了解更多信息。
geth还提供了一个交互JavaScript操作台,可以使用JavaScript API进行程序交互。该交互
操作台使用JSON-RPC与geth进行通信。在后面的章节中,我们将学习更多关于JSON-RPC和
JavaScript API的内容。2.15.3 子命令和选项
让我们通过例子学习geth命令的一些重要的子命令和选项。用户可以使用help子命令发
现所有子命令和选项的列表。我们将在下面学习更多关于geth及其命令的知识。
1.连接至主网网络
以太坊网络中的节点默认用30303端口通信。但是节点还可以收听一些端口。
为了连接到主网网络,只需要运行geth命令即可。如下是一个例子,展示如何明确指定
网络ID和指定将存储下载区块链的自定义目录:
其中,--datadir选项用于指定在哪里存储区块链。如果没有提供,默认路径
是“HOME.ethereum”;--networkid用于指定网络ID。1代表主网网络ID。如果没提供网络ID,默认值是1。2代
表测试网络ID。
2.创建私有网络
要创建私有网络,只需给出一个随机网络ID即可。通常创建私有网络的目的是进行开
发。geth还提供了多个与日志和调试相关的标记(flag),这对于开发很有益处。可以简单使
用--dev标记运行一个私有网络,该网络允许多个与日志和调试相关的标记,而不用给出一个
随机网络ID并放上多个与日志和调试相关的标记。2.15.4 创建账户
geth还允许创建账户,即生成密钥和相关地址。为了创建账户,可以使用下面的命令:
当运行上述命令时,需要输入密码以加密账户。如果忘记密码,就无法访问账户了。
为了在本地钱包获得所有账户的列表,可以使用下面的命令:
执行上述命令将打印账户中所有地址的列表。密钥默认存储在--datadir路径中,但用户
可以使用--k ey store选项指定一个不同的目录。
1.挖矿
默认geth不启动挖矿。为了指示geth开始挖矿,只需要提供--mine选项。还有一些与挖矿
相关的选项:
除了--mine选项之外,这里还给出了其他选项。--minerthreads选项用于指定哈希过程中
使用的线程总数,默认使用8个线程。etherbase是挖矿赚取的回报存入的地址。账户默认是
加密的。所以要访问账户中的以太币,就需要解锁,即解码账户。解密用于解码账户相关私
钥。为了开始挖矿,不需要解锁它,因为只需要地址就能存入挖矿回报。可以使用-unlock选
项解锁一个或者多个账户。使用逗号分隔地址可以提供多个地址。--minergpus用于指定挖矿使用的GPU。为了得到GPU列表,可以使用geth gpuinfo命
令。每个GPU需要1~2GB的RAM。默认只使用CPU,而不使用GPU。
2.快速同步
在写本书时,区块链大小大约为30GB。如果用户的网速慢,则下载需要花费几个小时甚
至几天。以太坊实现了一种快速同步算法,可以更快地下载区块链。
快速同步(fast synchronization)不下载整个区块,而只下载区块头、交易凭证和最新的
状态数据库。因此用户不需要下载和重播全部交易。为了检查区块链的真实性,该算法在每
一个已定义的区块序号之后下载一个完整的区块。要更深入地学习快速同步算法,请访问
https:github.comethereumgo-ethereumpull1889 。为了在下载区块链过程中使用fast sy nc,用户需要在运行geth的过程中使用--fast。
出于安全原因,fast sy nc只在初始同步时运行(即该节点自身的区块链为空时)。在节
点成功与网络同步后,fast sync就永远禁用了。作为一项额外的安全功能,如果在枢轴点
(pivot point)附近或者之后快速同步失败,就会禁用fast sync,然后节点返回到完整的、以
区块处理为基础的同步。2.16 以太坊钱包
以太坊钱包是一个以太坊UI客户端,它允许用户进行创建账户、发送以太币、部署合
约、调用合约方法等操作。
以太坊钱包与geth捆绑在一起。运行以太坊时,它会尝试发现一个本地geth实例并与之
连接;如果它不能发现geth正在运行,它就启动自己的geth节点。以太坊钱包使用IPC与geth
通信。geth支持以文件为基础的IPC。
如果在运行geth时更改数据目录,就是也在更改IPC文件路径。所以为了让以太
坊钱包发现并连接到geth实例,需要使用--ipcpath选项指定IPC文件位置为其默认位置,这样
以太坊钱包可以发现它;否则,以太坊钱包就不能发现它,将启动自己的geth实例。为了发
现默认IPC文件路径,运行geth help,它会显示--ipcpath选项的默认路径。
请访问https:github.comethereummistreleases 下载以太坊钱包。它适用于Linux、OS X
和Windows操作系统。与geth一样,它有两种安装方式:二进制安装和脚本安装。
以太坊钱包的示意图如图2-1所示。2.17 浏览器钱包
浏览器钱包(Mist)是以太坊、Whisper和Swarm的一个客户端,它允许用户发送交易、发送Whisper信息、检查区块链等。
Mist和geth之间的关系类似于以太坊钱包和geth。
Mist最热门的功能是它带有浏览器。目前,浏览器中运行的前端JavaScript可以使用
web3.js库(该库为其他应用提供以太坊操作台的JavaScript API与geth通信)访问geth节点的
web3 API。
Mist的基本思想是创建第三代Web(Web 3.0),即使用以太坊、Whisper和Swarm替代
中心化服务器端,这样就不需要服务器端了。图2-1 以太坊钱包的示意图
Mist的示意图如图2-2所示。2.18 以太坊的缺点
每个系统都有一些缺点,同理以太坊也有一些缺点。显然,像其他应用一样,以太坊源
代码可能有bug。它也像其他以网络为基础的应用一样面临着DoS攻击。让我们看看以太坊独
有的且最重要的缺点。
1.Sybil攻击
攻击者可能试图用他控制的普通节点占满整个网络,那么用户很有可能只连接到攻击者
节点。一旦连接到攻击者节点,攻击者可以拒绝从所有节点转播区块和交易,从而将用户从
网络中断开。攻击者只能转播他创建的区块,从而会将用户放到分开的网络上。
图2-2 Mist的示意图
2.51%攻击
如果攻击者掌握了网络中一半以上的算力,他就可以比网络中其他人更快地生成区块。
攻击者可以保留他的私有分叉,直到分叉比诚实网络创造得更长,然后广播自己的分叉。
拥有50%以上的算力,矿工就可以重写交易,阻止全部一些交易被挖出,并阻止其他矿工挖出的区块被添加到区块链中。2.19 serenity
serenity是以太坊下一个主要更新的名字。在写本书之时,serenity仍处于开发阶段。这
个更新将要求硬分叉。serenity把共识协议改为casper,并将整合状态通道和分片。在写本书
时,完整细节尚不明确。
1.支付和状态通道
在学习状态通道以前,我们需要了解支付通道的概念。支付通道功能允许将两个以上向
另一个账户发送以太币的交易合并成两个交易。其工作原理为:假设X是一个视频网站老
板,Y是个用户。X每分钟收费1个以太币。现在X想让Y看视频期间每分钟交一次钱。当
然,Y可以每分钟广播交易,但是这里还有些问题,例如X不得不等待确认,所以视频就会
中断一会。支付通道可以解决这个问题。使用支付通道,Y可以广播一个锁定交易,为X把
一些以太币(比如100个以太币)锁定一段时间(比如24小时)。现在每看完一分钟视频,Y
将发送一个签名记录表示可以解锁,一个以太币就进入X的账户,其余的进入Y的账户。再
过一分钟,Y将发送一个签名记录表示可以解锁,两个以太币就进入X的账户,其余的进入Y
的账户。Y观看X网站的视频过程中,该过程将持续。现在假设Y看完了100小时视频或者24
小时时间到了,X将向网络广播最后的签名记录,以把钱收到自己的账户里。如果X没有在
24小时内提款,全款会返还给Y。所以在区块链中,我们将看到lock和unlock两种交易。
支付通道是与发送以太币相关的交易。类似地,状态通道允许合并与智能合约相关的交
易。
2.权益证明和casper
在学习casper共识协议之前,我们需要理解权益证明(Proof-of-Stake,PoS)共识协议
的工作原理。
权益证明是工作量证明最常见的替代共识。工作量证明会浪费大量算力。PoW和PoS之
间的区别就是:在PoS中,矿工不需要解决问题;而在Pow中,矿工需要证明挖矿权益的所
有权。在PoS系统中,账户中的以太币被当作权益,矿工挖矿的概率与矿工持有的权益成正
比。所以如果矿工拥有网络中10%的权益,它将挖到10%的区块。
但问题是怎样才能知道谁将挖下一个区块。我们不能简单地让持有最多权益的矿工总能
挖出下一个区块,因为这将导致中心化。对于下一个区块的选择,存在不同的算法,例如随
机区块选择和基于币龄的选择。
casper是PoS的一个修订版本,它解决了PoS中的一些问题。
3.分片
目前,每个节点都需要下载全部交易,数量庞大。按照现在区块链发展的速度,未来用
不了几年,下载整个区块链并同步将是非常困难的。
如果用户熟悉分布式数据库架构,那么肯定熟悉分片(sharding)。简言之,分片就是在多个计算机分布数据的方法。以太坊将实现分片,以分割区块链并跨节点分布区块链。
读者可以在https:github.comethereumwikiwikiSharding-F AQ 学习将区块链分片的知
识。2.20 总结
在本章中,我们具体学习了以太坊的工作原理、区块时间如何影响安全以及以太坊的缺
点;还学习了Mist和以太坊钱包的概念及其安装方法,以及geth的一些重要命令;最后学习
了以太坊serenity更新中的新内容。
在下一章中,我们将学习存储和保护以太币的不同方法。第3章 编写智能合约
在前一章中,我们学习了以太坊区块链的工作原理以及PoW共识协议保障其安全性的原
理。现在我们已经掌握了以太坊的工作原理,所以是时候开始编写智能合约了。有好几种语
言可以用于编写以太坊智能合约,不过Solidity是最热门的语言。在本章中,我们将首先学习
Solidity编程语言。然后创建一个DApp,用于证明在特定时间的存在、真实性和所有权,即
证明一个文件在一个特定时间属于一个特定所有者。
在本章中,我们将讲解以下内容:
·Solidity源文件的布局。
·理解Solidity的数据类型。
·合约的特殊变量和函数。
·控制结构。
·合约的结构和功能。
·编译和部署合约。3.1 Solidity源文件
Solidity源文件使用的扩展名为.sol。与其他编程语言一样,Solidity有多种版本。在写本
书时,其最新版本是0.4.2。
在源文件中,可以使用pragma Solidity说明编写代码时用的编译器版本。例如,现在,源文件不会用低于0.4.2的编译器版本,也不会用高于0.5.0的编译器版本进行编译
(第二个条件使用^添加)。0.4.2和0.5.0之间的编译器版本最有可能包括bug修复。
可以为编译器版本指定更复杂的规则;使用与npm一样的表达式。3.2 智能合约的结构
合约就像一个类(class),其中包含状态变量(state variable)、函数(function)、函
数修改器(function modifier)、事件(event)、结构(structure)和枚举(enum)。合约
还支持继承,通过在编译时备份代码来实现。最后,合约还支持多态。
下面来看一个智能合约的例子:上述代码的工作原理如下:
1)使用contract关键字声明一个合约。
2)声明两个状态变量data和owner。data包含一些数据,owner包含所有者的以太坊钱包
地址,即部署合约者的以太坊地址。
3)定义一个事件(event)。事件用于通知客户端。一旦data发生变化,将触发这个事
件。所有事件都保存在区块链中。
4)定义一个函数修改器(function modifier)。修改器用于在执行一个函数之前自动检
测条件。这里,修改器检测合约所有者是否在调用函数。如果不是,就抛出异常。
5)得到合约构造函数(constructor)。在部署合约时,调用构造函数。构造函数用于初
始化状态变量。6)定义两个方法。第一个方法用于得到data状态变量的值,第二个方法用于改变data的
值。
在更深入地学习智能合约的函数之前,我们先来学习一些与Solidity有关的其他知识,然
后再回到合约。3.3 数据位置
截至目前,我们学过的所有编程语言可能都把变量存储在内存中。但是在Solidity中,根
据情况的不同,变量可能不存储在内存和文件系统中。
根据情况的不同,数据总有一个默认位置。但是对于复杂数据类型,例如字符串
(string)、数组(array)和结构类型(struct),可以用向类型添加storage或者memory进
行重写。函数参数(包括返回参数)默认用memory,本地变量默认用storage。显然,对于
状态变量来说,位置强制用storage。
数据位置很重要,因为它们会改变分配的行为:
·storage变量和memory变量之间的分配总是创建一个独立的备份。但如果分配是从
memory存储的一种复杂类型到另一种复杂类型,则不创建备份。
·到一个状态变量的分配(即使是来自其他状态变量)总是创建一个独立的备份。
·不能把memory中存储的复杂类型分配给本地存储变量。
·在分配状态变量给本地存储变量的情况下,本地存储变量指向状态变量,也就是说,本
地存储变量变为指针。3.4 什么是不同的数据类型
Solidity是一种静态类型语言,变量存储的数据类型需要预先定义。所有变量默认值都是
0。在Solidity中,变量是有函数作用范围的,也就是说,在函数中任何地方声明的变量将对
整个函数存在适用范围,无论它是在哪里声明的。
现在让我们看看Solidity提供的不同数据类型:
·最简单的数据类型是布尔值,可以是true或者false。
·uint8,uint16,uint24,…,uint256分别用于存储无符号的8位,16位,24位,…,256位
整数。同理,int8,int16,…,int256分别用于存储8位,16位,24位,…,256位整数。uint
和int是uint256和int256的别名。类似于uint和int,ufixed和fixed代表分数。ufixed0x8,ufixed0x16,…,ufixed0x256分别用于存储未签名的8位,16位,24位,…,256位分数。同
理,fixed0x8,fixed0x16,…,fixed0x256分别用于存储8位,16位,24位,…,256位分数。
如果一个数字超过256位,则使用256位数据类型存储该数字的近似值。
·address可以用于存储最大20字节的值(十六进制表示)。它用于存储以太坊地址。
address类型有两个属性:balance和send。balance用于检测地址余额,send用于向地址发送以
太币。send方法拿出需要转账那些数量的wei,并根据转账是否成功返回true或者false。wei
从调用send方法的合约中扣除。用户可以在Solidity中使用0x前缀给变量分配一个十六进制的
数值。3.4.1 数组类型
Solidity支持generic和byte两种数组类型。它们支持固定长度和动态长度两种数组,也支
持多维数组。
by tes1,by tes 2,by tes3,……,bytes32是字节数组的类型。by te是by tes 1的别名。
下面给出了generic数组语法的一个示例:关于数组的重要内容如下:
·数组还有length属性,用于发现数组的长度。用户还可以给length属性分配一个值,以改
变数组大小,但不可以在内存中改变数组大小,也不可以改变非动态数组大小。
·如果想访问动态数组的未设置索引(unset index),会抛出异常。
记住:array、structs和map都不可以用作函数参数,也不可以用作函数返回值。3.4.2 字符串类型
在Solidity中,有两种方法创建字符串:使用bytes和string。bytes用于创建原始字符串,而string用于创建UTF-8字符串。字符串长度总是动态的。
下面给出了字符串语法的一个示例:3.4.3 结构类型
Solidity还支持结构类型(struct)。下面给出了struct语法的一个示例:
注意:函数参数不可以是结构类型,且函数不可以返回结构类型。3.4.4 枚举类型
Solidity还支持枚举类型(enum)。下面给出了enum语法的一个示例:3.4.5 mapping类型
mapping数据类型是一个哈希表。mapping类型只可以存在于storage中,不存在于
memory中,因此它们是作为状态变量声明的。可以认为mapping类型包含k ey value对,不是
实际存储key,而是存储key的k eccak 256哈希,用于查询value。mapping类型没有长度。
mapping不可以被分配给另一个mapping。
下面给出了一个创建和使用mapping的示例:
记住:如果想访问mapping中不存在的k ey,返回的value均为0。3.4.6 delete操作符
delete操作符可以用于任何变量,将其设置成默认值。默认值均为0。
如果对动态数组使用delete操作符,则删除所有元素,其长度变为0。如果对静态数组使
用delete操作符,则重置所有索引。还可以通过对特定索引位置使用delete来重置索引。
如果对map类型使用delete操作符,什么都不会发生。但是如果对map类型的一个键使用
delete操作符,则会删除与该键相关的值。
下面给出了delete操作符的一个示例:3.4.7 基本类型之间的转换
除了数组类型、字符串类型、结构类型、枚举类型和map类型外,其他类型均称为基本
类型。
如果把一个操作符应用于不同的类型,编译器将尝试把一个操作数隐式转换为另一种类
型。通常来说,如果没有语义信息丢失,值和类型之间可以进行隐式转换:uint8可转换为
uint16,int128可转换为int256,但是int8不可转换为uint256(因为uint256不能存储,例
如-1)。此外,无符号整数可以转换成同等大小或者更大的字节,但是反之则不然。任何可
以转换成uint160的类型都可以转换成地址。
Solidity也支持显式转换。所以,如果编译器不允许在两种数据类型之间隐式转换,则可
以进行显式转换。建议尽量避免显式转换,因为可能返回难以预料的结果。
来看一个例子:
这里是将uint32类型显式转换为uint16,也就是说,把较大类型转换为较小类型,因此高
位被砍掉了。3.4.8 使用var
Solidity提供了用于声明变量的var关键字。变量类型根据分配给它的第一个值来动态确
定。一旦分配了值,类型就固定了,所以如果给它指定另一个类型,将引起类型转换。示例
如下:
记住:在定义数组array和map时不能使用var。var也不能用于定义函数参数和状
态变量。3.5 控制结构
Solidity支持if、else、while、for、break、continue、return、?:等控制结构。
下面给出了控制结构的一个示例:3.6 用new操作符创建合约
一个合约可以使用new关键字来创建一个新合约,但前提是必须知道新创建的合约的完
整代码。示例如下:3.7 异常
在一些情况下,异常会被自动抛出。也可以使用throw手动抛出异常。抛出异常会停止回
滚目前执行的调用(也就是说,撤销对状态和余额的所有改变)。捕获异常是不可能的:3.8 外部函数调用
在Solidity中,有两种函数调用:内部函数调用和外部函数调用。内部函数调用是指一个
函数在同一个合约中调用另一个函数。
外部函数调用是指一个函数调用另一个合约的函数。示例如下: 使用this关键字进行的调用称为外部调用。在函数中,this关键字代表当前合约实
例。3.9 合约功能
现在是时候深入学习合约了。我们将看看一些新的功能,还将深入学习已经见过的一些
功能。3.9.1 可见性
函数或者状态变量的可见性定义了谁可以看到它。函数和状态变量有四种可见性:
external、public、internal和private。
函数可见性默认为public,状态变量可见性默认为internal。各可见性函数的含义如下:
·external。外部函数只能由其他合约调用,或者通过交易调用。外部函数f不能被内部函
数调用,也就是说,f没有用,但是this.f有用。不能把external可见性应用到状态变
量。
·public。公共函数和状态变量可以用所有可行办法访问。编译器生成的存取器
(accessor)函数都是公共状态变量。用户不能创建自己的存取器。事实上,它只生成
getters,而不生成setters。
·internal。内部函数和状态变量只可以内部访问,也就是说,从当前合约内和继承它的
合约访问。不可以使用this访问它。
·private。私有函数和状态变量类似于内部函数,但是继承合约不可以访问它们。
下面给出了可见性和存取器(accessor)的一个示例:3.9.2 函数修改器
我们之前看到了函数修改器(function modifier)的概念,还编写了一个基本的函数修改
器,现在来深入学习修改器。
修改器由子合约(child contract)继承,且子合约可以对其重写。可以通过用空格分隔
的列表指定修改器将多个修改器应用到一个函数,并将多个修改器按顺序估值;还可以向修
改器传送实参。
在修改器中,无论下一个修改器体或者函数体二者哪个先到达,会被插入到“_”;出现
的地方。
让我们来看一个函数修改器的复杂代码例子:myFunction的执行代码如下:
在上述代码中调用myFunction方法时,将返回0。但是之后访问状态变量a时,将得
到8。
修改器或者函数体中的return(返回)立即离开整个函数,返回值被分配成它需要成为
的任何变量。
就函数来说,return之后的代码在调用者的代码完成运行后再执行。就修改器来说,上
述修改器中的“_;”之后的代码在调用者的代码完成运行后再执行。在上面的例子中,第5、6和7行从未执行过。在第4行之后,执行从第8~10行开始。
修改器中的return不可以有相关值,它总是返回全0。3.9.3 回退函数
一个合约可以有唯一的未命名函数,称为回退函数(fallback function)。该函数不能有
实参,不能返回任何值。如果其他函数都不能匹配给定的函数标识符,就在合约调用上执行
回退函数。
当合约不用任何函数调用就接收以太币(即交易发送以太币给合约却不调用任何方法)
时,也执行该函数。在此情况下,用于函数调用的gas通常很少(准确地说是2300 gas),所
以使回退函数尽可能便宜很重要。
接收以太币但是却不定义回退函数的合约会抛出异常,把以太币发送回去。所以如果你
想让你的合约接收以太币,就必须要实现回退函数。
下面给出了回退函数的一个示例:3.9.4 继承
Solidity通过代码备份(包括多态)支持多重继承(multiple inheritance)。即使一个合约
继承自其他多个合约,在区块链上也只创建一个合约,来自父合约(parent contract)的代码
总是被复制到最终合约里。示例如下:1.super关键字
super关键字用于引用最终继承链中的下一个合约,示例如下:其中,引用sample6合约的最终继承链是sample6、sample5、sample4、sample2、sample3和sample1。继承链始于衍生最充分的合约,终于衍生最不充分的合约。
2.抽象合约
仅包含函数原型而不包含函数实现的合约称为抽象合约(abstract contract)。这些合约
不能被编译(即使包含实现函数和非实现函数)。如果一个合约继承自抽象合约且不重写并
实现所有非实现函数,那么它自己也是抽象的。
抽象合约仅在创建编译器已知的接口时提供。这在引用已部署的合约和调用其函数时是
很有用的。示例如下:3.10 库
库类似于合约,但其目的是在一个特定地址只部署一次,且其代码由不同合约反复使
用。这意味着如果调用库函数,其代码在调用合约(calling contract)中执行,也就是说,this指向调用合约,特别是来自调用合约的storage可以被访问。由于库是源代码中独立的一部
分,它只能访问调用合约的状态变量,如果这些变量是显式的(否则无法命名这些变量)。
库没有状态变量——它们不支持继承,也不能接收以太币。库可以包含结构类型
(struct)和枚举类型(enum)。
一旦在区块链中部署Solidity库,任何知道其地址和源代码(只知道原型或者知道完整实
现)的人都可以使用它。Solidity编译器需要有源代码,这样能确保所欲访问的方法在库中真
实存在。示例如下:
不能在合约源代码中添加库地址,而是需要在编译时向编译器提供库地址。
库有许多使用示例。两个主要的示例如下:
·如果有许多合约,它们有一些共同代码,则可以把共同代码部署成一个库。这将节省
gas,因为gas也依赖于合约的规模。因此,可以把库想象成使用其合约的基础合约。使用基
础合约(而非库)切分共同代码不会节省gas,因为在Solidity中,继承通过复制代码工作。
由于库被当作基础合约,库里面带有内部可视性的函数被复制给使用它的合约;否则,库里
面带有内部可视性的函数不能被使用这个库的合约调用,因为这需要外部调用,而带有内部
可视性的函数不能通过外部调用被调用。此外,库里的structs和enums被复制给使用这个库的
合约。
·库可用于给数据类型添加成员函数。 如果一个库里只包含内部函数和或structsenums,则不需要部署库,因为库里面
的所有内容都被复制给使用它的合约。
using for
using A for B这条指令可用于连接库函数(从库A到任意类型B)。这些函数将被调用的
对象作为它们的第一个参数接收。
using A for的结果表示来自库A的函数被连接到所有类型。示例如下:3.11 返回多值
Solidity允许函数返回多值(multiple values),示例如下:3.12 导入其他Solidity源文件
Solidity允许一个源文件导入其他源文件,示例如下:3.13 全局可用变量
有些特殊变量和函数永远存在于全局中。3.13.1 区块和交易属性
区块和交易属性有如下几项:
·block.block hash(uint blockNumber)returns(bytes32)。给定区块的哈希值,只支持最
近256个区块。
·block.coinbase(address)。当前区块矿工的地址。
·block.difficulty(uint)。当前区块的难度值。
·block.gaslimit(uint)。当前区块的gas上限。它定义了整个区块中的所有交易一起最多
可以消耗多少gas。其目的是使区块的传播和处理时间保持在较低水平,这样才能有足够去中
心化的网络。矿工有权利将当前区块的gas上限设置为上一个区块的gas上限~0.0975%(11,024)以内的数值,所以gas上限的结果应当是矿工偏好的中间值。
·block.number(uint)。当前区块的序号。
·block.timestamp(uint)。当前区块的时间戳。
·msg.data(by tes)。完整的调用数据里存储的函数及其实参。
·msg.gas(uint)。当前剩余的gas。
·msg.sender(address)。当前调用发起人的地址。
·msg.sig(by tes4)。调用数据的前四个字节(函数标识符)。
·msg.value(uint)。这个消息所附带的货币量,单位为wei。
·now(uint)。当前区块的时间戳,等同于block.timestamp。
·tx.gasprice(uint)。交易的gas价格。
·tx.origin(address)。交易的发起人(完整的调用链)。3.13.2 地址类型相关
地址类型相关变量如下:
·.balance(uint256)。地址余额,单位为wei。
·.send(uint256 amount)returns(bool)。发送指定数量的wei到地址,失败时
返回false。3.13.3 合约相关
合约相关变量如下:
·this。当前合约,可显式转换成地址类型。
·selfdestruct(address recipient)。销毁当前合约,把其中的资金发送到指定地址。3.14 以太币单位
一个数字可以用wei、finney、szabo或者Ether等单位转换不同面值的以太币。以太币如
果不标明货币单位,就默认以wei为单位,例如,2Ether可转换成2000finney。3.15 存在、真实性和所有权合约的证明
本节将编写一个不用出示实际文件就可以证明文件所有权的Solidity合约。它可以证明该
文件在某个特定时间存在,并最终检查文件真实性(integrity)。
将成对存储文件哈希和所有者名字以实现所有权证明(Proof of Owernership,PoO),成对存储文件哈希和区块时间戳以实现存在证明(Proof of Existence,PoE)。最后,存储
哈希自身证明文件真实性,也就是说,如果文件被修改了,则它的哈希会随之改变,合约就
不能发现任何这样的文件了,由此证明文件被修改了。
相关智能合约的代码如下:3.16 编译和部署合约
以太坊提供了solc编译器,其中提供一个命令行界面编译.sol文件,请访问如下网
址:http:solidity.readthedocs.ioendevelopinstalling-solidity.htmlbinary -packages 找到安装指
南,并访问https:Solidity.readthedocs.ioendevelopusing-the-compiler.html 找到使用指南。我
们不会直接使用solc编译器;而是使用solcjs和Browser Solidity。Solcjs允许在node.js中以编程
方式编译Solidity,而Browser Solidity是一个适用于小型合约的IDE(集成开发环境)。
现在使用以太坊提供的浏览器编译前面的合约。如需深入相关知识,请访问
https:Ethereum.github.iobrowser-Solidity 。用户还可以下载Browser Solidity源代码,并离线
使用。请访问https:github.comEthereumbrowser-Soliditytreegh-pages 进行下载。
使用browser Solidity的主要优点是,它提供了一个编辑器(editor),并生成代码以部署
合约。
在编辑器中,复制粘贴前面的合约代码。将看到它编译并提供web3.js代码,以使用geth
交互操作台进行部署。
输出如下:
data代表EVM理解的字节码(by tecode)。源代码首先转换成opcode,然后再转换成字
节码。每个opcode都有相关gas。web3.eth.contract的第一个实参是ABI定义。在创建交易时使用ABI定义,因为它包含所
有方法的原型。
现在在开发者模式下启用挖矿,运行geth。运行如下命令:
现在打开另一个命令行窗口,在其中输入下面的命令,以打开geth的交互JavaScript操作
台:
这将使JS操作台连接到在另一个窗口运行的geth实例上。
在browser Solidity的右侧复制web3部署文本框的全部内容,并将其粘贴到交互操作台
上。现在按
交易哈希值是该交易的,每个交易的哈希都不一样。每个被部署的合约都有一个独特的合约
地址,以便在区块链中标识合约。
合约地址是确定的,它由生成器(creator)的地址(from address)和生成器发送的交
易数量(交易随机数)计算得到。这二者用RLP编码,然后使用k eccak -256 hashing算法进行
哈希计算。我们在后面还将深入学习交易随机数。若要更深入地学习RLP,请访问
https:github.comEthereumwikiwik iRLP 。
下面存储文件细节并检索。
用如下代码广播交易以存储文件细节:
这里用得到的合约地址代替合约地址。proofContract.at方法的第一个实参是合约地址。
这里并没有提供gas,它是自动计算的。下面发现文件细节。为了发现文件细节,运行如下代码:
会得到这样的输出:
call方法用于在EVM当前状态上调用一个合约的方法。它不广播交易。若要读取数据,则不需要广播,因为会有自己的区块链复制。
我们将在后面的几章中更多地学习web3.js。3.17 总结
在本章中,我们学习了Solidity编程语言以及数据位置、数据类型和合约的高级功能,还
学习了编译和部署智能合约最快速、最简便的方法,接下来应该放心地编写智能合约了。
在下一章中,我们将创建智能合约前端,这有利于部署智能合约和运行交易。第4章 开始使用web3.js
在前一章中,我们学习了编写智能合约的方法以及使用web3.js在geth交互接口上部署和
广播交易。在本章中,我们将学习web3.js的相关内容,包括如何导入、如何连接到geth以及
如何在node.js或者客户端JavaScript中使用它,还将学习如何使用web3.js为前一章中的智能
合约创建web客户端。
在本章中,我们将讲解以下内容:
·在node.js和客户端JavaScript中导入web3.js。
·将web3.js连接到geth。
·探索用web3.js可以做的各种事。
·探索web3.js最常用的几个API。
·为所有权合约创建node.js应用。4.1 web3.js概述
web3.js提供了用于和geth通信的JavaScript API。它内部使用JSON-RPC与geth通信。
web3.js还可以与所有种类的、支持JSON-RPC的以太坊节点通信。它把所有JSON-RPC API
当作JavaScript API,也就是说,它不仅支持所有与以太坊相关的API,还支持与Whisper和
Swarm相关的API。
随着不同项目的创建,我们会越来越了解web3.js。目前我们先来看一些最常用的
web3.js API,然后使用web3.js创建一个所有权智能合约前端。
在写本书时,web3.js的最新版本是0.16.0。本章所述内容也是这个版本。
web3.js托管在https:github.comethereumweb3.js ,完整文档
在https:github.comethereumwik iwik iJavaScript-API 。4.1.1 导入web3.js
为了在node.js中使用web3.js,可以在项目目录中运行npm install web3,且在源代码中可
以使用“require(web3);”导入它。
为了在客户端JavaScript使用web3.js,可以使web3.js文件入队,该文件可以在项目源代
码的dist目录中找到。现在,Web3对象对全局可用。4.1.2 连接至节点
web3.js可以与使用HTTP或者IPC的节点通信。我们将使用HTTP与节点建立通信。
web3.js允许与多个节点建立连接。一个web3实例代表与节点的一个连接。该实例公开了
API。
当在Mist中运行一个App时,它自动使一个连接到mist节点的web3实例可用。实例变量
名是web3。
为了连接到节点所使用的基础代码如下:
首先,通过检查web3是否是undefined,来确定代码是否在Mist中运行。如果web3被定义
了,则使用已经可用的实例;否则,通过连接至自定义节点创建一个实例。如果无论App是
否在Mist中运行都连接到自定义节点,则从程序代码中删除if。这里假设自定义节点在8545端
口本地运行。
Web3.providers对象使用多种协议显示构造函数(在此称为providers),以建立连接和传
输信息。Web3.providers.HttpProvider允许建立HTTP连接,Web3.providers.IpcProvider允许
建立IPC连接。
web3.currentProvider属性被自动分配给当前的provider实例。在创建web3实例之后,可
使用web3.setProvider方法改变provider。它有一个实参,即新provider的实例。
记住:geth默认禁用HTTP-RPC。所以在运行geth时通过--rpc选项以使用HTTP-
RPC。HTTP-RPC默认在8545端口运行。
web3显示isConnected方法,可用于查询是否已经与节点连接。根据连接状态的不
同,返回true或者false。4.1.3 API结构
web3包含一个eth对象(web3.eth),专门用于以太坊区块链交互;还包含一个shh对象
(web3.shh),用于whisper交互。web3.js的大部分API都在这两个对象中。
所有API都是默认同步的。如果想发出异步请求,可以把一个可选回调函数作为最后的
参数传送给大多数函数。所有回调函数都采用错误优先(error-first)回调方式。
一些API对于异步请求采用别名。例如web3.eth.coinbase是同步的,web3.eth.getCoinbase是异步的。示例如下:
getBlock使用区块序号或者哈希值获取区块信息。或者,它可以使用一个字符串,例
如earliest(创世区块)、latest(区块链最上面的区块)或者pending(正在挖的区
块)。如果不传送实参,则默认是web3.eth.defaultBlock,默认分配latest。
所有需要区块身份证明作为输入的API可以用序号、哈希值或者一个可读字符串作为输
入。如果值未通过,则这些API默认使用web3.eth.defaultBlock。4.1.4 BigNumber .js
JavaScript本质上对于正确处理大数字不在行。因此,需要处理大数字和进行完美计算的
应用会使用BigNumber.js库。
web3.js还依赖于BigNumber.js,且自动进行加载。web3.js总是对序号值返回BigNumber
对象。它可以用JavaScript数字、数字字符串和BigNumber实例作为输入,示例如下:
这里使用web3.eth.getBalance方法获取地址余额,该方法返回一个BigNumber对
象。需要在BigNumber对象上调用toString,把它转换成数字字符串。
BigNumber.js不能正确处理有超过20个浮点数位的大数字,因此推荐以wei为单位存储余
额,在显示时再转换成其他单位。web3.js自身总是以wei为单位返回和调取余额。例如,getBalance方法以wei为单位返回该地址的余额。4.1.5 单位转换
web3.js提供了把wei余额转换成任何其他单位和把任何其他单位余额转换成wei的API。
web3.fromWei方法用于将wei转换成其他单位,而web3.toWei方法用于将以其
他单位表示的数字转化成以wei为单位的数字。示例如下:
第一行代码将wei转换为ether;第二行代码将ether转换为wei。方法中的第二个实参可以
是以下字符串之一:
·kweiada
·mweibabbage
·gweishannon
·szabo
·finney
·ether
·k ethergrandeinstein
·mether
·gether
·tether4.1.6 检索gas价格、余额和交易细节
让我们看看API如何检索gas价格、地址余额和交易信息:
输出如下:
上述方法的执行过程如下:
·web3.eth.gasPrice。由x个最新区块的gas价格中位数决定gas价格。
·web3.ethgetBalance。返回任何给定地址的余额。所有web3.js API哈希地址应当是
十六进制的字符串,而不是十六进制的文字。solidity地址类型的输入也应当是十六进制的字
符串。
·web3.eth.getTransactionReceipt。用于获取交易使用其哈希的细节。如果在区块链
中发现交易,则返回交易收据对象;否则,返回null。交易收据对象包含下列属性:
·blockHash。该交易所在区块的哈希地址。
·blockNumber。该交易所在区块的序号。
·transactionHash。交易哈希。·transactionIndex。区块中交易索引位置的整数部分。
·from。发起人地址。
·to。接收者地址;如果是合约创建交易,则为null。
·cumulativeGasUsed。在区块中执行该交易时使用的gas总量。
·gasUsed。这个特定交易独自使用的gas量。
·contractAddress。如果交易是合约创建,表示被创建的合约地址;否则,为null。
·logs。该交易生成的日志对象数组。4.1.7 发送以太币
让我们看看如何向任意地址发送以太币。为了发送以太币,需要使用
web3.eth.sendTransaction方法。该方法可用于发送任意种类的交易,但主要用于发送以
太币,原因是使用这种方法部署合约或者调用合约方法比较麻烦——它要求生成交易数据而
不是自动生成交易数据。该方法的交易对象包含下列属性:
·from。发送账户的地址。如未标明,使用web3.eth.defaultAccount属性。
·to。可选项。信息目的地的地址,对于合约创建交易,该项未定义。
·value。可选项。通常在转账中单位为wei(在合约创建交易情况下,作为合约的资金注
入,单位也是wei)。
·gas。可选项。交易使用的gas量(未使用的gas被退回)。如果不提供,则自动决定该
项。
·gasPrice。可选项。交易中以wei为单位的gas价格,默认为网络平均gas价格。
·data。可选项。它或者是包含信息相关数据的字节字符串,或者是初始代码(在合约创
建交易情况下)。
·nonce。可选项。它是个整数。每一个交易都有一个相关计数nonce。该数字表示交易发
起人发送的交易数量。如果未提供nonce,则自动确定。它的作用是防止重播攻击。nonce不
是与挖区块相关的那个随机数。如果使用的nonce大于交易应当有的nonce,则交易被放入一
个队列直到其他交易数量到达。例如,如果下一个交易的nonce应该是4,而nonce被设为
10,则geth在广播这个交易之前将等待之间的6个交易。nonce为10的交易称为排队交易,而
不是待定交易。
向一个地址发送以太币的示例如下:
这里从账户0向账户1发送一个以太币。在运行geth时,确保使用unlock选项解锁两个账
户。在geth交互接口上,提示输入密码,但是如果账户被锁定,交互接口以外的web3.js API
将返回error。这个方法返回交易哈希。然后可以使用getTransactionReceipt方法检查是否
挖出了交易。
还可以用web3.personal.listAccounts、web3.personal.unlockAccount(addr,pwd)和web3.personal.newAccount(pwd)实时管理账户。4.1.8 处理合约
让我们学习如何部署一个新合约、如何使用一个已部署合约的地址获取其引用、如何向
合约发送以太币、如何发送交易以调用合约的函数(方法),以及如何估算一个函数调用的
gas。
若要部署一个新合约或者获取一个已部署合约的引用,首先需要使用
web3.eth.contract方法创建一个合约对象。该方法以合约ABI作为一个实参,并返回合约
对象。
创建合约对象的代码如下:
有了合约之后,可以使用合约对象的新方法部署它,或者使用at方法获取与ABI匹配
的、一个已部署合约的引用。
部署合约的示例如下:其中,new方法的调用是异步的,所以如果成功创建和广播交易,回调函数将被调用两
次。第一次,广播交易之后调用它;第二次,挖出交易之后调用它。如果不提供回调函数,则proof变量的address属性被设成undefined。挖出交易之后,address属性将被设置。
在proof合约中,没有构造函数,但是如果有构造函数,则构造函数实参应当放在new方
法的开头。传送的对象包含from地址、合约字节码和使用的gas上限。这三个属性必须存
在,否则无法创建交易。该对象可以有被传送给sendTransaction方法的对象所展示的属性,但是这里,data是合约字节码,且to属性被忽略。
可以用at方法引用一个已经部署的合约。相关代码如下:
现在让我们看看如何发送交易以调用合约方法。相关代码如下:这里调用方法同名对象的sendTransaction方法。被传送给这个sendTransaction方法的对
象属性与web3.eth.sendTransaction相同,只是data和to属性被忽略了。
如果想调用节点本地的方法,而非创建交易并广播,则可使用call而非sendTransaction。
示例如下:
有时必须发现找到调用方法所需的gas,这样可以决定是否调用。web3.eth.estimateGas
可用于此目的。然而,直接使用web3.eth.estimateGas要求生成交易,因此可以使用方法
同名对象的estimateGas方法。示例如下:
如果只想发送一些以太币到合约,而不调用任何方法,则可以使用
web3.eth.sendTransaction方法。4.1.9 检索和监听合约事件
现在让我们看看如何监听一个合约事件。监听事件很重要,因为通过交易调用方法的结
果通常是以触发事件的形式返回的。
在了解如何检索和监听事件之前,我们需要学习事件的索引参数。一个事件最多
有三个参数可以有被索引(indexed)属性。该属性用于提示节点对它进行索引,这样应用客
户端可以用匹配返回值来检索事件。如果不使用indexed属性,则必须检索所有事件,并筛选
出需要的那些事件。例如,可以这样编写logFileAddedStatus事件:
下面是给出了监听合约事件的一个示例:上述代码的执行过程如下:
1)调用一个合约实例的事件同名的方法获取事件对象。该方法用两个对象作为实参,用于筛选事件:
·第一个对象用索引返回数值筛选事件。例如,{'valueA':1,'valueB':
[myFirstAddress,mySecondAddress]}。所有筛选数值都默认设置为null。这意味着它们将匹
配该合约发出的任意类型事件。
·第二个对象可以包含三个属性,即fromBlock(搜索起始区块,默认为latest)、toBlock(搜索截至区块,默认为latest)和address(仅获取日志的地址列表;默认为合约地
址)。
2)事件对象显示三种方法:get、watch和stopWatching。get用于获取区块范围内的所有
事件。watch与get类似,但是它在获取事件后还监听变化。stopWatching可以用于停止监听变
化。
3)合约实例的allEvents方法用于检索合约的所有事件。
4)每一个事件由一个包含下列属性的对象代表。
·args。一个带有来自事件的实参的对象。
·event。用一个字符串表示事件名。
·logIndex。用一个整数表示区块中的日志索引位置。
·transactionIndex。用一个整数表示日志最初的交易索引位置。
·transactionHash。用一个字符串表示日志最初的交易哈希。
·address。用一个字符串表示日志最初的地址。
·blockHash。用一个字符串表示日志所在区块的哈希。如待定,则为null。
·blockNumber。日志所在区块的序号。如待定,则为null。
web3.js提供web3.eth.filter API以检索和监听事件。用户可以使用这个API,但是
处理事件的Event方法更简便。要想学习更多内容,请访问
https:github.comethereumwikiwikiJavaScript-APIweb3ethfilter 。4.2 为所有权合约创建客户端
在前一章中,我们为所有权合约编写了Solidity代码;在前一章和本章中,我们学习了
web3.js的有关知识和使用web3.js调用合约的方法。现在是时候为智能合约创建客户端了,这样方便用户使用。
创建一个客户端,用户从中选择一个文件,输入所有者细节,然后按下Submit按钮广播
交易,用文件哈希和所有者的细节调用合约的set方法。一旦交易被成功广播,将显示交易哈
希。用户还能够选择一个文件,并从智能合约中得到所有者的细节。客户端还将实时显示最
新挖出的set交易。
我们将在前端使用sha1.js获取文件哈希,使用jQuery进行DOM操纵,并使用Bootstrap 4
创建一个反应层(responsive layout)。在后端使用express.js和web3.js。我们将使用
socket.io,这样不需要前端间隔相等的时间请求数据,后端就把最近挖出的交易推到前端。
web3.js可以在前端使用,但对于应用是个安全漏洞。也就是说,我们在使用存储
在geth中的账户,并把geth节点URL显示给前端,这将使存储在那些账户中的以太币面临风
险。4.2.1 项目结构
在本章的练习文件中,将发现两个目录:Final和Initial。Final包含项目的最终源代码,而
Initial包含可以用于迅速创建应用的空的源代码文件和库。
为了测试Final目录,需要在其中运行npm install,并把app.js中硬编码的合约地址
替换为在部署合约之后得到的合约地址。然后,使用Final目录中的node app.js命令运行该应
用。
在Initial目录中,将发现一个public目录和两个文件(app.js和pack age.json)。
package.json包含应用的后端相关内容,app.js包含应用的后端源代码。
public目录包含与前端相关的文件。在publiccss中会发现bootstrap.min.css,它是
Bootstrap库;在publichtml中会发现index.html,所应用的HTML代码放在这里;在publicjs目
录中将发现jQuery、sha1和sock et.io的JS文件。在publicjs中还会发现一个main.js文件,应用
的前端JS代码放在这里。4.2.2 创建后端
先创建App后端。首先,在initial目录中运行npm install,为后端安装所需相关内容。其
次,在进行后端编码之前,确保geth运行时启用rpc。如果是在私有网络上运行geth,要确保
启用mining。最后,确保账户0存在并被解锁。可以在私有网络上运行geth,这时需要启用
rpc和mining,并解锁账户0:
编码开始前最后需要做的一件事是,使用在前一章中见到的代码部署所有权合约,并复
制合约地址。
现在创建一个单独的服务端,它将为浏览器提供HTML,并接收socket.io连接:
这里把运行在端口8080上的两个服务端express和socket.io合并成一个服务端。
现在创建路径以用于静态文件和App主页。相关代码如下:
这里使用了express.static中间件,用于在公共目录中发现静态文件。
现在连接到geth节点,并获取已部署合约的引用,这样可以发送交易并监听事件。相关
代码如下:上述代码就是用得到的合约地址替换原有的合约地址。
现在创建广播交易和获取文件信息的路径。相关代码如下:
其中,“submit”路径用于创建和广播交易。获取交易哈希之后,把它发送给客户端。然
后等待挖出交易。“getInfo”路径用于调用节点自身的合约get方法,而非创建交易。它仅仅发送回所得到的回应。
现在监听来自于合约的事件,并向所有客户端广播。相关代码如下:
这里需要检查一下状态是否为true,如果为true,才能向所有连接的socket.io客户端广播
事件。4.2.3 创建前端
让我们从应用的HTML开始创建前端。把下面的代码放入index.html文件:上述代码的执行过程如下:
1)显示Bootstrap的文件输入框,这样用户可以选择一个文件。
2)显示一个文本框,用户可以输入所有者的细节。
3)得到两个按钮。一个用于存储文件哈希和合约中的所有者细节,另一个用于从合约
中获取文件信息。单击Submit按钮触发submit方法,单击Get Info按钮触发getInfo方
法。
4)得到一个显示信息的报警框。
5)显示一个有序列表,以显示用户在该页面上时被挖出的合约交易。
接下来为getInfo和submit方法编写实现,与服务端建立socket.io连接,并从服务
端监听sock et.io信息。
相关代码如下。把该代码放入main.js文件:上述代码的执行过程如下:
1)定义submit方法。在submit方法中,确保选择一个文件,且文本框不为空,然后
读取文件内容作为数组缓存,并传送数组缓存给sha1.js显示的sha1方法,以获取数组缓
存中的内容哈希。得到哈希之后,使用jQuery发出一个AJAX请求给“submit”路径,然后在
报警框中显示交易哈希。2)定义getInfo方法。该方法首先确定选中一个文件,然后就像之前一样生成哈
希,并发出请求到“getInfo”端点,以得到关于那个文件的信息。
3)使用sock et.io库显示的io方法建立socket.io连接,然后等待事件连接到触发器——
这表示连接已经建立。在连接建立之后,监听来自服务端的信息,并向用户显示交易细节。
之所以不在以太坊区块链中存储文件,是因为存储文件很昂贵——它需要大量
gas。对于本节的示例子,其实不需要存储文件,因为网络中的节点将可以看见文件。因此,如果用户希望文件内容是秘密的,其实是做不到的。这里的应用是想证明一个文件的所有
权,而不是像云服务那样存储和服务文件。4.2.4 测试客户端
运行app.js节点,以运行应用服务端。打开浏览器,访问http:localhost:8080 ,可以看
到图4-1所示的界面。图 4-1
现在选择一个文件,输入所有者姓名,单击Submit按钮,界面将变为图4-2所示的样子。图 4-2
在这里可以看到显示交易哈希。现在等待,直到交易被挖出。一旦挖出,就可以在当前
交易列表中看到交易,如图4-3所示。图 4-3
现在再次选择同一个文件,单击Get Info按钮,界面如图4-4所示。图 4-4
在这里可以看到时间戳和所有者的细节。至此,为第一个DApp创建客户端的工作就完
成了。4.3 总结
在本章中,我们首先通过示例学习了web3.js的基础知识,包括如何连接至节点、基础
API、发送不同种类的交易以及监听事件,最后为所有权合约建立了一个适合生产用途的客
户端。现在可以编写智能合约和创建UI客户端了。
在下一章中,我们将创建钱包服务,可供用户在其中方便地创建和管理以太坊钱包,这
也是离线的。我们将专门使用LightWallet库实现上述目的。第5章 创建钱包服务
钱包服务用于发送和接收钱款。创建钱包服务面临的主要挑战是安全和信任。用户必须
觉得他的钱是安全的,并相信钱包服务管理员不会偷他的钱。本章所涉及的钱包服务将处理
这些问题。
本章将讲解以下内容:
·在线钱包和离线钱包的区别。
·用Hook ed-Web3-Provider和EthereumJS-tx库使创建和签署那些没有被以太坊节点管理的
账户交易变得容易。
·理解HD钱包的概念及其使用方法。
·使用LightWallet.js创建HD钱包和交易签名者。
·创建钱包服务。5.1 在线钱包和离线钱包的区别
钱包是多个账户的集合,账户是一个地址及其相关私钥的集合。
如果一个钱包与互联网相联,则称其为在线钱包。例如,在geth中存储的钱包、任何网
站数据库等都称为在线钱包。在线钱包也称为热钱包、Web钱包、托管钱包等。不推荐使用
在线钱包,至少在存储大量以太币或者长期存储以太币时不推荐使用,因为有风险。而且根
据钱包存储位置的不同,它还可能要求信任第三方。
例如,最热门的钱包服务本身存储钱包私钥,并允许用户通过e-mail和密码访问钱包,所以用户基本上不会实质性地访问钱包,如果有人想偷,就能偷钱包里的钱。
如果一个钱包不与互联网相联,则称其为离线钱包。例如,存储在闪存盘、纸张、文本
文件等中的钱包。离线钱包也称为冷钱包。离线钱包比在线钱包更安全,因为要偷钱的人必
须能够访问物理内存。离线存储的问题是,用户需要找到一个不会意外删除或者忘记的位
置,或者让其他任何人都不能访问的位置。如果想长期安全地保管钱款,许多人会在纸上存
储钱包,然后把纸放入保险箱。如果想从账户频繁地发送钱款,则可以存在带有密码保护的
闪存盘和保险箱里。用数字设备存储钱包有点危险,因为数字设备可能随时坏掉,那样就无
法访问钱包了。这就是为什么既要存在闪存盘中,还应当存在保险箱里。根据需求的不同,用户还可以找到更好的解决方法,但是必须确保方法安全,且不会意外地丢失对钱包的访问
路径。5.2 Hooked-Web3-Provider和EthereumJS-tx库
到目前为止,Web3.js库的sendTransaction方法的所有例子都使用以太坊节点出现的
from地址,因此以太坊节点能够在广播之前签署交易。但是如果用户把钱包的私钥存储在其
他地方,geth就发现不了它。因此在这种情况下,需要使用web3.eth.sendRawTransaction
方法广播交易。
web3.eth.sendRawTransaction用于广播原始交易,也就是说,用户不得不编写代码
来创建和签署原始交易。以太坊节点将直接广播,而不对交易做任何其他操作。但是使用
web3.eth.sendRawTransaction编写代码以广播交易并非易事,因为它要求生成数据部
分、创建原始交易并签署交易。
Hooked-Web3-Provider库提供自定义程序提供方(custom provider),它使用HTTP与
geth通信。这个提供方的独特之处在于,它允许使用密钥签署合约实例的
sendTransaction调用,因此不再需要创建交易的数据部分了。自定义程序提供方事实上
重写了web3.eth.sendTransaction方法的实现,所以基本上它允许签署合约实例的
sendTransaction调用以及web3.eth.sendTransaction调用。合约实例的
sendTransaction方法在内部生成交易数据,并调用web3.eth.sendTransaction广播交
易。
EthereumJS是一系列与以太坊相关的库。EthereumJS-tx是其中之一,它提供了多种与交
易相关的API,例如,允许创建原始交易、签署原始交易、检查交易是否正确使用密钥进行
了签名,等等。
这两个库对node.js和客户端JavaScript可用。访问https:www.npmjs.compack agehooked-
web3-provider 可下载Hook ed-Web3-Provider,访问
https:www.npmjs.compack ageethereumjs-tx 可下载EthereumJS-tx。
在写本书时,Hooked-Web3-Provider的最新版本是1.0.0,EthereumJS-tx的最新版本是
1.1.4。
下面来看如何使用这些库从一个不由geth管理的账户发送交易。上述代码的执行过程如下:
1)创建一个Hook edWeb3Provider实例(由Hook ed-Web3-Provider库提供)。该构造函
数有一个对象,这个对象有两个必须提供的属性host和transaction_signer。host是节点的HTTP
URL,transaction_signer是自定义服务提供方用于签署交易的通信对象。
2)transaction_signer对象有两个属性hasAddress和signTransaction。调用hasAddress检查
交易是否可以签署,即检查交易签署者是否有from地址账户的私钥。该方法接收地址和一个
回调函数。如果找不到地址私钥,回调函数的第一个实参应当是错误信息,第二个实参应当
是false;如果找到地址私钥,第一个实参应当是null,第二个实参应当是true。3)如果发现地址私钥,则自定义程序提供方调用signTransaction方法得到交易签名。该
方法有两个参数,即交易参数和回调函数。在这个方法中,首先将交易参数转换为原始交易
参数,也就是说,将原始交易参数数值编译为十六进制的字符串。然后创建一个缓存存储私
钥。缓存使用EthJS.Util.toBuffer方法创建,该方法是EthereumJS-util库的一部分。
EthereumJS-util库由EthereumJS-tx库导入。接下来创建一个原始交易并签名,之后编序号,并转换成十六进制字符串。最后需要用回调函数为自定义服务提供方提供签名原始交易的十
六进制字符串。该方法发生错误时,回调函数的第一个实参应当是一个错误信息。
4)自定义服务提供方进行原始交易,并用web3.eth.sendRawTransaction进行广播。
5)调用web3.eth.sendTransaction函数向另一个账户发送若干以太币。这里需要提供
nonce以外的所有交易参数,因为自定义服务提供方可以计算nonce。此前,许多参数都是可
选项,因为把它们留给了以太坊节点进行计算,但是由于这里要自己签署,就需要提供所有
交易参数。如果交易没有任何相关数据,则gas总是21000。
公钥的情况呢?
在上述代码中,并未提及签署地址的公钥。如果矿工没有公钥,该如何验证交易的真实
性?矿工使用了ECDSA算法的一个独特属性,该属性允许矿工通过信息和签名计算公钥。在
交易中,信息表示交易意向,签名用于发现签署信息时是否使用了正确的私钥。这是ECDSA
算法的独特之处。EthereumJS-tx提供一个API用于验证交易。5.3 分层确定性钱包
分层确定性钱包(Hierarchical Deterministic wallet,HD钱包)是由一个单独的起点(称
为seed,即种子)衍生的地址和密钥的集成系统。确定性表明对于相同的seed生成相同的地
址和密钥,分层表明地址和密钥以相同顺序生成。它使备份和存储多个账户变得容易,因为
用户只需要存储seed,而不用存储单个密钥和地址。
为什么用户需要多个账户?
为什么用户需要多个账户?因为要隐藏财产。账户余额在区块链中公开可用,所以,如
果用户A与用户B分享地址以接收一些以太币,则用户B可以查看该地址中有多少以太币。因
此,用户通常在多个账户中存有财产。
HD钱包有多种类型,它们的种子格式以及生成地址和密钥的算法不同,例如BIP32、Armory、Coink ite、Coinb.in等。
什么是BIP32、BIP44和BIP39?
比特币改进提议(Bitcoin Improvement Proposal,BIP)是一个为比特币社区提供信息的
设计文档,它描述比特币的一个新功能或者其过程或者环境。BIP应当为该功能提供一份简
明技术规范和功能基本原理。在写本书时,有152个BIP。BIP32和BIP39分别提供关于实现
HD钱包和助记种子规范(mnemonic seed specification)的算法信息。
如要学习更多BIP的相关知识,请访问https:github.combitcoinbips 。5.4 密钥衍生函数
不对称的加密算法定义密钥的性质以及生成密钥的方法,因为密钥需要相互关联。例
如,RSA密钥生成算法是确定性的。
对称的加密算法只定义密钥长度。密钥由用户来生成。有多种算法生成密钥,其中一种
是KDF。
密钥衍生函数(KDF) 是用于从一些密值(例如主密钥、密码)中衍生对称密钥的确定
性算法。有多种类型的KDF,例如bcry pt、crypt、PBKDF2、scrypt、HKDF等。如要学习更
多KDF的相关知识,请访问https:en.wik ipedia.orgwik iKey _derivation_function 。
为了从一个密值生成多个密钥,可以对一个数执行拼接(concatenate)和递增
(increment)运算。
基于密码的密钥衍生函数用一个密码生成一个对称密钥。由于在实践中,用户通常使用
较弱的密码,基于密码的密钥衍生函数比较慢,占用了大量内存,使其难以启动强力攻击和
其他攻击。基于密码的密钥衍生函数使用广泛,因为难以记住密钥,且存储也有风险——有
可能被盗。PBKDF2是一个基于密码的密钥衍生函数的例子。
主密钥或者密码难以使用强力攻击破解;因此,如果你想从主密钥或者密码生成一个对
称密钥,可以使用不以密码为基础的密钥衍生函数,例如HKDF。HKDF比PBKDF2快得多。
为什么使用KDF,而不使用哈希函数?
哈希功能的输出可以当作对称密钥加密技术使用。所以你肯定在奇怪为什么需要KDF。
如果使用的是主密钥、密码或者强密码,那么使用一个哈希函数就行了。例如,HKDF仅使
用哈希函数生成密钥。但是如果不能保证用户使用强密码,最好使用哈希函数衍生的密码。5.5 LightWallet
LightWallet是一个实现BIP32、BIP39和BIP44的HD钱包。LightWallet提供API来创建和
签署交易,或者使用LightWallet生成的地址和密钥加密和解密数据。
LightWallet API被分成4个命名空间,即k eystore、signing、encry ption和txutils。signing、encrpy tion和txutils分别用来提供API以签名交易、非对称的密码和创建交易,而keystore命名
空间用于创建keystore、生成种子等。k eystore是一个存储加密种子和密钥的对象。如果使用
Hooked-Web3-Provider,keystore命名空间实现交易签名者方法,该方法要求签署
we3.eth.sendTransaction调用。因此k eystore命名空间对于在其中发现的地址可以自动创
建和签署交易。实际上,LightWallet的主要目的是成为Hooked-Web3-Provider的一个签名提
供方。
可以配置密钥存储实例,来创建和签署交易或者加密和解密数据。签署交易用secp256k1
参数,加密和解密用curve25519参数。
LightWallet的种子是一个12词的助记符,容易记住但不容易进行破解。它不是任意12个
词,而是LightWallet生成的种子。LightWallet生成的种子在选择词和其他东西方面有特定的
属性。
HD衍生路径
HD衍生路径是一个字符串,可以使多个加密货币(假设它们使用相同的签名算法)、多个区块链和多个账户等的处理变得容易。
HD衍生路径需要多少参数就可以有多少参数,还可以对参数使用不同的数值,可以产
生不同的地址群和相关密钥。
LightWallet默认使用m0' 0' 0'衍生路径。这里,n'是参数,n是参数值。
每个HD衍生路径都有curve和purpose。purpose可以是sign或者asymEncry pt。sign表示该
路径用于签署交易,asyEncrypt表示该路径用于加密和解码。curve表示ECC的参数。为了签
名,参数必须是secp256k 1;对于不对称加密,curve必须是curve25591,因为LightWallet出于
自身利益强迫我们使用这些参数。5.6 创建钱包服务
我们已经学习了关于LightWallet的理论,现在是时候用LightWallet和Hooked-Web3-
Provider创建钱包服务了。钱包服务将允许用户生成独一无二的种子,显示地址和相关余
额,最后将允许用户发送以太币给其他账户。所有操作都在客户端上进行,这样比较容易取
得用户的信任。用户必须记住种子或者把它存储在某个地方。5.6.1 必要条件
在开始创建钱包服务之前,应确保运行geth开发实例(即挖矿),它已启动了HTTP-
RPC服务器,允许来自任何域名的客户端请求,最后解锁账户0。运行下面的代码:
其中,--rpccorsdomain用于允许一些特定域与geth通信。需要提供一个以空格分隔的域
名列表,例如“http:localhost:8080 https:mySite.com ”。它还支持通配符。--rpcaddr表示
geth服务器可以到达哪个IP地址。默认的是127.0.0.1,所以如果它是一个托管服务器,就不
能使用服务器的公共IP地址到达它。因此,将它的值改为0.0.0.0,这表示该服务器可以使用
任何IP地址到达。5.6.2 项目结构
在本章的练习文件中,你将发现Final和Initial两个目录。Final包含项目的最终源代码,而
Initial包含可以用于迅速创建应用的空的源代码文件和库。
为了测试Final目录,需要在其中运行npm install,然后使用Final目录中的node
app.js命令运行该应用。
在Initial目录中,你将发现一个public目录和两个文件(app.js和package.json)。
package.json包含应用的后端相关内容,把后端源代码放在app.js里。
public目录包含与前端相关的文件。在publiccss中会发现bootstrap.min.css,它是bootstrap
库;在publichtml中会发现index.html,把应用的HTML代码放在这里;在publicjs目录中将发
现Hooked-Web3-Provider、web3js和LightWallet的.js文件。在publicjs中还会发现一个main.js
文件,把应用的前端JS代码放在这里。5.6.3 创建后端
先来创建App后端。首先,在Initial目录中运行npm install,为后端安装所需相关内容。
运行快捷服务并用于index.html文件和静态文件的完整后端代码如下:
上述代码无须解释。5.6.4 创建前端
现在开始创建App前端。前端所包括的主要功能有生成种子、显示种子地址和发送以太
币。
编写应用的HTML代码。把如下代码放入index.html文件中:上述代码的执行过程如下:
1)把Bootstrap 4样式表排入队列。
2)显示一个信息框,上面将显示多个信息。
3)得到一个表单,上面有一个输入框和两个按钮。输入框用于输入seed或者在生成新的
seed时显示seed。
4)Generate Details按钮用于显示地址,Generate NewSeed按钮用于生成一个新的、独
一无二的seed。用户单击Generate Details ......
您现在查看是摘要介绍页, 详见PDF附件(8088KB,329页)。





