原文:
zh.annas-archive.org/md5/7f932e9670331dae388d1a76f72881d8
译者:飞龙
第十一章:Hyperledger Fabric 安全性
Hyperledger Fabric 是一个模块化的区块链系统。它经过设计允许一组已知的参与者在区块链网络中参与并执行操作(所谓的许可区块链)。由于其模块化的特性,它可以在许多不同的配置中部署。Hyperledger Fabric 的不同部署配置对网络操作者及其用户具有不同的安全影响。
在其核心,Hyperledger Fabric 是一个公钥基础设施(PKI)系统,因此它继承了与这类系统相关的安全性(和复杂性)。在编写本书时,Hyperledger Fabric v1.1 已发布。
设计和实现区块链网络的安全方面已在先前的应用章节中讨论过,我们打算在这里更广泛并更深入地了解 Hyperledger Fabric 的安全特性。
在本章中,我们将涵盖以下主题:
-
影响安全性的设计目标
-
Hyperledger Fabric 架构简述
-
网络引导和治理 - 安全的第一步
-
强身份 - Hyperledger Fabric 网络安全的关键
-
链代码安全性
-
常见安全威胁以及 Hyperledger Fabric 如何减轻这些威胁
-
Hyperledger Fabric 和量子计算
-
常规数据保护法规(GDPR)考虑
影响安全性的 Hyperledger Fabric 设计目标
要了解 Hyperledger Fabric 的安全性,重要的是阐明影响安全性的关键设计目标:
-
现有成员应确定如何在网络中添加新成员:网络中的现有实体必须就是否接收新实体的加入达成一致意见。这一原则是创建许可区块链的基础。与其允许任何实体下载软件并连接到网络,网络成员必须就接受新成员的政策达成一致意见(例如,通过多数投票),然后由 Hyperledger Fabric 强制执行。在成功投票后,新成员的数字凭证可以被加入到现有网络中。
-
现有成员应确定如何更新配置/智能合约:与第一条类似,网络配置的任何更改或部署或实例化智能合约都必须得到网络成员的同意。总结起来,第一和第二点赋予了 Hyperledger Fabric 执行许可区块链的能力。
-
账本及其相关的智能合约(链码)可能会被范围化以满足更广泛的隐私和保密要求:在公共区块链网络中,所有节点都拥有区块链账本的副本并执行智能合约。为了保持机密性和范围化,需要创建存储与其交易相关的账本的一组节点(在 Hyperledger Fabric 中为通道和通道私有数据)。更新这种账本的智能合约(在 Hyperledger Fabric 中为链码)将范围化到这样一个组的成员。
只有参与通道的成员才需要确定如何更新该通道的配置。
-
智能合约可以用通用语言编写:Hyperledger Fabric 的主要设计目标之一是允许智能合约用通用语言编写,如 Go 和 JavaScript。显然,如果在执行前没有治理和流程来验证和部署智能合约,允许通用语言编写智能合约会暴露系统于各种安全问题。即便如此,用通用语言编写的智能合约也应该被合理地隔离,以限制它们可能无意中造成的危害。
-
必须确保事务完整性:事务是智能合约的执行。事务必须以一种方式创建和存储,以防止其他节点篡改它们,或者使其易于检测到任何篡改。通常,确保事务完整性需要使用加密原语。
-
应利用行业标准:系统应利用行业标准来断言数字身份(例如,X.509 证书),以及节点之间的通信(例如,TLS 和 gRPC)。
-
共识与交易执行和验证分离:现有的区块链网络将交易执行和验证与区块链网络中的节点达成共识紧密结合在一起。这种紧密耦合使得很难实现共识算法的可插拔性。
-
无处不可插拔:系统应具有模块化设计,并且每个模块都应通过标准接口可插拔。能够插入特定于网络的模块使得 Hyperledger Fabric 具有在各种环境中使用的灵活性。然而,这种可插拔性也意味着基于 Hyperledger Fabric 的两个不同的区块链网络实例可能具有不同的安全属性。
要了解这些原则如何影响 Hyperledger Fabric 的安全性,我们将简要解释 Hyperledger Fabric 的架构。详细的架构请参考前面的章节。
Hyperledger Fabric 架构
Hyperledger Fabric 架构如下所示:
Hyperledger Fabric 架构
Fabric CA 或成员服务提供商
成员服务提供商(MSP)负责为组织的节点和用户创建数字身份。节点的身份必须在现有网络中进行配置,以便新实体可以参与通道。
Fabric CA 是 MSP 的一种实现,提供了从网络成员注册用户并为他们颁发数字身份(X.509 证书)的机制。Fabric CA 通常在 Docker 容器中运行。每个 Fabric CA 都配置有后端数据库(默认为 SQLite,其他选项包括 PostgreSQL 或 MySQL),用于存储注册的身份,以及它们的 X.509 证书。Fabric CA 不存储用户的私钥。
节点
一个节点是参与 Hyperledger Fabric 网络的实体。它的身份是根据其对应的成员服务提供商确定的。节点负责部署和实例化链代码,更新账本,与其他节点交互以共享与交易相关的私有数据,并与订购服务以及其运行的智能合约(在前面的截图中称为链代码)进行交互。类似于 Fabric CA,一个节点通常也在 Docker 容器中运行。
智能合约或链代码
智能合约(SC)是应用逻辑,以高级语言(如 Go 或 JavaScript)编写;成功执行时,它读取或写入最终提交到账本的数据。智能合约没有直接访问账本的权限。一个节点可以部署零个或多个作为 Docker 容器运行的智能合约。一个节点也可以部署多个版本的智能合约。
账本
每个节点维护着一个数字账本,其中包含节点接收到的所有已提交交易的记录。账本中的条目以键/值对的形式存储。对同一键的更新将用新值替换键的当前值。当然,旧值将保留在账本中。为了有效查询键的最新值,一个节点可以将每个键的最新值存储在诸如CouchDB
之类的数据库中。这个数据库在超级账本中被称为世界状态。
请注意,一个节点只会从它参与的通道接收要提交到它账本的区块。
一个节点可以是零个或多个通道的一部分 —— 在前面展示超级账本架构的图表中没有显示通道。
私有数据
随着 Hyperledger Fabric v1.1,节点可以选择通过链私有数据实验性功能(jira.hyperledger.org/browse/FAB-1151
)与通道中的一部分节点选择性共享私有数据。账本上的区块只包含此类数据的哈希,而私有数据存储在账本之外的私有状态数据库中。
订购服务
订购服务负责接收来自对等方的执行交易,在将它们组合成区块,并将其广播给相同通道上的其他对等方。接收交易区块的对等方在提交到总账簿之前会对其进行验证。订购服务的责任是不将一个通道上的区块混入到另一个通道上。
在 Hyperledger Fabric 1.0 版本中,对等方会将交易(密钥和关联值,以及读/写集)发送到订购服务。因此,订购服务对于与交易相关的所有数据都是可见的,这在保密方面有着影响。在 Hyperledger Fabric 1.1 版本中,客户端可以将交易数据的哈希值(输入和读/写集)发送到订购服务,同时将与交易相关的数据直接传输给相关的对等方。
目前,订购服务是使用 Kafka 实现的,并且是崩溃容错(CFT)的,但不是拜占庭错误容忍(BFT)。但这是一个时间点的说法,因为 HyperLedger 据称是可插拔的,包括共识服务。可插拔性意味着未来可能会提供其他共识模型。
尽管在描述 Hyperledger Fabric 架构的图表中没有显示,但对等方,订购者和 fabric 使用可插拔的加密服务提供者,允许他们插入新的加密算法以及硬件安全模块(HSMs)(en.wikipedia.org/wiki/Hardware_security_module
)来管理加密密钥。
网络引导和治理 - 通往安全的第一步
当组织决定使用 Hyperledger Fabric 组建许可的私有区块链网络时,他们需要考虑几个治理方面,这将最终决定网络的整体安全状况。这些治理方面包括但不限于以下内容:
-
网络应如何引导启动和成员验证以创建网络? 网络引导是创建区块链网络的第一步。不同的实体可以一起创建一个网络。这些实体可以进行场外通信,就第一批成员达成一致,并制定治理政策,接下来将进行讨论。
-
新实体加入网络(或通道)的流程是什么? 确定网络中新成员的准入政策是至关重要的,并由网络的业务需求来规定。
-
谁可以在网络上的对等方上部署和升级链码? 确定一个过程是重要的,以防止恶意或有 bug 的链码被安装在一个或多个对等方上(参见第七章,业务网络示例)。
-
将在区块链上存储什么数据模型? 成员必须就将存储在区块链中的共同数据模型达成一致意见;否则,区块链对其成员将毫无用处。数据模型应设计得不会违反任何合规法规,例如通用数据保护条例(GDPR)(
gdpr-info.eu/
)。
创建网络
当实体决定创建网络时,他们必须决定以下事项:
-
谁将运行排序服务
-
网络中将有多少不同的排序服务实例
排序服务的角色非常关键,因为根据配置,它可以看到流经它的所有频道的交易哈希或交易数据。因此,决定组建网络的实体可以选择信任其中一个实体充当排序服务;他们还可以决定信任中立的第三方来运行排序服务。
排序服务可以查看其提供服务的所有频道中的所有交易(哈希或键值对)。因此,如果需要向排序服务隐藏交易数据,则在对等方之间直接交换数据时,应仅向排序服务发送交易中读/写集的哈希。
一旦为网络建立了排序服务,就必须将创始成员的对等方的数字身份配置到其中。这通常是通过在排序服务创世区块中配置对等方的数字证书来完成的。对等方还必须配置排序服务的数字身份。
添加新成员
在创建网络或频道时的创始成员必须还要定义新成员如何加入网络或频道的政策。默认情况下,此政策通常是由多数人选择的(即两个中的两个,三个中的两个,四个中的三个等)。成员可以决定网络中任何其他接纳新成员的政策。对于接纳新成员的政策的任何更改通常将通过商业协议决定。一旦达成协议,就可以根据当前政策更新频道配置,以反映接纳新成员的新政策。
创世区块的创建以及用于更新配置的后续交易是特权操作,必须在确认之前经过对等方管理员的批准。
部署和更新链码
一旦成员决定参与通道,他们可以选择部署和实例化链码(又称智能合约)。链码定义了如何更新或读取通道范围内的键/值对。链码可以定义其认可策略——即,它可以要求网络中一些或所有对等方的数字签名。由于 Hyperledger Fabric 的权限性质,需要对等方的数字签名(认可)的链码必须安装和实例化在对等方上。有关部署链码的更多详细信息,请参见第五章、公开网络资产和交易和第七章、业务网络示例。
在通道上部署链码之前,预期网络成员将希望审核链码,以确保其符合其政策。此过程可以正式化为链码治理,要求所有相关成员对其节点实例化的链码进行强制审核。
建立一个在同行上部署链码的流程,包括手动审核和验证链码作者数字签名。
数据模型
实体必须就将存储在区块链中的数据模型达成一致意见,这反过来由链码确定。网络或通道部署链码的创始成员将确定存储在通道中的键/值对。此外,成员将决定哪些数据与其他成员共享,哪些数据他们将保留私有给自己或一部分成员。数据模型应设计得对成员希望完成的业务功能有用,合理地具有未来的保障,并且不会无意中泄露信息。请记住,通道中的所有参与对等方都存储了提交的交易(及其键/值对)。
建立一个定义将存储在通道中的数据模型的流程。
前述步骤可总结如下:
-
确定谁将运行订购服务。
-
配置订购服务中创始成员的数字身份。
-
创建通道并确定接纳新成员的通道政策。
-
定义编写、分发、部署和实例化链码的治理。
-
建立数据模型。
强身份——Hyperledger Fabric 网络安全的关键。
强身份是 Hyperledger Fabric 安全的核心。创建、管理和撤销这些身份对于基于 Hyperledger Fabric 的部署的运行安全至关重要。这些身份由一个 MSP 发行。如前面的 Hyperledger Fabric 架构图所示,一个逻辑 MSP 通常与一个对等体关联。MSP 可以发行任何适当的加密签名的身份。Hyperledger Fabric 配备了一个默认的 MSP(Fabric CA),该 MSP 为经过身份验证的实体发行 X.509 证书。
引导 Fabric CA
Fabric CA 可以配置一个 LDAP 服务器或以独立模式运行。当以独立模式运行时,必须配置一个引导身份,该身份存储在 Fabric CA 的后端数据库中。默认情况下,使用 SQLite 数据库,但对于生产用途,可以配置 PostgreSQL 或 MySQL 数据库。如果使用独立服务器,则 Fabric CA 服务器与其数据库之间的连接通常通过 TLS 进行。
在本章的其余部分,我们将在没有 LDAP 服务器的情况下运行时将引导实体称为 ca-admin
。在没有 LDAP 服务器的情况下运行时,必须在 Fabric CA 的引导时提供 ca-admin
及其密码。
为了让 ca-admin
与服务器交互,必须向 Fabric CA 服务器提交一个证书签名请求(CSR)以获取 X.509 证书。这个过程称为注册身份,或简称注册。拥有 X.509 证书后,ca-admin
就可以添加其他用户,我们将在下面解释。
将管理员用户的密码保存在安全的地方,因为这是您组织的 root
用户。将其视为您对待 root
Linux 用户的密码一样安全。使用它创建具有适当权限的新用户,但除了在安全漏洞的情况下,永远不要使用此用户进行任何其他操作,在这种情况下,此用户可用于撤销所有注册实体的证书。
Fabric CA 在系统中提供了两个关键操作,即注册和登记。我们将在下面解释这些操作。
注册
注册操作将指定的标识符添加到 Fabric CA 中的新实体。注册操作不为用户创建 X.509 证书;这在注册操作中发生。由 Fabric CA 的管理员定义将新用户添加到网络的政策和程序。
在注册用户时,有一些重要的注意事项:
- 如果策略是注册电子邮件地址,则在随后的注册过程中,用户的电子邮件地址将被编码在证书中。在 Hyperledger Fabric 中,发起交易的用户的证书与提交的交易一起存储在账本中。任何人都可以解码证书并确定电子邮件地址。
仔细确定如何在 Fabric CA 中注册新实体,因为当这些实体发出交易时,它们的数字证书将最终出现在账本上。
- 另一个重要考虑点是允许该用户的注册次数。每个注册将导致向用户发放新证书。在 Hyperledger Fabric 中,正在注册的新用户可以被注册有限次数,或者可以具有无限次数的注册。通常,正在注册的新实体不应该配置为具有无限次数的注册。
对于新用户,最好将最大注册数设置为 1。这个设置确保实体和其数字证书之间存在一对一的对应关系,从而使实体吊销管理更容易。
- 使用 Hyperledger Fabric 1.1,现在可以在注册时为实体定义属性。然后,这些属性将被编码到实体的 X.509 证书中。
在独立模式下使用时,在成功注册时,Fabric CA 将创建一个唯一密码(如果在注册过程中未提供)。然后,ca-admin
可以将此密码传递给被注册的实体,后者将使用它创建 CSR 并通过注册操作获取证书。
默认 Fabric 角色
在 Fabric CA 中注册实体时,实体应该具有一组角色。Fabric CA 配置了以下默认角色:
hf.Registrar.Roles = client, user, peer, validator, auditor
Fabric CA 可以注册任何具有以下角色之一的实体:
hf.Registrar.DelegateRoles = client, user, validator, auditor
Fabric CA 可以吊销角色:
hf.Revoker = true
Fabric CA 还可以注册中间 CA:
hf.IntermediateCA
在 Fabric CA 中注册身份,实体必须具有 hf.Registrar
。角色被赋予一个逗号分隔的值列表,其中一个值等于正在注册的身份类型。
其次,调用者身份的关联必须等于或是被注册身份的关联的前缀。例如,具有 a.b
关联的调用者可以注册具有 a.b.c
关联的身份,但不能注册具有 a.c
关联的身份。
注册
拥有 ID 和密钥的实体随后可以使用 Fabric CA 注册自己。为此,它生成公钥/私钥对,创建 CSR,并将其与 Authorization
标头中注册的 ID 和密钥一起发送到 Fabric CA。验证成功后,服务器将向被注册的实体返回 X.509 证书。发送注册请求的实体负责管理私钥。这些私钥应该以安全的方式存储(如硬件安全模块)。
证书签名请求中允许哪些加密协议?
CSR 可以定制为生成支持 椭圆曲线数字签名算法 (ECDSA) 的 X.509 证书和密钥。支持以下密钥大小和算法:
大小 | ASN1 OID | 签名算法 |
---|---|---|
256 | prime256v1 | ecdsa-with-SHA256 |
384 | secp384r1 | ecdsa-with-SHA384 |
521 | secp521r1 | ecdsa-with-SHA512 |
吊销身份
由于 Hyperledger Fabric 是一个 PKI 系统,必须从系统中删除的身份必须明确撤销。这是通过标准证书吊销列表(CRLs)完成的。必须将 CRLs 同步到所有组织中,以确保每个人都能检测到被吊销的证书。向其他对等方分发 CRLs 需要使用带外机制。
Fabric CA 中管理用户的实际考虑
通常,组织有自己的身份(LDAP)服务器来管理其员工。一个组织可以选择参与一个或多个 Hyperledger Fabric 网络,但是只有其员工的子集可能被注册到每个网络。每个网络的 Fabric CA 管理员可以选择在每个网络中注册一部分员工。
由于员工必须生成和管理私钥才能成功参与 Hyperledger Fabric 网络,因此管理私钥及其相应的数字证书的责任在于组织的员工。管理私钥和数字证书并不简单,这可能会给员工带来不必要的负担,并且可能会导致员工无意中暴露密钥。由于员工需要记住其组织发放的凭据(例如用户名和密码)以登录组织系统,组织可以选择代表参与一个或多个 Hyperledger Fabric 网络的员工管理私钥和证书。根据行业的不同,私钥可能存储在硬件安全模块中,这将使得篡改密钥变得不可行。硬件安全模块的精确配置超出了本章的范围。
链码安全性
在 Fabric 中,智能合约,也称为链码,可以用 Go 或 JavaScript 编写。链码必须安装在对等方上,然后明确启动。当启动时,每个代码都在单独的 Docker 容器中运行。以前版本的链码也在单独的 Docker 容器中运行。
运行链码的 Docker 容器可以访问虚拟网络以及整个网络堆栈。如果在将链码安装到对等方之前不仔细审查链码,并将该链码的网络访问隔离开来,则可能会导致恶意或配置错误的节点探测或连接到相同虚拟网络上附加的对等方。
运营商可以配置策略来禁用链码 Docker 容器上的所有出站或入站网络流量,除了白名单节点。
如何与其他背书对等方共享链码?
组织必须建立一个流程,与参与 Hyperledger Fabric 网络的其他组织分享链码。由于链码必须安装在所有背书对等方上,因此在与其他对等方共享时需要通过加密机制确保链码的完整性。更多关于分享链码方法的详细信息,请参阅第八章, 区块链网络中的灵活性。这个问题也在 Nettitude 对 Hyperledger Fabric 进行的安全评估中得到了强调wiki.hyperledger.org/_media/security/technical_report_linux_foundation_fabric_august_2017_v1.1.pdf
。
谁可以安装链码?
要在对等方上安装链码,实体的证书必须安装在对等方的节点上(存储在本地 MSP 中)。由于安装链码是一项权限非常高的操作,必须小心,只有具有管理能力的实体才能执行此操作。
链码加密
实体可以选择在调用链码时使用 AES 加密密钥来加密键/值对(github.com/hyperledger/fabric/tree/master/examples/chaincode/go/enccc_example
)。加密密钥传递给链码,然后在发送提案之前加密值。需要解密值的实体(例如,为了支持交易)必须拥有密钥。预期这些加密密钥将以点对点的方式与其他对等方共享。
基于属性的访问控制
正如你可能还记得第四章中所提到的 使用 Golang 设计数据和事务模型,Hyperledger 1.1 新增的功能之一是基于属性的访问控制。在注册实体时,可以为实体指定属性,然后这些属性将在注册时添加到 X.509 证书中。属性的示例包括由参与网络的组织约定的角色名称,例如“审计员”。当执行链码时,它可以在调用或查询操作之前检查身份是否具有某些属性。在简单级别上,这允许应用级别的属性通过 X.509 证书传递到链码中。
属性访问控制的优缺点
在证书中编码属性具有其自身一套优缺点。一方面,与身份相关的所有信息都编码在证书中,因此可以基于属性做出决策。另一方面,如果必须更新属性,例如,用户转移到不同部门,就必须吊销现有证书,并发行一个具有新属性集的新证书。
Hyperledger Fabric 如何应对常见威胁
Hyperledger Fabric 针对一些最常见的安全威胁提供了保护,并假定了一个共享责任模型来解决其他威胁。在下表中,我们将总结最常见的安全威胁,以及 Hyperledger Fabric 是否解决了这些威胁以及如何解决,或者这是否是节点/网络操作员的责任来解决这些威胁:
威胁 | 描述 | Hyperledger Fabric | 网络/节点操作员 |
---|---|---|---|
欺骗 | 使用令牌或其他凭证来假冒授权用户,或者 compromise 用户的私钥。 | Fabric 证书颁发机构为其成员生成 X.509 证书。 | 管理证书吊销列表的分发,以确保被吊销的成员不能再访问系统。 |
篡改 | 修改信息(例如,数据库中的条目)。 | 使用密码措施(SHA256,ECDSA)使篡改变得不可行。 | 源自 Fabric。 |
否认 | 实体无法否认谁做了什么。 | 使用数字签名跟踪谁做了什么。 | 源自 Fabric。 |
重放攻击 | 重放交易以破坏分类帐。 | Hyperledger Fabric 使用读/写集来验证交易。重放交易将由于无效的读取集而失败。 | 源自 Fabric。 |
信息泄露 | 通过有意的违规或意外曝光暴露的数据。 | Hyperledger Fabric 支持使用 TLSv1.2 进行在传输时加密。它不会加密静态数据(操作员的责任)。系统中所有对等体及其交易的信息都会暴露给排序服务。 | 通过遵循信息安全最佳实践以及静态数据加密,操作员有责任防止信息泄露。 |
拒绝服务 | 使合法用户访问系统变得困难。 | 这是操作员的责任。 | 防止系统遭受拒绝服务攻击是操作员的责任。 |
特权提升 | 获得应用程序的高级访问权限。 | 已经发行的身份无法升级其访问权限(例如,创建身份)而不经过手动审核。 | Hyperledger Fabric 在 Docker 容器中运行链代码。限制访问并使用适当的限制运行链代码容器是网络/节点操作员的责任。 |
勒索软件 | 使用加密或其他手段阻止对文件系统上数据的访问。 | 运营者的责任。 | 运营者有责任确保勒索软件无法阻止节点账本的访问。 |
Hyperledger Fabric 中的交易隐私
Hyperledger Fabric 的主要设计考虑之一是提供交易的隐私和保密性。Hyperledger Fabric 提供了一些机制来实现这些目标。
通道
Hyperledger Fabric 节点如果只想与网络中的一部分节点共享数据,则可以通过通道实现。在这些情况下,只有参与通道的同行者才能存储交易数据;不参与通道的同行者无法看到交易数据,因此也无法存储交易数据。然而,此数据暴露给了排序服务。一个健壮的通道设计将解决参与者之间的隔离、数据隐私和保密性以及具有健壮审计能力的受控/有权限访问。
私有数据
通道中的同行者可以选择确定他们将与哪些其他同行者共享他们的数据。私有交易数据在同行者之间点对点传递,而仅将交易数据的哈希广播给排序服务和未共享此数据的同行者。
加密交易数据
同行者还可以选择在发送交易进行认可之前对交易数据进行加密。然而,对于认可交易的同行者来说查看数据可能是必要的。必须使用一种带外机制在这些同行者之间交换加密密钥。
Hyperledger Fabric 和量子计算
Hyperledger Fabric 使用椭圆曲线密码学对交易进行数字签名。椭圆曲线密码学依赖于数学技术,可以通过量子计算加速。然而,Hyperledger Fabric 提供了可插拔的密码提供者,允许用其他算法替换这些数字签名算法。此外,根据 NIST 信息技术实验室主任的说法,量子计算对区块链系统安全的影响至少还有 15 到 30 年才会成为现实。
通用数据保护条例 (GDPR) 考虑因素
通用数据保护条例(GDPR)是一项欧盟法律,规定了个人数据如何被获取、处理和最终从计算系统中删除。GDPR 中对个人数据的定义非常广泛——例如姓名、电子邮件地址和 IP 地址。
区块链本质上创建了数据的不可变、永久和复制记录。基于 Hyperledger Fabric 的区块链网络显然将涵盖这三个特性。因此,在不可删除或修改的区块链网络上存储个人数据可能会从 GDPR 的角度带来挑战。同样重要的是要知道个人数据与谁共享。
Hyperledger Fabric 的通道和通道私有数据特性提供了一种确定与之共享数据的实体的机制。在通道私有数据的情况下,数据永远不会存储在区块链上,但其加密哈希存储在链上。通过治理流程,节点可以确定与之共享此数据的其他节点。Hyperledger Fabric 中的通道私有数据特性可能提供了一种将个人数据存储在链外,并确定与之共享此数据的对象的机制,同时通过存储在区块链中的加密哈希维护此数据的完整性。
Hyperledger Fabric 还会在数字账本中存储创建交易的实体的 X.509 证书。这些 X.509 证书可能包含个人数据。从版本 1.1 开始,Hyperledger Fabric 提供了一种基于零知识证明的身份证明机制,同时隐藏属性的实际值。这些基于零知识证明的凭证随后存储在账本中,取代传统的 X.509 证书,并且有助于达到 GDPR 的合规性。
摘要
在本章中,我们首先讨论了与安全相关的 Hyperledger Fabric 的设计目标。所有描述的一系列点都被认为是考虑到 Fabric 安全性。我们简要研究了 Hyperledger Fabric 安全性,并了解了强身份是 Fabric 安全的核心。我们还简要讨论了链码安全性。
Hyperledger 本身擅长处理威胁。我们深入了解了常见的 Hyperledger 安全威胁以及 Fabric 如何应对它们。
我们还简要讨论了量子计算对 Hyperledger Fabric 的影响。
第十二章:区块链技术介绍
本章节将概述区块链及其关键概念,如加密学、哈希算法、分布式账本、交易、区块、工作证明、挖矿和共识。我们详细介绍了区块链技术的鼻祖比特币。我们通过指出比特币的一些局限性以及以太坊如何解决这些问题,简要介绍了以太坊。虽然比特币和以太坊是公共区块链的示例,但 IBM 的超级账本被用作企业区块链的示例。在本章的最后,我们提到了区块链的演变:基于它们的用例,包括区块链 1.0、2.0、3.0 及以后版本。具体来说,我们将涵盖以下区块链主题:
-
区块链的家谱类比
-
比特币共识机制
-
对超级账本的简要讨论
-
区块链的演变
家谱类比
作者之一最近参加了北京的一次赛里斯大学校友聚会,在那里,区块链成为了一个热门讨论话题。一位备受尊敬的校友和学者,杨教授,他曾撰写过关于密码学和公共数据保障的书籍,用家谱来描述区块链。这是一个深思熟虑的类比,因为它直观、易于理解地解释了区块链。这里借用这个类比来说明技术背后的基本思想。
在赛里斯的古老时代,每个姓氏家族都有一个习俗,即保存该家族的族谱副本(具有相同姓氏的人)。当家族成员因婚姻、生子或收养而发生变化时,新成员的姓名会出现在每个副本中。然而,新成员必须在姓名被添加之前得到家族的接受。由于各种原因,有时候结婚未得到大多数家族的认可。在这种情况下,新成员的姓名就不会被录入家谱中。换句话说,当一个新成员加入家族时,消息会传达给其他家族。如果家族就接受新成员达成共识,每个家族都会更新他们的家谱副本以反映这一变化。另一方面,如果家族决定不接受新成员,姓名就不会被添加。家谱可以用于验证目的。例如,如果一个陌生人声称是家族的成员,或者两个拥有相同姓氏的人想知道他们是否有共同的祖先,有了家谱,就很容易验证。结果会被接受,因为家谱被认为是可靠的,这要归功于上述共识和分散记录,除非大多数家族同意,否则很难操纵。
区块链与家谱有许多相似之处,总结如下:
-
就像一个由许多相关家庭组成的家族一样,区块链网络由节点组成。每个节点就像一个家庭。
-
就像每个家庭都保存一份家谱副本一样,区块链的每个节点都保留着从一开始就发生在链上的所有交易的副本。所有交易的集合构成了一个分类帐。这使得区块链成为了一个分散的数据存储库。
-
一个家谱以一个家族的共同祖先开始,名字与直系亲属之间的关系,如父母和子女,通过一条线连接起来。同样,分类帐由块组成。每个块包含一个或多个交易,取决于区块链的类型。(正如您将在后面看到的,比特币或以太坊上的区块包含多个交易,而 R3 的 Corda 使用只有一个交易的块)。交易就像名字,而块类似于包含夫妇名字的看不见的盒子。根祖先的等价物称为创世块,它是区块链的第一个块。类似于连接父母和子女的线,一个哈希,稍后将更详细地解释,从当前块指向其祖先块。
-
就像为将新的名字添加到家谱的共识机制一样,比特币区块链使用一种称为工作证明的机制来决定是否可以将一个块添加到链上。就像家谱一样,在一个块被添加到链上之后,除非拥有网络大多数(称为 51%攻击)的计算能力,否则很难改变(入侵)。
-
家谱提供了家族历史的透明度。同样,区块链允许用户查询整个分类帐或分类帐的一部分,并了解硬币的流动情况。
-
由于每个家庭都保存了家谱的副本,即使由于自然灾害、战争或其他原因导致许多副本丢失,也不太可能丢失家谱。只要至少有一个家庭幸存下来,家谱就会幸存下来。同样,只要至少有一个节点幸存下来,分散式分类帐就会幸存下来。
虽然家谱是解释区块链一些关键概念的一个好比喻,但它们并不相同。不可避免地,它们之间存在着不共享的特征。例如,区块链广泛使用密码学和哈希来保护数据和阻止黑客。家谱没有这种需求。因此,接下来我们将摆脱家谱的比喻,按照时间顺序解释关键的区块链概念。
比特币
区块链技术最初引起人们的关注是因为比特币区块链,由中本聪在 2008 年 10 月发表在 metzdowd.com 的密码学邮件列表上的一篇白皮书中概述的一个想法。它描述了比特币数字货币(BTC),标题为比特币:一个点对点的电子现金系统。2009 年 1 月,中本聪发布了第一个比特币软件,这启动了比特币网络和比特币加密货币的第一个单位:BTC 币。
为什么比特币
比特币的诞生是在 2008 年金融危机之后,这是自大萧条以来最严重的经济危机。这不是巧合。比特币加密货币的发明者旨在解决人们对金融机构的幻灭,其在风险控制方面的史诗般失败导致了 2008 年的金融危机。
金融机构所扮演的一个基本角色是成为一个中间实体,将不信任的各方聚集在一起促成交易。例如,零售银行吸引个人的剩余资金并借给需要资金的个人或公司。支付给资金供应方和借款方的利息之差是银行提供中介服务的费用。金融机构非常成功地提供这些服务,并在全球经济中发挥着关键作用。然而,这种商业模式存在许多缺陷。以下是一些例子:
-
慢:完成一笔金融交易通常需要数天的时间。例如,完成并结算跨境汇款需要三天时间(在初始输入订单后)。为了使其发生,一个机构内和机构间的多个部门和应用系统必须共同努力促成交易。另一个例子是股票交易。投资者雇佣经纪人输入订单以路由到证券交易所。在这里,经纪人可以是交易所的成员,也可以将订单路由到另一个具有会员资格的中介机构。在交易所的买卖双方找到匹配后,交易细节由两方记录并分别发送到其后台办公室。后台团队与清算所进行清理和结算。对于交易双方来说,完成证券(股票)所有权和现金交换的行动需时 T + 3。
-
昂贵:金融中介在提供这些服务时通常收取高昂的费用。例如,美国银行可能会收取 10 到 30 美元的费用,以向其他国家的接收者发送资金。在股票交易中,全服务经纪人通常会收取数十美元或更多的费用。即使是折扣经纪人,投资者每笔交易也需要支付 7 到 10 美元。
-
易受黑客攻击:由于客户和交易细节保存在机构内的集中区域,因此容易受到黑客攻击,造成严重的财务损失或泄露客户的机密个人信息。最近,一些知名公司如 JP 摩根(2014 年有 8300 万账户遭黑客攻击)、Target(2013 年有高达 7000 万客户信息遭黑客攻击)、以及 Equifax(2017 年有 1.48 亿美国消费者信息遭黑客攻击),都发生了高调的个人数据泄露事件。
-
不透明:金融机构保存有关交易的详细和汇总信息。然而,大部分信息对个体客户不公开,导致信息不对等。以跨境汇款为例,发送方和接收方都要等待三天才能知道交易是否成功完成。如果交易失败,就必须进行漫长的调查。想象一下如果接收方处于紧急情况,需要立即获得资金。尽管客户必须支付高额费用,这样的服务仍是不尽人意的。
利用区块链技术,上述问题得到了优雅的解决。在比特币区块链的情况下,要转移的基础资产是数字硬币 BTC。跨境 BTC 交易最多只需 1 小时即可完成。不需要结算,因为交易和结算在一个动作中完成。该交易的成本仅是通过银行转账的一小部分。例如,美国银行(BoA)最近发布的一份报告称,通过区块链转账的成本是 BoA 收费的 1/6000。然而,对一些客户来说,等待一个小时仍然太长。瑞波,一个全球汇款提供者,可在不到 1 分钟内完成。
词汇“比特币”经常引起混淆,因为人们将该词用于三种事物:加密货币、区块链和协议。为避免混淆,我们使用 BTC 指代加密货币,使用比特币指代区块链和使用分布式账本的相应网络。对于协议,我们将完全拼写比特币协议或简单称为协议。
点对点网络
要解释比特币的工作原理,让我们看看用于完成跨境交易的现有业务模型涉及哪些步骤:
-
客户通过访问银行分行或通过网络输入订单。发送者提供订单的详细信息,如金额、发送货币、接收者姓名、接收货币、接收者银行名称、账号和分行号、以及一个 SWIFT 号码。这里,SWIFT 代表 全球银行间金融电信协会,一个金融机构用来通过一套标准化的代码系统安全传输信息和指令的消息网络。SWIFT 为每个金融组织分配一个称为 银行识别代码(BIC)、SWIFT 代码、SWIFT ID 或 ISO 9362 代码的唯一代码。
-
发送银行接受订单并验证发送者是否有足够的资金可用。
-
银行收取费用并通过外汇交易将剩余金额从发送货币转换为接收货币的金额。
-
发送银行向 SWIFT 输入一条转账信息,包含所有必要信息。
-
接收银行在收到消息后验证接收者的账户信息。
-
在成功验证并根据协议在发送和接收银行之间结算资金后,接收银行将金额记入接收者账户。
由于涉及多个步骤、实体和系统,上述活动需要数天才能完成。
比特币网络连接全球各地的计算机。每台计算机都是一个具有平等地位的 节点,除了一部分被称为 矿工 的节点群,他们选择扮演验证交易、构建区块和连接链的角色。在比特币中,完成货币转账的商业模式涉及以下步骤:
-
发送者通过一个 电子钱包 输入比特币数量、待转出比特币的地址和待转入比特币的地址。
-
交易请求通过电子钱包发送到比特币网络。
-
在矿工成功验证交易并将其提交到网络后,比特币现已可供接收者使用。
比特币转账更快(1 小时内,若使用 Ripple,可能更快),原因如下:
-
交易和结算一步到位。这避免了需要经历耗时且昂贵的对账过程。
-
由于 BTC 跨国无界限,不需要外汇交易。它可以在全球自由快速地移动。
-
由于交易不需要中间银行,银行之间不需要资金结算。
在发送方或接收方希望使用美元(USD)、英镑(GBP)、人民币(CNY)或日元(JPY)等法定货币的情况下,可以使用加密货币市场进行比特币与法定货币之间的转换。一个名为 CoinMarketCap 的网站列出了这些市场:coinmarketcap.com/rankings/exchanges/
。截至 2018 年 9 月 21 日,有 14,044 个市场。就市值而言,前三名分别是币安(www.binance.com/
)、OKEx(www.binance.com/
)和火币(www.huobi.pro
)。
对等网络可以连接全球节点。然而,仅有物理连接并不足以使两个不信任的交易方进行交易。为了允许他们交易,比特币采取了以下措施:
-
每个节点保存了分散账本中所有交易的完整副本。这使得对链上交易的任何更改都变得不可行。
-
账本交易被分组到区块中。非创世区块通过保存所有先前区块交易的哈希与其前一个区块相链接。因此,改变一个交易需要更改当前的交易块和所有后续块。这使得黑客攻击分散式账本变得极其困难。
-
比特币通过使用工作量证明共识算法解决了双重支付问题,即同一比特币被两次花费的问题。
-
哈希广泛用于保护各方的身份,并检测出块中发生的任何更改。
-
公钥/私钥和地址用于掩盖交易各方的身份,并对交易进行数字签名。
凭借这些措施,不信任的交易方因以下原因感到舒适进行交易:
-
交易是不可变的和永久的。任何一方都无法单方面使交易失效。
-
不可能发生双重支付。
-
交易和结算同时发生;因此,不存在结算风险。
-
身份得到保护。
-
交易由双方签署,这将避免任何未来的法律纠纷。
密码学和哈希函数
密码学或密码学是研究在对手存在的情况下保护通信技术的技术。在旧时代,密码学与加密同义。现代密码学严重依赖于数学理论和计算机科学。它还利用了其他学科的作品,如电气工程、通信科学和物理学。
密码算法是基于这样的假设设计的,即在可预见的计算硬件进步下,任何对手都不太可能根据这些算法解密加密的消息。换句话说,理论上可以解码加密的消息,但在实践中不可行。因此,这些算法被定义为计算安全的。理论研究(例如并行或整数分解算法)和计算技术的进步(例如量子计算机)可能使这些算法在实践上不安全,因此加密算法需要不断地进行调整。
加密是将明文转换为不可理解的文本,称为密文。解密是反向操作,换句话说是从不可理解的密文转换回明文。
比特币挖掘使用的加密算法是哈希函数。哈希函数是将任意大小的数据映射到固定大小数据的函数。哈希函数返回的值称为哈希值或简称哈希。密码哈希函数允许轻松地验证一些输入数据是否映射到给定的哈希值。然而,反过来—当输入数据是未知的—从哈希值重建输入明文在实践上是不可行的。换句话说,哈希是一个单向操作。哈希函数的另一个显著属性是,输入明文的微小变化将导致完全不同的哈希值。这个特性对于保护信息是可取的,因为黑客对原始数据的任何微小改变都会导致明显不同的哈希。
两种常见的哈希算法是 MD5(消息摘要算法 5)和 SHA-1(安全哈希算法):
-
由罗纳德·里维斯特在 1991 年开发,MD5 将输入明文映射为一个 128 位的哈希值。MD5 消息摘要校验和通常用于在数字文件传输或存储时验证数据完整性。已发现 MD5 存在广泛的漏洞。
-
SHA-1 是一种密码哈希函数,将输入的明文映射为一个 160 位(20 字节)的哈希值,也称为消息摘要,通常以 40 位十六进制数显示。SHA-1 由美国国家安全局设计,并且是美国联邦信息处理标准之一。
SHA-256 是 SHA-1 的继任哈希函数。它是目前最强大的哈希函数之一,迄今为止尚未以任何方式被破解。SHA-256 为文本生成几乎唯一的 256 位(32 字节)签名。例如,我的测试字符串 映射为 5358c37942b0126084bb16f7d602788d00416e01bc3fd0132f4458d
d355d8e76. 经过微小改动,*我的测试字符串* 的哈希值为
98ff9f0555435`
f792339d6b7bf5fbcca82f1a83fde2bb76f6aa95d66050887cc*
,完全不同的值。SHA-256 可以生成 2²⁵⁶ 种可能的哈希值。迄今为止,尚未出现两个不同的输入产生相同的 SHA-256 哈希的情况,这在密码学中称为碰撞问题。即使使用最快的超级计算机,也需要比宇宙的年龄更长的时间才能发生碰撞。因此,SHA-256 被比特币用于加密。
分布式账本、区块、交易、地址和 UTXO
在金融机构,账本是记录财务交易的账簿。类似地,比特币通过地址维护账本,记录比特币交易和余额。一个关键区别是,银行的账本是集中式的,而比特币的账本是分散式的。因此,银行的账本更容易被篡改。另一方面,比特币的账本非常难以篡改,因为必须在全球所有节点上更改账本。
用户提交包含以下信息的交易:
-
要转移的比特币的来源
-
要转移的比特币数量
-
应转移比特币的目标
根据维基网站,交易的一般结构如下所示:
源地址和目标地址都是 64 字符的哈希值。以下是一个地址的示例:979e6b063b436438105895939f4ff13d068428d2f71312cf5594c132905bfxy1
。
术语地址有点令人困惑。程序员可能认为它是与磁盘或内存位置相关的地址。然而,它与物理位置无关。相反,它是一个逻辑标签,用于将已转移的比特币分组。在某种程度上,可以将其视为银行账户号码,但它们之间存在根本性差异。例如,银行有一个集中的地方,用于保存有关账户的元数据,例如,所有者姓名、账户开立日期和账户类型。此外,账户余额是预先计算并保存的。在比特币中,地址上没有元数据,必须查询整个账本以找到地址的余额,通过计算进出地址的净比特币数量。地址仅在比特币交易中引用。当地址余额降至 0 时,任何从该地址取出比特币的未来请求将由于资金不足而失败交易验证。
比特币利用UTXO模型管理其比特币转账。这个术语是由加密货币引入的,指的是未花费的交易输出。这是一个未被花费且可以作为未来交易输入的区块链交易输出。在比特币交易中,只有未花费的输出可以作为输入,这有助于防止双重花费和欺诈。因此,承诺的交易导致在区块链上删除输入并创建 UTXO 形式的输出。新创建的未花费交易输出可以由持有相应私钥的所有者花费。换句话说,UTXO 持续处理,承诺的交易导致删除已花费的硬币并在 UTXO 数据库中创建新的未花费硬币。
像地址一样,比特币不与任何实物对象(如数字代币文件或实际铸造的硬币)相关联。相反,它只存在于分布式分类账中的交易中。例如,如果一个人想知道迄今为止铸造的比特币总数,就必须查看区块链上所有非零余额地址并加总所有比特币。由于比特币的每个节点都保存着分类账的副本,因此只需要计算时间就可以找到答案。
当用户在节点输入比特币交易请求时,节点上安装的比特币软件将该交易广播给所有节点。网络中的节点将通过检索包含输入地址的所有历史交易并确保这些地址中的比特币是合法且足够的来验证交易的有效性。之后,矿工节点开始通过收集经过验证的交易来构建一个区块。通常,比特币区块包含 1,500 至 2,000 笔交易。赢得解决难题竞赛的矿工将承担构建并链接一个新区块到区块链的角色。在比特币区块链上,大约每 10 分钟创建一个新区块。截至 2018 年 9 月 21 日,在比特币上已经创建了大约 542,290 个区块。比特币区块的结构如下所示:
在这里,区块头包含以下字段:
随机数的概念将在挖矿子章节中进行解释。hashPrevBlock
与hashMerkleRoot
的值相同。Merkle 树哈希根本上是区块中所有交易哈希的哈希,通过二叉树聚合结构进行。以下图解释了这个想法:
共识机制
如果有人用 1 美元购买一瓶水,那个人就不能再用相同的 1 美元购买一罐可乐。如果一个人可以自由地二次花费一美元,货币将变得毫无价值,因为每个人都将拥有无限数量的货币,而赋予货币价值的稀缺性将消失。这就是所谓的双重支付问题。对于比特币而言,双重支付是同一比特币被使用多次的行为。如果这个问题没有解决,比特币就会失去稀缺性,不能用来促进两个不信任方之间的交易。比特币核心网络通过共识机制防止双重支付。要解释比特币共识机制是如何运作的,我们首先介绍PoW(工作证明)和挖矿的概念。
正如前面所解释的,为了获得成为当前新区块的建造者并获得做这项工作的奖励,矿工需要比其他矿工更早地解决一个困难的数学难题。解决数学难题的工作称为PoW。
为什么需要 PoW?想象一下:在一个由相互不信任的参与者组成的网络中,为了使网络正常运作,需要比不诚实的攻击者更多的诚实参与者。想象一下,当一个矿工收集到足够的交易来构建一个新的区块时,他被允许立即构建新的区块。这简直就是一个谁能迅速组合足够的交易的比赛。这为恶意攻击者留下了大门,可以通过包含无效或假交易来攻击网络,并总是赢得这场比赛。这将允许黑客自由双重支付比特币。
因此,为了防止攻击者引入恶意交易,参与节点需要足够的时间窗口来验证每笔交易的有效性,确保比特币尚未被花费。由于每个节点都维护着账本的副本,一个诚实的矿工可以追踪历史,确保以下内容以确认交易的有效性:
-
交易请求者拥有这些比特币。
-
相同的比特币在账本中没有被任何其他交易所花费过。
-
相同的比特币在候选区块内没有被其他交易所花费过。
目前,这段时间窗口大约设置为 10 分钟。为了强制执行 10 分钟的等待时间,比特币要求矿工解决一个足够困难的数学难题。这个难题只需要进行简单的计算。矿工需要重复进行相同的计算多次,以烧掉足够的 CPU 时间,达到网络每 10 分钟平均建立一个新区块的目标。重复猜测的过程称为挖矿,而设备(专门制造的)称为矿机。
由于为了赢得挖矿竞赛,矿工需要大量投资于硬件,这些矿工专注于挖矿工作,并旨在获得足够的 BTC 来支付挖矿运营成本并获利。截至 2018 年上半年,给予获胜矿工的奖励为 12.5 BTC。可以通过访问 CoinMarketCap 网站(coinmarketcap.com/
)找到 BTC 的价格。截至 2018 年 9 月 21 日,一个 BTC 大约以 6,710 美元的价格交易。因此,12.5 BTC 约价值 83,875 美元。
按照比特币协议,挖矿是发行新 BTC 的唯一方式。慷慨地奖励矿工有三个目的:
-
补偿矿工在硬件上的投资。
-
包括支付挖矿运营成本,例如水电费用,这可能是由于在矿场部署了大型挖矿设备而显著的,人工工资和场地租金。
-
给矿工激励,以防止网络遭受恶意黑客攻击。矿工有动力维护比特币网络,以免自己的 BTC 和挖矿基础设施价值损失。如果比特币被黑客攻击,比特币的声誉将受到严重损害,BTC 价格将暴跌。这正是比特币发明者所希望的:有更多的好矿工而不是坏矿工来解决双花问题。
可以发行的 BTC 总数被固定为 2100 万。截至今天(2018 年 9 月 19 日),大约已发行了 1700 万 BTC。比特币协议规定了动态调整支付速率的规则,剩下的 400 万枚硬币预计还需要另外 122 年才能完全挖掘出来。以下是区块创建支付速率如何动态调整的说明:
- 每 210,000 个区块更改一次速率。它是基于链上的块高度函数,创世=0,并使用 64 位整数运算来计算,例如:(50 * 100000000)>>(高度/210000)。速率最初从 50 BTC 开始,到第 210,000 个区块时下降到 25 BTC。到第 420,000 个区块时下降到 12.5 BTC,当网络达到 6,930,000 个区块时最终将降至 0。
分叉
比特币区块链可以分为两条潜在的路径,因为矿工不一定以相同的方式或同时收集交易和合约区块候选。其他原因,如黑客攻击或软件升级,也可能导致路径分歧。这些分裂的修补程序称为分叉。有临时分叉和永久分叉。
如果由于例如恶意攻击导致永久分叉,则会发生硬分叉。类似地,还有软分叉的概念。硬分叉和软分叉都指对协议的根本性更改。硬分叉使先前无效的区块/交易变为有效,而软分叉使先前有效的区块/交易变为无效。
为了消除临时分叉,比特币协议规定应选择最长链。换句话说,当面对两条路径时,胜出的矿工会选择更长的链来连接新区块。因此,更长的路径会继续增长,而输掉的(更短的)路径上的区块会变成孤立块。比特币节点将会很快丢弃或不接受这些孤立块。它们只保存最长链上的区块作为有效区块。
在永久性分叉的情况下,网络节点必须选择跟随哪个链。例如,比特币现金由于比特币社区就如何处理可伸缩性问题存在分歧而产生分叉。结果,比特币现金变成了自己的链,并与创世区块直至分叉点的交易历史。截至 9 月 21 日,比特币现金的市值约为 80 亿美元,排名第四,而比特币为 2150 亿美元。
挖矿和难度级别
还有一个需要解决的问题:如何保持每 10 分钟一个新区块的速率。如果不做任何处理,由于以下因素,挖矿速率将会产生变化:
-
网络矿工数量会随着 BTC 价格的波动而变化。
-
技术进步使矿机逐渐快速起来
-
挖矿机的总数量不断变化
比特币通过调整数学难题的难度级别来保持每 10 分钟一个新区块的速率。难度级别是根据最近添加的区块的速率计算出来的。如果新的区块添加速率的平均值低于 10 分钟,难度级别将会增加。如果平均速率超过 10 分钟,难度级别将会降低。难度级别每 2,016 个区块更新一次。以下图表显示了比特币难度级别的历史趋势。
我们还没讨论实际的挖矿算法。假设当前的难度级别是找到第一个哈希值的开头字符为 0。在比特币中,解决难题的过程,也就是挖矿,需要矿工按照以下步骤进行:
-
首先,在进行中的区块中找到 SHA-256 哈希值。
-
如果得到的哈希值的开头是 0,那么矿工解决了难题。矿工将该区块连接到节点上的账本并获取奖励,12.5 个比特币。矿工的节点向所有节点广播这个消息。网络上的所有其他节点和矿工验证这个答案(通过将区块信息加上随机数映射得到相同的哈希值),并验证账本的整个历史,确保区块包含有效交易。
-
如果通过检查,所有网络节点都会将该区块加入到其账本的拷贝中。矿工们开始着手下一个新区块的工作。
-
如果获胜的矿工是恶意攻击者并在区块中包含不良交易,则这些交易的验证将失败,其他矿工将不会在他们的分类帐副本中包含该块。他们将继续在当前块上进行挖矿。随着时间的推移,包含不良块的路径将不再是最长的路径,因此不良块将成为孤立块。这基本上是网络中所有节点如何达成共识,只向网络添加良好的块,防止不良块潜入,从而解决双重支付问题。
-
如果生成的哈希不以 0 开头,那么允许矿工在输入文本中附加一个已知为随机数的序列号,从 0 到输入文本再试一次哈希。
-
如果生成的哈希仍不包含前导零,矿工将对输入文本添加另一个序列号 1,获得一个新的哈希。矿工会通过这种方式不断尝试,直到找到第一个具有前导零的哈希。
以下是明文和随机数(nonce)如何共同工作的示例。原始明文是输入字符串 ,随机数变化范围为 0 到 1:
-
输入字符串:
f23f4781d6814ebe349c6b230c1f700714f4f70f735022bd4b1fb69421859993
-
输入字符串 0:
5db70bb3ae36e5b87415c1c9399100bc60f2068a2b0ec04536e92ad2598b6bbb
-
输入字符串 1:
5d0a0f2c69b88343ba44d64168b350ef62ce4e0da73044557bff451fd5df6e96
在比特币中,难度调整 很大程度上指的是改变所需的前导零位数。(实际调整涉及到其他矿工调整到该要求。)每增加一个前导零位数,都将显著增加尝试的平均次数,因此会增加计算时间。这是比特币如何维持平均每 10 分钟添加一个新区块的速率。当前比特币的难度级别是 18 个前导零。
黑客行为 - 51%问题
由于 BTC 价格上涨,挖矿操作变得更具吸引力。投资者纷纷涌入,成千上万的矿池加入网络,以在竞赛中率先解决难题并获取奖励优势。对于没有大型资本投资的玩家,他们可以选择参与矿池。当矿池赢得一场竞赛时,奖励将根据贡献的计算能力分配给每个参与者。
矿池的这种不断增长的计算能力构成了所谓的51%问题的真正威胁。当一个矿工成功地将计算能力积累到至少等于网络总计算能力的 51%时,就会出现这个问题。这时,矿工将有机会领先其他矿工。由于这个矿工有超过 50%的机会首先解决谜题,所以矿工可以继续增加包含不良交易的区块来扩展账本。很快,恶意矿工的账本将变得最长,并且所有其他节点都必须根据比特币的共识协议保存此路径。
对于比特币这样的大型和成熟的网络,51%问题并不是一个关键问题,主要原因如下:
-
一个建立良好的网络将吸引更多的参与方,并连接非常多的节点。对于一个已经成熟的网络,黑客购买所需的挖矿设备需要巨额的初始投资。当这样的网络受到攻击时,当消息公开后,加密货币的价格会迅速下跌,黑客将很难收回投资。
-
在比特币的历史上,曾经有矿池积累了危险的高计算能力接近这条线的情况。当参与的矿工意识到问题时,许多人选择离开该矿池。很快,矿池的计算能力降到了一个安全水平。
-
对于一个小而不成熟的网络,矿工很容易集结超过 51%的计算能力。然而,这些网络的加密货币价值微不足道,对黑客来说几乎没有任何财务激励来利用 51%的问题。
私钥和比特币钱包
正如前面讨论的那样,比特币并不存在于物理上。它们存在的唯一证据是当它们与地址关联时,在交易中被提及。当地址被初始创建时,会生成一对公钥和私钥。公钥对公众公开,而私钥仅由地址所有者保留。当所有者想要花费他们的 BTC 时,所有者使用私钥签署数字签名,并将 BTC 请求发送到比特币网络。换句话说,想要花费 BTC,必须同时知道地址和它的私钥。
如果所有者丢失私钥,与之相关的 BTC 将永久丢失。因此,建议将此信息存放在安全的地方。通常最好将地址和私钥分开存放。为了防止数字副本丢失,所有者应保留纸质打印的物理副本。为了使转换更容易,所有者可以打印一个 QR 码,以后在需要时扫描该 QR 码。
比特币钱包应用程序可用于帮助用户管理密钥和地址。可以使用钱包执行以下操作:
-
生成地址和相应的公/私钥
-
保存和组织 BTC 的信息
-
向比特币网络发送交易请求
在比特币中,私钥是一个 256 位长的哈希,公钥是 512 位长。它们可以转换为十六进制表示中的较短长度。以下截图提供了一个公/私钥对和一个地址的示例:
比特币私钥也可以用由 5 和一个字符 51 开始的字符串表示,公钥可以用由 72 个字符的字符串表示。一个示例私钥是 `5Jd54v5mVLvy
RsjDGTFbTZFGvwLosYKayRosbLYMxZFBLfEpXnp 和一个示例公钥是 BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68
。
比特币脚本
一个人可以为编程比特币操作安装以下开发工具:
-
NodeJS:这是一个开源的、跨平台的 JavaScript 运行时环境,用于在浏览器之外执行 JavaScript 代码。它允许程序员快速、轻松地编写和执行脚本。这些脚本可以编写为在 Web 浏览器或服务器上运行。
-
BitcoinJS:这是一个用于处理比特币及其密码功能的 JavaScript 库。BitcoinJS 可用于生成公钥/私钥和地址。
-
Blockchain.info:这是一个公共 API,可用于查询区块链以查找余额并将交易广播到网络。它可用于实现比特币节点并安装和运行比特币节点。
安装完上述工具后,可以执行以下操作:
-
生成一个新的私钥并计算一个公钥
-
检查某个地址的余额
-
生成地址
-
构建一个新的交易
-
发送一个交易,涉及三个步骤:
-
构建一个带有输入和输出列表的交易
-
使用必要的私钥对交易进行签名
-
将交易广播到网络
-
-
建立一个托管账户
-
广播交易
替代币
感谢比特币,区块链技术已经引起了全球的关注。就像任何新技术一样,它也有其局限性。许多比特币的变种被创建来解决比特币的特定限制。在这里,我们提到其中一些:
-
比特币现金:这是比特币链的硬分叉,是因为一群比特币核心开发者想要使用不同的方法来解决可扩展性问题而创建的。
-
莱特币:这几乎与比特币相同,只是添加新区块的时间从 10 分钟减少到 2 分钟。
-
Zcash:这基于比特币,但提供了完全的付款保密性。
-
门罗币和 Zcash:这两种替代币通过使交易历史不可追踪来解决隐私问题,但它们实现了两种不同的解决方案。
-
Dash:主要改进了用户友好性。例如,交易变得无法追踪,用户不必等待添加了几个额外新区块后才考虑将交易提交到链上。
-
Namecoin:这扩展了比特币的用例,后者仅用于交易 BTC,提供域名服务。
-
Peercoin:这个山寨币解决了 PoW 的缺陷,后者环境不友好且吞吐量低。相反,它采用权益证明来实现共识。根据这一规则,矿工根据持有的币数验证区块交易。换句话说,矿工的挖矿能力与持有的 Peercoin 数量成比例。
-
Primecoin:Primecoin 矿工竞相寻找下一个最大质数。
以太坊
尽管上述山寨币采取了一些措施来解决比特币的一部分限制,但仍然存在一些尚未解决的基本问题:
-
比特币和这些山寨币都特定于一个目的:交易 BTC 或山寨币。
-
尽管程序员可以使用诸如 BitcoinJS 之类的工具与网络交互,但结果代码位于区块链之外,并不能保证运行。链本身没有用于直接在区块链上编码的图灵完备编程语言。
-
这些区块链是无状态的,人们必须在整个账本中搜索才能找到答案,例如已铸造的 BTC 总数。
针对这些问题,加拿大加密货币研究员和程序员 Vitalik Buterin 在 2013 年底提出了以太坊的想法。通过在线众筹资助,该系统于 2015 年 7 月 30 日上线,为众筹预挖了 1190 万枚币。
以太坊的核心思想是构建一个通用的区块链,以便用户可以解决各种业务问题,不仅限于加密货币转移。以太坊引入了一些新的关键概念:
-
在区块链上保存智能合约的概念
-
使用 Solidity 等图灵完备编程语言实现智能合约的概念,并在区块链上运行代码片段
Solidity 最初由 Gavin Wood 在 2014 年 8 月提出。以后,由 Christian Reitwiessner 领导的以太坊项目 Solidity 团队开发了这种语言。它是针对以太坊虚拟机(EVM)设计的五种语言之一(Solidity、Serpent、LLL、Vyper 和 Mutan)。
程序员和律师 Nick Szabo 在 1996 年首次提出了术语智能合约。在他的博客中,Nick Szabo 将其描述为所有智能合约的始祖,即自动售货机。
今天的区块链上的智能合约与自动售货机具有完全相同的属性。自动售货机是根据硬编码的规则构建的,这些规则定义了在满足某些条件时执行的操作,例如:
-
如果苏珊在自动售货机中放入一张美元钞票,那么她将收到一袋椒盐脆饼。
-
如果汤姆放进一张五美元的钞票,他将收到一袋椒盐脆饼并且会得到四美元的零钱。
换句话说,规则由自动售货机物理上定义并执行。同样,智能合约包含在区块链上运行的程序代码中的规则,当满足某些条件时触发。
智能合约概念的引入具有重要意义:
-
智能合约是一种脚本化的法律文件。
-
写入合同的代码存储在以太坊区块链上,不能被篡改或删除。这极大地增加了法律文件的可信度。
-
这段代码无法被停止,意味着任何一方——不管这个方有多么强大——都无法命令或干预智能合约代码的运行。只要满足一定条件,代码就会运行,法律上定义的行动也将被完成。
-
以太坊对于区块链就像操作系统对于计算机一样。换句话说,这个平台是通用的,不再只服务于特定的目的。
-
现在它拥有了一种图灵完备的语言:Solidity。
企业区块链 - Hyperledger
以太坊的到来彻底改变了区块链技术。将技术应用于超越金融行业的商业问题已经成为可能。然而,以太坊并不足够的场景还有很多。以太坊的问题包括以下内容:
-
真正的企业应用程序,特别是在金融行业,需要高吞吐量,这意味着每天可能有数十亿的交易。当前形式的以太坊每天的最大容量为 140 万笔。比特币甚至更糟糕:每天 30 万笔交易。在压力测试期间,比特币现金达到了 220 万笔。正在开发中的以太坊 2.0 目标是达到每天 10 亿笔的交易量,同时保持去中心化和安全的公共区块链。
-
许多金融市场,比如场外衍生品或外汇交易,都是基于许可的。以太坊或比特币支持的公共区块链不符合这种需求。
为了满足他们的需要,各行各业的知名公司成立联盟,共同开展基于许可的企业区块链项目。换句话说,节点必须在加入区块链网络之前获得批准。企业区块链的例子有 Hyperledger 和 R3 的 Corda。
2015 年 12 月,Linux Foundation(LF)宣布了 Hyperledger 项目的创建。其目标是通过开发区块链和分布式分类账来推动跨行业合作。2017 年 7 月 12 日,该项目宣布推出了可供使用的Hyperledger Fabric(HF)1.0。
目前,Hyperledger 包括五个区块链框架:
-
Hyperledger Fabric(HF):一个许可区块链,最初由 IBM 和 Digital Asset 贡献,旨在成为以模块化架构开发应用程序或解决方案的基础。它采用插件组件提供功能,如共识和成员服务。与以太坊类似,HF 可以托管和执行智能合约,称为链代码。HF 网络由对等节点组成,这些节点执行智能合约(链代码),查询分类账数据,验证交易,并与应用程序交互。用户输入的交易被通道化到一个命名为排序服务组件的服务,该服务最初用作 HF 的共识机制。称为排序节点的特殊节点验证交易,确保区块链的一致性,并将已验证的交易发送到网络的对等节点以及实施为证书颁发机构的成员服务提供商(MSP)服务。
-
Hyperledger Iroha:基于 HF,专为移动应用设计。Iroha 由 Soramitsu、日立、NTT 数据和 Colu 贡献。它采用了现代化和领域驱动的 C++设计。它实现了一种名为 Sumeragi 的共识算法。
-
Hyperledger Burrow:最初由 Monax 和 Intel 贡献,Burrow 是一个模块化的区块链,是按照 EVM 规范构建的客户端。
-
Hyperledger Sawtooth:由 Intel 贡献,实现了一种名为Proof of Elapsed Time(PoET)的共识算法。PoET 旨在尽可能高效地实现分布式共识。Sawtooth 支持许可和非许可网络。Sawtooth 设计灵活多变。
-
Hyperledger Indy:最初由 Sovrin 基金会贡献,旨在支持分布式分类账上的独立身份。Indy 提供工具、库和可重用组件,这些组件旨在提供数字身份。
该倡议的早期成员包括以下几个:
-
区块链独立软件供应商(Blockchain、ConsenSys、Digital Asset、R3、Onchain)
-
技术平台公司,如思科、富士通、日立、IBM、英特尔、NEC、NTT DATA、红帽和 VMware
-
金融机构,如荷兰银行、澳新银行、纽约梅隆银行、CLS 集团、芝加哥商品交易所集团、美国存管与清算公司(DTCC)、德国证券交易所集团、摩根大通、美国富国银行
-
软件公司,如 SAP
-
学术机构,如剑桥替代金融中心、哥伦比亚区块链和加州大学洛杉矶分校区块链实验室
-
系统集成商及其他公司,如安永、Calastone、Wipro、Credits、Guardtime、IntellectEU、Nxt Foundation 和 Symbiont
区块链的演变
区块链技术仍处于早期阶段。在它成熟并充分发挥潜力之前,还需要很多年的时间。目前,还没有普遍认可的方式来分类或定义区块链的世代。
在她关于区块链的书中,Melanie Swan 基于区块链平台创建的使用场景将区块链 1.0 到 3.0 进行了定义。
"区块链 1.0 是货币,部署加密货币在与现金相关的应用中,比如货币转移、汇款和数字支付系统。
区块链 2.0 是合同,使用区块链进行更昂贵的经济、市场和金融应用的完整 slate:股票、债券、期货、贷款、抵押贷款、所有权、智能财产和智能合同。
区块链 3.0 是超越货币、金融和市场的区块链应用,特别是在政府、健康、科学、文化和艺术领域。
有些人将区块链发展划分为从区块链 1.0 到 4.0 的四代:
-
区块链 1.0:以比特币为此领域最突出的例子,在此段落中,使用案例基于分布式分类技术(DLT),其中可以执行金融交易。加密货币被用作互联网上的现金。
-
区块链 2.0:以太坊是此领域最突出的例子,其中新的关键概念是智能合约,这些合约存储并在区块链上执行。
-
区块链 3.0:关键词是 DApps,即去中心化应用的缩写,它们避免了集中式基础设施。它们使用去中心化存储和去中心化通信。与仅涉及后端或服务器端代码的智能合同不同,DApp 可以具有前端代码和用户界面,即客户端代码,用于与其在区块链上的后端代码进行交互。与智能合同代码一样,DApp 的前端可以存储和在以太坊的 Swam 上执行。总之,DApp 是前端加上在以太坊上运行的合同。
-
区块链 4.0:此领域的区块链平台旨在为工业 4.0 提供服务。简单来说,工业 4.0 指的是自动化、企业资源规划和不同执行系统的集成。
无论区块链技术如何划分版本,可以肯定的是技术的增长远未结束。新的想法和实施将被纳入现有平台,以应对解决现实问题的挑战。换句话说,区块链技术将变得灵活,并且能够自我调整,成为解决商业问题的推动者。
摘要
区块链是一种新兴技术。由于其不可变性、透明性、避免双重支付的共识机制以及其他巧妙的设计,比如将块与前一个块的哈希链接在一起,这项技术使不信任的各方能够相互交易。在本章中,我们解释了其重要特性的基本概念。大部分讨论都集中在比特币上,它是这项技术的鼻祖。我们简要讨论了以太坊,它扩展了比特币并引入了智能合约的概念。智能合约的引入使以太坊区块链变得通用,使我们能够开发超越比特币被发明的无国界现金支付用例的应用程序。企业链的概念以及其中的一个例子——Hyperledger 也被提及。最后,我们简要介绍了区块链的演进,以使读者对技术的趋势有所了解。在下一章中,我们将详细讨论以太坊的概念。
第十三章:以太坊基础知识
以太坊是一个开源的公共区块链,被认为是比特币的替代币。一位加拿大的加密货币研究员和程序员 Vitalik Buterin 在 2013 年底提出了这个想法。该平台于 2014 年中期通过在线众筹成立,并于 2015 年 7 月底上线。2016 年的 DAO 事件 导致了硬分叉,结果分为以太坊(ETH)和以太经典(ETC)。
在本章中,我们涵盖了以下关于以太坊的主题:
-
以太坊概述
-
基本概念,如以太、ERC20 代币、智能合约、EVM、gas、账户和预言机
-
以太坊性能问题以及解决该问题的持续努力,如 PoS、Casper、Plasma 和 Sharding
以太坊概述
2013 年底,Vitalik Buterin 向区块链社区发送了一封电子邮件,宣布一份概述以太坊想法的白皮书。他将其描述为一个具有内部语言的通用平台,因此任何人都可以编写应用程序。根据 Vitalik 的说法,以太坊最初的想法是创建一个面向金融科技的通用区块链。以太坊是比特币的一种变体。与专注于支付的比特币不同,以太坊是一种可编程的通用区块链。智能合约的引入是以太坊与比特币区分的关键。
一个众所周知的类比来描述以太坊和智能合约,将不信任的交易方聚集在一起交易数字或数字化的实物资产,就像 第十二章 结尾所述的自动售货机,区块链技术简介。
制作自动售货机后,包括机器所有者在内的任何人都无法更改规则。购买者在交易前或交易期间不需要担心所有者更改规则。因此,购买者可以信任机器按预期的方式行事,并且感到足够舒适以继续交易。当然,自动售货机未必提供完美的解决方案。顾客偶尔可能会遇到故障的机器并插入 1 美元,但什么也不发生。如果自动售货机没有提供退款解决方案,例如发布联系电话,顾客将永远失去 1 美元。另一方面,以太坊的解决方案要坚固得多。智能合约形式的规则分布到所有节点。同样的智能合约将在全球数千个(甚至更多)节点上几乎同时运行。只要至少有一个节点在运行,交易就会成功执行。换句话说,以太坊真正是一台全球计算机。
一些区块链爱好者回应了 Vitalik 的电子邮件,并组成了一个核心团队来推进并执行这个想法。 (这篇开创性的论文,标题为A Next-Generation Smart Contract and Decentralized Application Platform,可以在github.com/ethereum/wiki%20Wiki/
上获得,截至 2018 年 8 月 22 日,经过 169 次修订,该网址已存档,原文发表于 2015 年 3 月 28 日。)2014 年 1 月,成立了以太坊基金会。不久之后(2014 年初),一位英国计算机科学博士 Gavin Wood 发表了一篇名为Ethereum: A Secure Decentralized Generalized Transaction Ledger的黄皮书(ethereum.github.io/yellowpaper/paper.pdf
)。Gavin 的论文统一了多个实现以太坊想法的努力,并成为未来开发工作的蓝图。
在谈论以太坊的众筹活动之前,我们需要首先解释众筹的概念。众筹指的是通过从大量人群中募资,通常在互联网上,来为项目或倡议筹集资金的做法。众筹是一种替代的筹资方式。在区块链项目的情况下,众筹通常是以项目所有者出售预先设定的(预挖矿的)总量数字货币的一部分,将它们交换成法定货币或其他已建立的数字货币(如比特币)。
从 2014 年 7 月至 8 月,进行了一次在线众筹活动。这次活动导致了预挖矿以太——以太坊的本地加密货币,总量 1190 万枚代币的销售。这约占以太币总量的 12%:102,431,467 枚。通过这次众筹销售的收入,开发工作开始了。核心以太坊团队包括 Vitalik Buterin,Mihai Alisie,Anthony Di Iorio 和 Charles Hoskinson。以太坊项目的真正开发是由一家名为 Ethereum Switzerland GmbH(EthSuisse)的瑞士公司启动的。该平台于 2015 年 7 月 30 日上线。
Stephan Tual,一位前以太坊首席市场官,在 2016 年 4 月 30 日成立了一个名为The DAO的公司。这个实体的目的是管理选择部署哪个智能合约的流程。The DAO提出了一个聪明的想法,基于投资来选择合约。完成的智能合约会被发布在互联网上。潜在投资者将宣布要投资多少金额到一个智能合约中。投资金额最多的智能合约将被选择部署。The DAO通过众筹销售筹集了创纪录的 1.5 亿美元资金用于此项目。The DAO在六月被黑客攻击,由于软件中的漏洞损失了 5 千万美元价值的以太币。这次黑客攻击引发了以太坊社区内一场激烈的讨论,探讨如何处理这件事。出现了两种相争的观点:
-
加强以太坊代码,使未来类似攻击不可行,并将代码部署到所有节点
-
不对核心以太坊代码进行任何更改,冒着未来攻击的风险
Vitalik 呼吁进行硬分叉解决方案,并公开要求所有以太坊节点停止交易以部署修补代码。几小时后,全球数千节点完全关闭。大多数节点投票赞成采取硬分叉方法,并用修补程序升级了它们的核心以太坊代码,但仍有一小部分节点选择不采用修补程序,继续运行相同的代码。
这一硬叉事件将以太坊区块链分为两条。运行旧代码并维护原始区块链的节点变成了以太坊经典,代币符号为 ETC,而运行修补代码并维护分叉以太坊区块链的节点变成了以太坊,代币符号为 ETH。硬叉发生在区块编号为 1,920,000。硬叉在这两个网络之间造成了竞争。如今,ETH 价格涨了 130 多倍,而 ETC 只值 ETH 价格的十分之一,这是因为其不受欢迎以及担心未来发生类似The DAO攻击的担忧。
在The DAO硬叉之后,以太坊在 2016 年第四季度进行了两次分叉来处理新的攻击。虽然硬叉解决了过去黑客的攻击,但显然这并非可持续发展的解决方案,因为不能总是依靠创建硬叉来解决未来的每一次攻击。因此,以太坊通过阻止黑客的新垃圾邮件攻击来增强自己的保护。
硬叉用于解决黑客攻击,以太坊使用软叉进行协议升级,这些升级是影响以太坊基础功能和/或激励结构的重要变化。一些值得注意的软叉如下:
-
家园用于改进交易处理、燃气定价和安全性。这一软叉在 2015 年 7 月 31 日进行。
-
都市部分 1: 拜占庭用于减少以太坊虚拟机的复杂性并为智能合约开发者增加更多灵活性。这一软叉在 2017 年 10 月 16 日进行。
-
未来还计划了两次协议升级:都市部分 2:君士坦丁堡为过渡到股权证明奠定了基础。
2017 年 3 月,区块链初创公司、研究团体和主要公司共同创建了由 30 位创始成员组成的企业以太坊联盟(EEA)。五月份,这个非营利组织扩大,纳入了 16 家知名企业成员,如康奈尔大学的研究团体、三星 SDS、微软、英特尔、摩根大通、DTCC、德勤、安永、桑坦德银行、纽约梅隆银行、安海斯集团和加拿大国家银行。到了 2017 年 7 月,成员名单增加到了 159 位。
尽管自其最初推出以来已经进行了许多改进,以太坊仍在不断发展。以太坊 2.0 旨在解决最薄弱的环节之一,即可扩展性,并预计将于 2019 年分阶段推出,正如 Vitalik 最近的评论所示。
以太坊基本概念
以太坊在比特币区块链的基础上构建,包括包含链接块的分布式账本、工作证明算法等关键功能。然而,它最大的增加是引入了能以图灵完备的脚本语言编写的智能合约。由于这一新的增加,与比特币或其非智能合约的亲属不同,以太坊允许开发人员解决通用业务问题。
在讲解基本概念之前,我们总结一些有用的以太坊事实如下:
-
以太坊有三个主要组成部分:
-
去中心化:用来保证执行
-
哈希值:用来保护世界状态
-
签名:用于授权程序和交易
-
-
由于以太坊是一个区块链,它使用数学算法来替代中间实体,并将不信任的各方聚集在一起做生意。
-
以太坊区块链通过其共识机制在节点上验证数据的有效性,从而为数据带来了信任。
-
它使用总体验证来替代中央控制。
-
与交易一样,部署智能合约需要数字签名。已部署的智能合约是永久且不可变的。
-
智能合约会被分配一个地址。
-
假设一个智能合约存在漏洞并需要修复。修补后的智能合约将被部署在一个新分配的地址上,并且因此被视为完全独立于旧合约的全新智能合约。
-
2017 年 5 月,以太坊全球可达节点数量为 25,000 个,包括全节点和轻节点。
-
全节点已下载并可用完整区块链。以太坊账本可以被修剪。全节点验证在构建区块中的交易。矿工节点必须是一个全节点。
-
轻节点不存储整个区块链,但是它从它信任的某个人那里存储它关心的部分。
-
合约代码的脚本通过以太坊虚拟机(EVM)在全节点上执行。智能合约的地址存储了在 EVM 上运行的名为操作码的字节码。
-
由于一个智能合约在数以万计的机器上的全节点上运行,它真正是全球性的。换句话说,将智能合约写入区块链是全球性的和永久性的。
-
由于智能合约脚本以分散的方式存储,这提供了额外的安全层。所有全节点都知道其他节点存储相同的代码。对于黑客来说,向全球所有良好的节点推送恶意脚本并崩溃它们是不可行的。
-
智能合约是一份脚本化的法律文件,并且它是执行保证的。因为智能合约在部署时被签署,调用它的交易也被签署,所以在交易的两个交易方之间不应该发生争议。换句话说,有了像以太坊这样的去中心化区块链,对于法官的需求消失了!节点并且可以发展成一个完整网络。
-
由于永久性和不可变性的特性,以太坊区块链上的数据和程序是可审计的。这对于政府执行监管和合规要求可能具有特殊的兴趣。
-
以太坊是开源的。任何人都可以下载代码并创建自己的以太坊网络版本。当然,问题在于如何说服他人加入网络以体验其价值。
-
以太坊是去中心化的。因此,没有控制或指挥整个网络的主节点。网络通过共识运行,按照其协议。
-
以太坊还提供了容错能力。只要在灾难性攻击期间至少有一个完整节点存活,网络就可以从幸存节点重新构建并发展成完整网络。
-
尽管以太坊提供了极高的健壮性,但其背后的问题是当其失控时如何停止它。就像The DAO黑客事件的例子一样,网络不得不依赖维塔利克及其权威来彻底关闭它。与今天相比,那时的网络规模要小得多。随着网络规模的不断增长,这种方法将变得更加困难。未来,网络可能会增长到数千万个节点甚至更多。只要有一个节点不响应权威调用,以太坊网络仍然活跃。换句话说,完全关闭网络变得极为困难。当然,这就是去中心化区块链的全部意义所在:没有中心化的权威来指挥其他人!
-
以太坊允许对其他智能合约进行递归调用。编写不良的智能合约可能导致无限循环。为了解决这个问题,以太坊引入了一个断路器机制,即 Gas,后面将详细解释。
-
在大数据平台上,一个任务被分成分块分配给网络上的节点,工作由节点共同完成。然而,以太坊的完整节点执行相同的脚本片段。这意味着以太坊区块链的每个完整节点都存储并计算相同的数据;这是可靠但不可扩展的。可扩展性问题是以太坊面临的主要批评之一。正如我们稍后将讨论的那样,已经有多个努力在进行中以解决这个问题。
-
以太是以太坊的本地加密货币。以太坊允许用户发行自己的数字货币,称为代币。ERC-20/ERC-721/ERC 1400 是发行以太坊代币时应遵循的常见技术标准。
-
以太坊可以被视为互联网的第三代。这可能是以太坊的 JS API 被称为 Web3 的一个原因。有关使用区块链技术重写互联网的讨论正在进行。
-
在去中心化互联网上提供集中化服务的做法(例如 Google 在去中心化互联网上提供集中式搜索功能)也将适用于区块链。
以太
由于以太坊是建立在比特币之上的,它被认为是比特币替代币。与比特币类似,以太坊中的 Ether 相当于比特币中的 BTC。在提到协议、区块链、客户端软件和主网时会使用以太坊这个名称。
以太坊主网络是客户端用于将数字资产从发送方转移到接收方的区块链网络。换句话说,这是实际交易在分布式分类帐上发生的网络。主网络相当于生产环境。以太坊测试网络是用于开发的。如www.ethernodes.org/network/2
上所述,截至 2018 年 10 月 8 日,主网络有 13,662 个节点,测试网络有 29 个节点。由于实际交易发生在主网络上,因此 Ether 只在以太坊主网络上具有真正的价值。换句话说,在测试网络上,它一文不值。Ether(ETH 和 ETC)在数以万计的数字货币市场上上市和交易。它们的价格变化很大。例如,在 2018 年 10 月 8 日,ETH 的交易价约为 $223,ETC 为 $11.
Ether 可以在地址(账户)之间转移。它用于支付矿工的计算工作,他们会通过交易费用和执行交易产生的燃气消耗来获得报酬。在这里,燃料的概念对于以太坊至关重要,稍后将更详细地讨论。
Ether 是最高面额的货币。还有其他单位。最小的被称为 WEI,以数字货币先驱 Wei Dai 命名,他是 B-money 的发明者。B-money 是他对匿名、分布式电子现金系统的提案。其他单位包括 Gwei、microether 和 milliether。它们都有第二个名称。例如,milliether 也被称为 finney,以数字货币先驱 Harold Thomas Finney II 命名,他在 2004 年撰写了世界上第一个实施的加密货币 RPOW(可重复使用的工作凭证),在比特币之前。以下表格给出了 ether 与其他单位之间的转换率:
Unit | Wei value | Wei |
---|---|---|
Gwei (shannon) | 10⁹ Wei | 1,000,000,000 |
microether (szabo) | 10¹² Wei | 1,000,000,000,000 |
miliether (finney) | 10¹⁵ Wei | 1,000,000,000,000,000 |
ether | 10¹⁸ Wei | 1,000,000,000,000,000,000 |
ERC20 代币
以太坊是一个通用区块链。它允许开发者构建 DApp 并交易数字资产。相应地,它允许开发者定义一个称为代币的用户特定硬币。其中大部分代币都是 ERC20 代币。ERC 指的是以太坊意见征求,20 是分配给此意见征求的编号。换句话说,ERC-20 是以太坊区块链上用于实施代币的智能合约技术标准。根据 Etherscan.io,截至 2018 年 10 月 8 日,在以太坊主网络上发现了 125,330 个 ERC-20 兼容代币。
ERC-20 为以太坊代币定义了一系列规则。通过这样做,它允许以太坊代币在更大的以太坊生态系统内进行交互和转换。目前,Ether 不符合 ERC-20 标准。但是,由于 Ether 是以太坊的本地币,因此可以转换为其他代币。ERC-20 规范定义了包含方法和事件的接口。
以下是所需方法列表(github.com):
-
name
: 返回代币的名称,例如,HelloToken: function name() view returns (string name)
。 -
symbol
: 返回代币的符号,例如,HTC: function symbol() view returns (string symbol)
。 -
decimals
: 返回代币使用的小数位数;例如,8 表示将代币金额除以 100,000,000 以获得其用户表示:function decimals() view returns (uint8 decimals)
。 -
totalSupply
: 返回总代币供应量:function totalSupply() view returns (uint256 totalSupply)
。 -
balanceOf
: 返回另一个账户的账户余额,具有address _owner: function balanceOf(address _owner) view returns (uint256 balance)
。 -
transfer
: 它将指定数量(_value
)的代币转移到_to
地址,并且必须触发转账事件。如果_from
账户余额不足以支出,则应该抛出错误:function transfer(address _to, uint256 _value) returns (bool success)
。 -
transferFrom
: 它将指定数量(_value
)的代币从_from
地址转移到_to
地址,并且必须触发 Transfer 事件。除非_from
账户已经通过某种机制明确授权了消息的发送者,否则应该抛出错误:function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
。 -
approve
: 允许_spender
多次从您的账户提取,最多到_value
金额。如果再次调用此函数,则将当前授权额覆盖为_value
:function approve(address _spender, uint256 _value) returns (bool success)
。 -
allowance
: 返回_spender
仍然被允许从_owner
提取的金额:function allowance(address _owner, address _spender) view returns (uint256 remaining)
。
所需事件列表如下:
-
transfer
: 在代币被转移时必须触发,包括零值转移。创建新代币的代币合约应该在创建代币时触发Transfer
事件,并将_from
地址设置为 0x0:event Transfer(address indexed _from, address indexed _to, uint256 _value)
。 -
approval
: 在任何成功调用 approve(address _spender
,uint256 _value)
时必须触发:event Approval(address indexed _owner, address indexed _spender, uint256 _value)
。
尽管以太坊允许一个人创建自己的货币,但以太坊真正的价值在于其对智能合约的保证执行。以太币和 ERC20 代币的创建主要是为了支持项目的初创资金,并在交易过程中用于支付以规避银行。没有真正的商业用例,代币一文不值。
智能合约
术语智能合约最初由尼克·萨博创造,他是一位计算机科学家、法律学者,也是比特黄金(Bit Gold)的发明者,在 1994 年提出。他因其对数字合同和数字货币的研究而成为加密货币世界的传奇人物。一些人甚至认为他是中本聪。尽管他拒绝了这一说法。
尼克·萨博最初定义了智能合约如下:
“智能合约是一种执行合同条款的计算机化交易协议。智能合约设计的一般目标是满足常见的合同条件(例如付款条件、留置权、保密性,甚至执行),最小化恶意和意外异常,并尽量减少对受信任中介的需求。相关的经济目标包括降低欺诈损失、仲裁和执行成本以及其他交易成本。”
在自动售货机上,交易规则内置在机器硬件中。数字资产的交易规则内置在脚本中。也就是说,智能合约由代码组成。以下是一些关于智能合约的有用事实:
-
智能合约是不可变的。
-
智能合约是永久的。
-
智能合约具有时间戳。
-
智能合约是全球可用的。
-
智能合约是数字化的法律文件。
-
智能合约是一种旨在在交易方之间数字化促进、验证或强制执行协议的计算机协议。
-
智能合约允许在没有第三方作为中介的情况下执行交易。这些交易是可审计和不可逆转的。
-
智能合约可以移动数字硬币,执行传统支付,或转移数字资产,甚至提供现实世界的商品和服务。
-
对于涉及第三方的商业交易,例如购买/出售房屋,经常使用第三方担保账户来暂时存储交易双方的资金。有了智能合约,就不需要担保账户。智能合约消除了担保账户的需要,因为它们被保证用于转移资金和资产。
-
智能合约比传统合同法提供了更多的安全性,其交易成本仅为与合同相关的其他交易成本的一小部分。
-
在以太坊基金会使用的解释中,智能合约并不一定指的是传统合约的经典概念。它可以是任何类型的计算机程序。
-
要部署和运行智能合约,必须对部署进行数字签名,类似于在以太坊区块链上发送其他数据或交易。
-
智能合约可以是公开的,对开发者开放。这引发了一个安全问题。如果智能合约存在缺陷或安全漏洞,所有开发者都能看到。更糟的是,由于其不可变性,这种缺陷或漏洞不易修复。这给黑客大量时间来探索弱点并对以太坊区块链发起攻击。The DAO事件就是这个问题的一个高调例子。
以太坊智能合约可以使用四种语言之一开发:solidity(受 JavaScript 启发)、Serpent(受 Python 启发,不再使用)、LLL(受 Lisp 启发)和 Mutan(受 Go 启发,不再使用)。无论使用哪种语言,智能合约都是用高级编程语言编写的,需要编译成低级的、可在机器上运行的语言。在以太坊智能合约实施中,采用了类似于 Java VM(JVM)的 VM 方法。以太坊的 VM 被称为EVM。智能合约脚本被转换为可在 EVM 上运行的代码,称为字节码。然后,操作码被部署到以太坊区块链上执行。目前,一种以研究为导向的语言正在开发中,这种语言被称为 Vyper,是一种基于 Python 的强类型语言。
以太坊虚拟机
在六十年代,当计算机刚刚发明时,编程使用的是较低级别的语言,例如汇编语言(assembler)。例如,汇编代码行ADD R1 R2 R3,是一个指令,用于将寄存器 1 和寄存器 2 的内容相加,结果放在第三个寄存器,R3 中。寄存器是内置在 CPU 中的临时存储区。对于 32 位 CPU,寄存器是 32 位长的。
然后,汇编语言中的代码被转换为 0 和 1 序列的机器语言,这是机器可执行的。使用低级语言编码是乏味且耗时的。当发明了像 ALGOL 或 BASIC 这样的高级语言时,编码时间大大缩短。然而,底层过程仍然相同:将代码编译成 0 和 1 序列的机器可执行语言。Java、Python、JavaScript 和 C++目前是流行的高级语言。
虽然编译方法效果不错,但它确实有一个不便之处:缺乏可移植性。在一台计算机上编译的代码是机器依赖的。换句话说,它不是可移植的。为了解决这个问题,引入了虚拟机的概念。虚拟机(VM)是对计算机系统的模拟。虚拟机有两种类型:系统虚拟机(也称为全虚拟化),它提供了一个真实机器的替代品;以及进程虚拟机,用于在平台独立的环境中执行计算机程序。我们前面讨论的 VM 过程指的就是这个。
用高级语言编写的程序被编译成虚拟机可执行代码。只要计算机支持这样的虚拟机,编译后的代码就可以在上面运行,无需重新编译。例如,JVM 是一个众所周知的 Java 虚拟机,它使计算机能够运行编译成 Java 字节码的 Java 程序。
在以太坊的情况下,智能合约是用高级语言编写的,主要是 solidity。智能合约被编译成操作码,这些操作码可在专为以太坊构建的虚拟机上执行,即EVM。EVM 具有可移植性和健壮性,因为 EVM 在运行时执行检查以防止崩溃。尽管这些检查确实会带来性能损失。
由于以太坊合约可以用四种语言之一编写:solidity、serpent、LLL 和 Mutan,因此有四个编译器将这四种语言编写的智能合约转换为在 EVM 上运行的操作码。另一个相关概念是以太坊客户端,它指的是安装在节点上的一组软件,用于解析和验证区块链交易、智能合约以及所有相关内容。以太坊客户端采用八种语言之一实现:Python、C++、Go、JavaScript、Java、Haskell、Ruby 和 Rust。已实现的 EVM 是以太坊客户端的重要组成部分。因此,操作码可以在八个客户端实现之一上运行。EVM 最初是为货币交易而设计的,后来扩展到其他数字资产。因此,支持某些功能存在限制。开发人员面临一些严格的限制(例如,字符串或本地寄存器的使用)。
以太坊 gas
以太坊交易可以调用智能合约,智能合约可以反过来调用另一个智能合约,然后又调用另一个,依此类推。当智能合约存在缺陷时,可能会导致无限循环。在区块链之外,很容易解决无限循环的问题。可以通过简单关闭服务器、重新启动服务器、调试程序、修复代码中的错误逻辑、重新编译和重新部署来停止失控程序。
在以太坊区块链上,这种方法根本行不通!想象一下,如果全球范围内数万个节点几乎同时进入无限循环。为了阻止无限循环的智能合约,所有节点都需要在短时间窗口内关闭。只要一个节点未能遵守,无限循环的智能合约仍将处于活动状态并运行。协调和关闭所有这些节点是一场后勤噩梦。
为了解决这个问题,引入了gas的概念。一辆车依靠燃烧汽油的发动机来运行。当发动机用完汽油时,车辆就会停止。以太坊引入了 gas 的概念来实现相同的效果。当向以太坊区块链提交交易时,请求者需要提供最大 gas 数量。例如,在下面的例子中,提交了一个调用 HelloWorld 智能合约的交易请求,其最大消耗不超过指定的 gas 值:
当此请求被挖矿节点验证时,将调用 HelloWorld 智能合约。在 EVM 上运行的每个操作都会消耗预定义数量的 gas。例如,ADD(求和操作)消耗三个 gas,而 MUL(乘法操作)则使用五个 gas。为了说明问题,假设一个智能合约写得很糟糕,并且包含一个无限循环。此外,我们假设每个循环由一个 ADD 操作和一个 MUL 操作组成。因此,一个循环将消耗八个 gas(三个 gas 用于 ADD,五个 gas 用于 MUL)。在 EVM 执行足够多的循环后,将消耗指定的最大 gas 值。因此,EVM 停止执行合约。因此,所有节点将在大约相同的时间停止运行。gas 的另一个优点是使垃圾邮件攻击的成本昂贵化,从而降低了黑客风险。
Gas 是一种用于测量消耗的计量单位,就像千瓦是用于测量电力使用的单位一样。假设一个家庭在一个月内使用了 210 KW。在向家庭发送账单之前,公用事业公司首先根据预先定义的转换率将 210 KW 转换为美元。假设 1 千瓦的价格为 0.2 美元,那么一个月的总费用为 0.2 * 210 = 42 美元。类似地,燃气使用量被转换为以太并收取给请求者。以太坊允许请求者在提交交易时指定转换率。矿工有权选择性地处理交易,优先处理费率较高的交易。如果请求者未指定费率,则 EVM 使用默认费率,这个费率会有所不同。例如,2016 年的费率是 1 gas = 0.00001 ETH。到 2018 年,一个 gas = 0.00000002 ETH。
账户
在第十二章 区块链技术导论中,我们讨论了地址,这是一个类似账户的概念,用于承载比特币的余额。比特币使用 UTOX 模型来管理地址之间比特币的转移。然而,要找到地址的余额,必须检索整个账本,这非常不方便。这种不便之处在于比特币不支持链上图灵完备编程语言,也没有状态的概念。另一方面,以太坊区块链支持脚本语言和智能合约;它可以维护状态。以太坊交易通过调用智能合约方法来管理状态转换。以太坊不再需要依赖 UTOX 来管理支付。相反,它通过状态转换使用账户和余额进行操作。状态表示所有账户的当前余额,以及其他数据。状态不存储在区块链上。它在离线保存在 Merkle Patricia 树中。这是因为状态是可变数据,而区块是不可变的。与比特币一样,加密货币钱包可用于管理公钥和私钥*或账户,用于接收或发送 ETH。换句话说,以太坊引入了账户的概念。
以太坊支持两种类型的账户:外部拥有的账户(由人类用户通过拥有私钥控制)和合约账户。
-
外部可控制的账户:
-
具有以太币余额
-
可以发起转移以太币或触发智能合约代码的交易
-
由用户通过私钥控制
-
没有关联的智能合约代码
-
-
合约账户:
-
具有以太币余额
-
具有关联的智能合约代码
-
通过从其他合约接收的交易或调用触发智能合约代码的执行
-
-
对于这两种类型的账户,有四个组件:
-
nonce
:对于外部拥有的账户,它指的是从该账户地址发送的交易数量;对于合约账户,nonce 每次调用另一个合约时增加 -
balance
:这是该地址拥有的 Wei 的数量 -
storageRoot
:账户存储内容的 256 位哈希 -
codeHash
:该账户的代码的哈希是 EVM;这是当地址接收到调用时执行的代码
-
当从合约账户向外部拥有的账户转移以太币时,会收取费用,例如 21,000 gas。当从外部拥有的账户发送以太币到合约账户时,费用较高,并取决于交易中发送的智能合约代码和数据。
以太坊地址具有以下格式:
-
以 0x 为前缀开始,这是十六进制的常见标识符
-
ECDSA 公钥的 Keccak-256 哈希的右侧 20 字节(大端序)
由于在十六进制中,两个数字被存储在一个字节中,一个 20 字节的地址用 40 个十六进制数字表示。一个示例地址是0xe99356bde974bbe08721d77712168fa074279267
。
预言机
正如我们已经了解的,相同的以太坊智能合约在全球的节点上运行。但我们还没有强调的是,所有这些节点接受相同的输入,并应该产生相同的输出。这被称为确定性。以太坊依赖于这种确定性,因为为了验证智能合约和交易的有效性,挖矿节点必须在运行相同的代码和输入时产生相同的结果。
这种确定性产生了一个挑战。一方面,以太坊是一个通用的平台,可以用于转移任何数字或数字化资产。其智能合约需要来自外部来源的数据或输入,如互联网上的股价、宏观经济或微观经济指标等。如果不能访问这些信息来源,智能合约的用例将仅限于其潜力的一小部分。另一方面,即使有微小的时间差异,节点可能从外部来源获取不同的信息。有了不同的输入,节点最终会得到不同的输出。因此,确定性性质不成立。因此,智能合约不允许调用互联网 URL 或直接从外部来源获取数据。为了解决这一悖论,实施了预言机的概念。
根据韦氏词典,预言机的其中一个定义是神明透过神谕者揭示隐藏的知识或神圣的目的的神殿。在区块链世界中,预言机指的是提供外部数据的第三方或去中心化数据源服务。预言机提供了从现实世界到数字世界的接口。预言机数据不是区块链的一部分,它被保存在链下。
预言机有不同类型。其中两种是软件预言机和硬件预言机:
-
软件预言机:通常指轻松获取的在线信息,如股指收盘价、外汇汇率、经济新闻或天气预报等。软件预言机很有用,因为它们为智能合约提供了各种类型和最新信息。
-
硬件预言机:通常指扫描信息,如 UPS 交付扫描、挂号邮件扫描或供应商货物交付扫描。这种信息对于激活在事件发生时触发的智能合约是有用的。
其他概念
由于以太坊是建立在比特币之上的,许多基本概念已经在第十二章中讨论过,区块链技术简介。在本小节的其余部分,我们将简要介绍其中一些,并重点关注关键区别。
-
共识算法:
- 与比特币一样,PoW 是其共识算法。与比特币不同,以太坊正在计划切换到另一种称为股权证明(PoS)的共识算法,以在下一个版本的 serenity 中显著提高性能。
-
私有区块链:
-
总的来说,比特币和以太坊都是公有区块链,因为网络对任何人开放,节点可以自由加入。
-
以太坊存在私有链的变体。在私有以太坊中,节点需要在加入网络之前获得批准。这些区块链称为私有区块链。私有区块链适用于企业应用。超级账本和 JPM 摩根的 Quorum 是以太坊私有区块链的著名变体的示例。另一个示例是 Brainbot 的 hydrachain。
-
-
链下数据:
-
在比特币区块链中,我们不太谈论链下数据的概念。在以太坊区块链中,需要讨论这个话题。存在多种情况,数据无法存储在链上:
-
第一种情况是状态变量。在区块链中存储的所有数据都是不可变的,因为区块的内容被哈希,而区块通过这些哈希链接在一起。区块的内容发生微小变化将导致之后所有区块的重构,这显然是不可行的。然而,状态变量例如用于保存余额。它们会改变内容以反映余额变化。解决方案是将它们保存在链下。
-
神谕是另一个例子,从外部来源提取的信息保存在链下,以供智能合约使用。
-
以太坊的发明是为了允许交易通用数字或数字化资产。描述底层资产的元数据保存在链下。
-
对于比特币,分布式分类账必须保存在所有节点上,以提供交易验证所需的信息。在以太坊的情况下,加密货币或数字资产的余额可以直接从状态变量中检索。无需浏览分类账即可获取余额以确定发送方地址是否有足够的资金。因此,完整节点可以选择仅保留分类账的一部分,即裁剪分类账。被裁剪的区块可以在链下的集中位置保存以供将来查询。
-
-
-
测试:
-
彻底测试、反复测试和三重测试智能合约至关重要。安全测试至关重要。正如前面所述,在以太坊短暂的历史中,曾发生过几起备受关注的黑客事件,主要是由于有漏洞的智能合约代码。
-
由于智能合约中引入的错误,以太坊比比特币不安全。以太坊智能合约保存在链式区块中,且未加密。黑客可以轻易发现并探索有漏洞的合约代码的脆弱性,并进行攻击。另一方面,像比特币一样,以太坊上的数据和交易相对安全,不容易受到黑客攻击。只有合约是黑客可以构造恶意交易来调用和滥用的。
-
智能合约部署后,将会永久不变。部署修订后的代码将成为一个具有不同地址的新合约。它具有具有新余额的不同状态变量。
-
智能合约的部署不是免费的。它会消耗 gas。
-
-
数字签名、加密和公钥/私钥:
-
比特币是一个多签名过程。为了执行交易,双方都必须签署它。以太坊类似。此外,智能合约的部署也需要数字签名。
-
像比特币一样,使用以太坊区块链可以同时生成一对公钥和私钥的钱包应用程序。地址由公钥派生;也就是说,地址只是公钥的哈希值。发送者使用私钥签署交易,接收者使用公钥验证签名的真实性。通常,一对公钥和私钥可以用于支持以下两种类型的活动:
-
发送秘密消息:公钥用于加密消息,私钥用于解密消息。
-
签名:使用私钥进行加密并生成签名。 公钥用于解密以进行签名验证。
-
目前比特币和以太坊的区块交易内容均未加密。另一方面,Zcash 的区块内容是加密的。
-
由于每个以太坊交易,包括智能合约,都必须经过数字签名,一个节点只需要接受数字签名的请求,可能无需验证整个交易历史。这种方法可以帮助提高性能。
-
-
DAO:
-
DAO 指的是分散自治组织。不应将其与名为 The DAO 的组织混淆,后者与一次黑客事件密切相关,导致了 以太坊 分裂为 以太坊 (ETH) 和 以太坊经典 (ETC)。
-
DAO 可以被认为由智能合约组成,这是一种构建于分散代码之上的层次结构,即分散核心 → 智能合约 → DAO。
-
分散的代码保存在多个节点上。它肯定会运行且无法停止。
-
智能合约转移货币和数字资产。
-
DAO 由智能合约组成并创建独立实体或社区。
-
-
-
DApp:
-
DApp 是一个重要的话题。由于书籍大小的限制,我们只简要提及它:
-
DApp 指的是分散应用程序,并使用分散代码。
-
以太坊是一个通用的 DApp 平台。
-
一个以太坊 DApp,像任何其他区块链 DApp 一样,具有去中心化的后端(例如,智能合约)和集中化的前端(用于与区块链交互的客户端应用程序)。这种架构是由今天区块链的性能和限制的原因所决定。
-
前面讨论过,大部分后端、数据库和业务逻辑都是托管在链外的。
-
-
-
以太坊问题:
-
以太坊受到从比特币继承而来的问题的影响:
-
数据可能因分叉而丢失。当存在两个竞争的链时,无法快速增长的链必须被丢弃,以保持所有节点上的数据一致性。如果交易未被包含在获胜链的区块中,那些在短链上的交易将会丢失,甚至被原始请求者所不知晓!
-
由于链上数据并未加密,区块链不具备匿名性和保密性。
-
地址未经验证。这很糟糕。如果接收者地址被错误输入,那么转移给它的硬币将是永久的,因为交易是永久的,硬币将永远锁定!
-
PoW 算法消耗大量电力。据报道,赛里斯一些大型挖矿操作需要专门的发电站供电。
-
-
性能
从比特币继承而来的另一个问题是以太坊运行速度缓慢。它比其他承载交易数据的平台慢得多,例如传统数据库。例如,比特币平均需要 10 分钟建立一个新的记录。按照一个经验法则,等待六个新区块建立完毕后,一笔交易就会被视为完成(就像在数据库中的确认一样)。这意味着,在平均情况下,请求者将等待一个小时才能看到请求完成。在以太坊中,矿工建立区块的平均时间是 17 秒,建议在交易确认前等待 12 个区块。这是 12 * 17 = 204 秒,即用户等待 3.4 分钟的时间。在这里,在确认交易前等待一些连续的区块建立是有用的。在任何时候,以太坊都可能存在竞争链。等待给予以太坊充足的时间来处理竞争链问题并达成共识。
吞吐量
吞吐量是衡量系统在一定时间窗口内可以处理多少信息单位的指标。为了衡量交易平台的性能,吞吐量用TPS表示,即每秒交易数量:
-
对于比特币,TPS 可以计算如下。比特币区块通常包含 1500-2000 笔交易。我们使用最高数值 2000。由于确认这 2000 笔交易需要 60 分钟,所以其 TPS = 2,000 / (60*60) = 0.56;也就是说,每秒仅半个交易。对以太坊进行类似的计算,得到 TPS = 2,000 / 204 = 9.8,几乎是 10 笔交易每秒——比比特币要好得多。以太坊基金会正在采用夏丁(sharding)方法,由维塔利克(Vitalik)领导,旨在将 TPS 提高 80 倍。
-
以 VISA 为例,平均 TPS 为 2000,峰值为 40,000。像 VoltDB 这样的高性能数据库可以处理每秒超过一百万次插入。证券交易所可以匹配成千上万笔交易。
-
然而,这种比较并不完整。从商业角度来看,只有在清算和结算时,信用卡或交易才最终确定。对于信用卡,账单周期通常为 2-3 个月。证券交易所需要三天来结算一笔交易。从这个意义上讲,以太坊要快得多,因为在区块链上交易和结算是同时进行的。
-
与数据库相比,以太坊处于不利地位。数据库提交可以在插入、更新或删除事务后立即进行。
-
这些是导致以太坊速度变慢的原因:
-
每个完整节点必须执行相同的智能合约代码。
-
随着以太坊网络规模的增长,达成共识所需的时间将变长,因为在越来越多的节点之间传输数据来验证交易、访问信息和通信需要时间。
-
-
有方法可以增加吞吐量。以下是一些:
-
当区块大小增加时,可以在一个区块中托管更多的交易,并且可以获得更高的 TPS。
-
并行运行多个链。企业链,如超级账本(Hyperledger Fabric)和 R3 的 Corda 使用这种方法。
-
状态通道设计有助于提高吞吐量。以太坊的状态通道实现示例是雷电网络(Raiden)。微型雷电网络于 2017 年 11 月推出。状态通道背后的想法是在两个参与方之间使用离链进行交易,并在链上进行交易结算。离链交易是另一个值得深入讨论的话题,但不在本书中。
-
股权证明(PoS)
PoS 共识算法基于这样一个原则,即当一个矿工拥有更多的硬币时,该矿工具有更多的权力来挖掘或验证交易,建立新区块的机会更高,因此获得更多奖励硬币的机会也更高。PoS 是节能的,并且可以更快地达成共识。
有几种随机化方法可用于选择构建下一个区块的矿工,而不仅仅是基于外部拥有账户的以太坊余额,以避免最富有的矿工始终被选择的情况:
-
随机区块选择*😗 使用一个公式来寻找组合中最低的哈希值以及选择矿工的股份大小。
-
货币年龄选择*😗 拥有足够长时间(比如 30 天)的货币有资格竞争下一个区块。拥有更老和更大一组货币的矿工有更好的机会被授予该角色。
-
委托股权证明*😗 该实现选择了一定数量的节点来提出和验证区块到区块链。
-
随机化股权证明*😗 每个节点都是随机选择的,使用可验证的随机信标来构建新区块。
以太坊正在努力在一个新的标记版本中用 PoS 替换 PoW。
Casper
PoS 正在被作为对计算效率低下的 PoW 算法的替代而进行研究。由于对出现一组集中化超级节点(在构建新区块方面起到过大作用)等问题的担忧,PoS 尚未完全在主网上实施和升级。Casper 是以太坊社区努力实现从 PoW 到 PoS 的过渡。
在 Per Casper 协议中,验证者(比特币中的矿工的以太坊等效物)将部分 Ether 作为赌注放在一边。当验证者确定要构建的候选区块时,验证者将在该区块上下赌注。如果该区块确实添加到链上,验证者将根据其赌注的大小获得奖励。行为恶意的验证者将被惩罚,其赌注将被移除。Casper 有两个主要项目:Casper FFG 和 Casper CCB。
Casper FFG(友好最终性小工具; Vitalik 版本的 Casper)是一个混合算法,运行在 PoW 上,但将网络上每 50^(th) 个区块视为 PoS 检查点。验证者对这些区块的最终性进行投票,并将其写入区块链。FFG 旨在成为向完全采用 PoS 过渡的中间步骤。FFG 已经在测试网络上运行。它将很快完全实现在主网上。
Casper CBC(Correct by Construction,Vlad’s Casper)更加激动人心。CBC 专注于设计协议,其中一个节点对安全性的局部视图可以扩展到达成共识安全。到目前为止,该方法仅仅是研究,并且没有发布计划可供其进入以太坊。
Plasma
2017 年,Buterin 和 Joseph Poon 提出了他们的想法,呼吁扩展以太坊的性能,即增加 TPS。与状态通道设计类似,Plasma 是一种在链下进行交易的技术,同时依赖底层以太坊区块链提供安全性。因此,Plasma 属于 链下 技术的一部分。Truebit 是该组中的另一个例子。
Plasma 的工作方式如下:
-
智能合约是在主链上创建的,并被视为 Plasma 子链的根。它们定义了子链的规则,并被调用来在主链和子链之间移动资产。
-
创建具有自己共识算法的子链,例如 PoS。
-
部署智能合约,定义实际的业务规则,到子链。
-
在主链上创建的数字资产通过调用 plasma 根合约转移到子链上。
-
子链上的区块构建者定期向主链提交验证,证明子链的当前状态符合共识规则。用户发送和执行请求,而不需要直接与主链交互。
Plasma 具有以下优势:
-
允许以太坊区块链处理更大的数据集
-
启用更复杂的应用在区块链上运行
-
大大增加吞吐量
以太坊社区正在积极开发以太坊 plasma. Plasma-MVP(最小可行产品)首先正在进行开发,以积累经验并测试其可行性。有可能在 2018 年底发布 plasma-mvp。Plasma 的发布将在一个或多个季度内进行。
分片
Vitalik 最初提出了用于扩展以太坊区块链的分片概念。他的提案是把区块链分割成数百或数千个独立的碎片:分片。所有分片共享相同的共识算法和安全模型。这些分片将不处理不同类型的任务,并且不需要所有全节点进行验证。相反,每个分片都用于单一目的,因此在该目的上非常高效。总之,分片将网络状态分割为多个分片,每个分片都有自己的交易历史和网络状态的一部分。为了在区块链上实现分片概念,需要一个验证者管理合约,这是一个智能合约。它验证每个分片的区块头,维护验证者的利益,并在分片之间伪随机地选择验证者。分片提供了一种替代方式,可以戏剧性地提高以太坊的性能,并且可能在 2020 年早期实施。
摘要
以太坊是在比特币的基础上开发出来的,引入了智能合约以及像 solidity 这样的图灵完备脚本语言。以太坊是一个面向 DApp 开发的通用平台。该平台非常受欢迎。然而,以太坊还不够成熟。与比特币相比,它更容易受到黑客攻击,因为编写智能合约时的任何人为错误都会被所有人看到。它从比特币那里继承了性能问题。目前有许多倡议正在解决这个可伸缩性问题。在接下来的章节中,我们将深入了解 solidity,这是编写以太坊智能合约的最流行语言。
第十四章:Solidity 编程概述
Solidity 是一种智能合约编程语言。它由 Gavin Wood、Christian Reitwiessner、Alex Beregszaszi 和几位以太坊核心贡献者开发。它是一种类似 JavaScript 的通用语言,旨在针对 以太坊虚拟机(EVM)。Solidity 是以太坊协议中的四种语言之一,与 Serpent(类似于 Python)、LLL(类 Lisp 语言)、Vyper(实验性)和 Mutan(已弃用)处于同一抽象层级。社区逐渐趋同于 Solidity。通常,今天任何人谈论以太坊的智能合约时,都隐含地指的是 Solidity。
在本章中,我们将讨论以下主题:
-
什么是 Solidity?
-
Solidity 开发环境工具
-
智能合约简介
-
常见智能合约模式
-
智能合约安全
-
案例研究 – 众筹活动
什么是 Solidity?
Solidity 是一种静态类型的合约语言,包含状态变量、函数和常见数据类型。开发者可以编写实现智能合约中业务逻辑函数的去中心化应用程序(DApps)。合约在编译时验证和强制约束,而不是在运行时。Solidity 被编译成 EVM 可执行字节码。一旦编译完成,合约被上传到以太坊网络。区块链将为智能合约分配一个地址。区块链网络上的任何有权限的用户都可以调用合约函数来执行智能合约。
下面是一个典型的流程图,展示了从编写合约代码到在以太坊网络上部署和运行的过程:
Solidity 开发环境工具
智能合约开发仍处于起步阶段。创建这样的合约并以方便的方式与之交互可以通过多种方式实现。以下强大的工具可用于在以太坊平台上构建、监视和部署您的智能合约开发。
基于浏览器的集成开发环境(IDE)
在这个部分,我们将看看像 Remix 和 EthFiddle 这样的在线浏览器工具。
Remix
Remix 是一个强大的、开源的智能合约工具,可以帮助你直接从浏览器中编写 Solidity 代码。它支持编译、运行、分析、测试和调试选项。在开发和测试时,Remix 提供了以下三种环境:
-
JavaScript VM:Remix 自带五个以太坊账户,每个账户默认存入 100 以太币。这对于在开发阶段测试智能合约非常方便。不需要进行挖矿,因为这是自动完成的。当你是一个初学者时,这个选项是一个不错的选择。
-
注入的 Web3:此选项将直接调用注入的浏览器 web3 实例,如 MetaMask,以太坊网络浏览器扩展。MetaMask 提供了许多功能和特性,就像普通的以太坊钱包一样,它允许您与 DApp 进行交互。
-
Web3 提供者:Remix 还支持 Web3 提供者。web3.js 库是官方的以太坊 JavaScript API。它用于与以太坊智能合约进行交互。您可以通过 web3j API 连接到区块链网络。Web3j 支持三种不同的提供者:HTTPProvider、WebsocketProvider 和 IpcProvider。在 Remix Web3 中,您可以提供 HTTP URL 来连接远程区块链实例。该 URL 可以指向您的本地私有区块链、测试网和其他实例端点。
首先使用 Remix solidity IDE:remix.ethereum.org
。以下是 Remix 的 UI 截图:
EthFiddle
EthFiddle 是一个非常简单的 solidity 基于浏览器的开发工具。您可以快速测试和调试智能合约代码,并分享代码的永久链接。使 EthFiddle 突出的一个功能是其进行安全审计的潜力。以下截图显示了软件界面:
EthFiddle 软件界面
这是 EthFiddle solidity IDE 的链接:ethfiddle.com
。
命令行开发管理工具
命令行工具是服务器端以太坊工具,用于创建 DApp 项目的基本结构。
Truffle
Truffle 是一个流行的以太坊开发环境和测试框架,是以太坊的资产管道。Truffle 的主要功能包括以下内容:
-
内置智能合约编译、连接、部署和二进制管理
-
使用 Mocha 和 Chai 进行自动合约测试的 Truffle 网站链接:http
-
可编写的部署和迁移框架
-
部署到多个公共和私人网络的网络管理
-
用于直接与合同通信的交互式控制台
-
我们将在下一章节中进行更详细的讨论,并使用 Truffle 开发 ERC20 代币的 DApp。
-
以下是 Truffle 的网站链接:
truffleframework.com/
智能合约简介
让我们从最基本的智能合约示例HelloWorld.sol
开始,如下所示:
pragma solidity ⁰.4.24;
contract HelloWorld {
string public greeting;
constructor() public {
greeting = 'Hello World';
}
function setNewGreeting (string _newGreeting) public {
greeting = _newGreeting;
}
}
Solidity 的文件扩展名是.sol
。它类似于 JavaScript 文件的.js
,以及 HTML 模板的.html
。
solidity 源文件布局
一个 solidity 源文件通常由以下结构组成:pragma、注释和导入。
Pragma
第一行包含关键字 pragma 的源代码文件简单地指出该文件不会与版本早于 0.4.24 的编译器一起编译。任何新版本也不会破坏功能。符号 ^
暗示另一个条件 - 源文件也不会适用于版本大于 0.5.0 的编译器。
注释
注释用于使源代码更容易让人类理解程序的功能。多行注释用于对代码进行大文本描述。编译器会忽略注释。多行注释以 /*
开始,以 */
结束。
在 HelloWorld
示例中,对 set
和 get
方法有注释:
-
方法:
setNewGreeting(string _newGreeting) {}
函数 -
@param
:这用于指示将哪些参数传递给方法,以及它们预期的值是什么
导入
Solidity 中的 import 关键字与 JavaScript 的过去版本 ES6 非常相似。它用于将库和其他相关功能导入到您的 solidity 源文件中。Solidity 不支持 export 语句。
以下是一些导入示例:
import * as symbolName from “solidityFile”
上述行将创建一个名为 symbolName
的全局符号,其中包含从导入文件 solidityFile
中的全局符号成员。
另一个与前述导入相当的 solidity 特定语法是以下内容:
import solidityFile as symbolName;
您还可以导入多个符号,并将其中一些符号命名为别名,如下所示:
import {symbol1 as alias, symbol2} from " solidityFile";
以下是使用 zeppelin solidity 库创建 ERC20 代币的示例:
pragma solidity ⁰.4.15;
import 'zeppelin/contracts/math/SafeMath.sol';
….
contract ExampleCoin is ERC20 {
//SafeMath symbol is from imported file SafeMath.sol'
using SafeMath for uint256;
…
}
对于上述代码片段中显示的示例,我们从 Zeppelin 导入了 SafeMath
库,并将其应用于 uint256
。
路径
当导入一个 solidity 文件时,文件路径遵循一些简单的语法规则:
- 绝对路径:
/folder1/ folder2/xxx.sol
从/
开始,路径位置是从相同的 solidity 文件位置到导入的文件。在我们的 ERC 20 示例中,如下所示:
import 'zeppelin/contracts/math/SafeMath.sol';
实际项目结构如下所示:
相对路径
../folder1/folder2/xxx.sol
:这些路径是相对于当前文件的位置进行解释的,.
表示当前目录,..
表示上级目录。
在 solidity 路径中,可以指定路径前缀重映射。例如,如果要导入 github.com/ethereum/dapp-bin/library
,可以先将 GitHub 库克隆到 /usr/local/dapp-bin/library
,然后运行如下所示的编译器命令:
solc github.com/ethereum/dapp-bin/library=/usr/local/dapp-bin/library
然后,在我们的 solidity 文件中,您可以使用以下 import
语句。它将重映射到 /usr/local/dapp-bin/library/stringUtils.sol
:
import "github.com/ethereum/dapp-bin/library/stringUtils.sol " as stringUtils;
编译器将从那里读取文件。
合约的结构
合约包括以下构造:状态变量,数据类型,函数,事件,修饰符,枚举,结构和映射。
状态变量
状态变量是永久存储在合约存储中的值,并用于维护合约的状态。
以下是代码示例:
contract SimpleStorage {
uint storedData; // State variable
//…
}
数据类型
Solidity 是一种静态类型语言。熟悉 JavaScript 和 Python 等语言的开发人员会发现 Solidity 语法很容易上手。每个变量都需要指定数据类型。永远传递的变量称为值类型,它是内置或预定义的数据类型。
Solidity 中的值类型如下:
类型 | 运算符 | 示例 | 注意 |
---|---|---|---|
Bool | !、&&、||、==、!= | bool a = true; | 布尔表达式为 true 或 false。 |
Int (int8 到 int256) | 比较运算符:<=、<、==、!=、>=、>、位运算符:&、|、^、+、-、一元 -、一元 +、*、/、%、**、<<、>> | int a = 1; | 有符号整数,从 8 到 256 位,步长为 8。 |
Uint (uint8 到 uint256) | 比较运算符:<=、<、==、!=、>=、>位运算符:&、|、^、+、-、一元 -、一元 +、*、/、%、**、<<、>> | uint maxAge = 100; | 无符号整数,从 8 到 256 位,步长为 8。 |
地址 | <=、<、==、!=、>=、> |
address owner = msg.sender;
address myAddress = 0xE0f5206B…437b9;
保存 20 字节值(以太坊地址的大小)。 |
---|
<address>.balance |
<address>.transfer |
<address>.send |
<address>.call |
someAddress.call.
value(1ether)
.gas(100000) ("register", "MyName")
另一个合约的执行代码,在失败的情况下返回 false,转发所有可用的 gas,可调节,当您需要控制要转发多少 gas 时应该使用。 |
---|
<address>.delegatecall |
固定大小的字节数组(bytes1、bytes2、…、bytes32) |
动态大小的数组 bytes string |
/**bytes array **/
bytes32[] dynamicArray
function f() {
bytes32[] storage storageArr = dynamicArray
storageArr.length++;
}
/**string array **/
bytes32[] public names
Solidity 支持动态大小的字节数组和动态大小的 UTF-8 编码字符串。 |
---|
十六进制字面量 |
地址字面量 |
字符串字面量 |
枚举类型
枚举是一种带有一组受限制的常量值的类型。以下是示例:
pragma solidity ⁰.4.24;
contract ColorEnum {
enum Color {RED,ORANGE,YELLOW, GREEN}
Color color;
function construct() public {
color = Color.RED;
}
function setColor(uint _value) public {
color = Color(_value);
}
function getColor() public view returns (uint){
return uint(color);
}
}
结构体类型
结构体是一种包含命名字段的类型。可以使用结构体声明新类型。以下是代码示例:
struct person {
uint age;
string fName;
string lName;
string email;
}
映射
映射充当由键类型和相应值类型对组成的哈希表。以下是示例:
pragma solidity ⁰.4.24;
contract StudentScore {
struct Student {
uint score;
string name;
}
mapping (address => Student) studtents;
address[] public studentAccts;
function setStudent(address _address, uint _score, string _name) public {
Student storage studtent = studtents[_address];
studtent.score = _score;
studtent.name = _name;
studentAccts.push(_address) -1;
}
function getStudents() view public returns(address[]) {
return studentAccts;
}
function getStudent(address _address) view public returns (uint, string) {
return (studtents[_address].score, studtents[_address].name);
}
function countStudents() view public returns (uint) {
return studentAccts.length;
}
}
函数
函数是合同内代码的可执行单元。以下是 Solidity 中函数的结构:
function (<Input parameters>) {access modifiers} [pure|constant|view|payable] [returns (<return types>)]
输入参数
函数可以传递输入参数。输入参数的声明方式与变量相同。
在上一个HelloWorld
示例中,我们使用输入参数string _newGreeting
定义了setNewGreeting
。以下是此步骤的示例:
function setNewGreeting (string _newGreeting) {
greeting = _newGreeting;
}
访问修饰符
Solidity 访问修饰符用于在 Solidity 中提供访问控制。
Solidity 中有四种类型的访问修饰符,列举如下:
-
Public:可以从此合同、继承的合同和外部访问
-
Private:只能从此合同访问
-
Internal:只能从此合同和继承自它的合同访问
-
External:无法在内部访问,仅可外部访问
输出参数
输出参数可以在return
关键字之后声明,如下所示的代码片段:
function getColor() public view returns (uint){
return uint(color);
}
在 Solidity 中,pure
函数是承诺不修改或读取状态的函数。
pure|constant|view|payable
如果函数修饰符定义为 view,则表示该函数不会更改存储状态。
如果函数修饰符定义为 pure,则表示该函数不会读取存储状态。
如果函数修饰符定义为 constant,则表示该函数不会修改合同存储。
如果函数修饰符定义为 payable,则修饰符可以接收资金。
uint amount =0;
function buy() public payable{
amount += msg.value;
}
在上面的示例中,buy
函数具有可支付修饰符,这确保您可以向buy
函数发送以太。没有任何名称,并用可支付关键字注释的函数称为可支付回退函数。
pragma solidity ⁰.4.24;
// this is a contract, which keeps all Ether to it with not way of
// retrieving it.
contract MyContract {
function() public payable { }
}
修饰符
在 Solidity 中,修饰符用于更改函数的行为。它们可以在执行函数之前自动检查条件。以下是示例:
pragma solidity ⁰.4.24;
contract Modifiers {
address public admin;
function construct () public {
admin = msg.sender;
}
//define the modifiers
modifier onlyAdmin() {
// if a condition is not met then throw an exception
if (msg.sender != admin) revert();
// or else just continue executing the function
_;
}
// apply modifiers
function kill() onlyAdmin public {
selfdestruct(admin);
}
}
事件
事件用于跟踪发送到合同的交易的执行情况。有与 EVM 日志记录设施方便的接口。以下是示例:
pragma solidity ⁰.4.24;
contract Purchase {
event buyEvent(address bidder, uint amount); // Event
function buy() public payable {
emit buyEvent(msg.sender, msg.value); // Triggering event
}
}
构造函数
构造函数是创建和初始化合同的特殊方法。在 Solidity v0.4.23 中,Solidity 引入了这种新的构造函数表示法,旧的构造函数已被弃用。
//new
pragma solidity ⁰.4.24;
contract HelloWorld {
function constructor() public {
// ...
}
}
//deprecated
pragma solidity ⁰.4.22;
contract HelloWorld {
function HelloWorld () public {
// ...
}
}
常量状态变量、单位和函数
常量的值不能通过重新分配来更改,并且在编译时间后不能重新声明。在 solidity 中,状态变量可以声明为常量。它不允许重新分配到区块链数据(例如,this.balance
,block.blockhash
)或执行数据(tx.gasprice
),或调用外部合约。
下表列出了 solidity 全局变量及其内建函数:
全局变量 / 函数 | 描述 |
---|---|
msg.sender(address) | msg.sender 是当前与合约调用消息交互的地址。 |
msg.data(bytes) | msg.data 是当前与合约进行交互的地址的完整调用。数据以字节形式呈现。 |
msg.value(unit) | msg.value 是当前与合约交互的地址,根据合约发送的消息的 Wei 数量。 |
msg.sig | msg.sig 是当前与合约交互的地址,返回调用数据的前四个字节。 |
gasleft() returns (uint256) | API 用于检查剩余的燃气。 |
tx.origin | API 用于检查交易的发送方。 |
tx.gasprice | API 用于检查交易的燃气价格。 |
now | 获取当前的 Unix 时间戳。 |
block.number | API 用于检索当前区块编号。 |
block.difficulty | API 用于检索当前区块的难度。 |
block.blockhash(uint blockNumber) returns (bytes32) | API 用于获取给定区块的哈希;结果仅返回最近的 256 个区块。 |
block.gasLimit(unit) | API 用于获取当前区块的燃气限制。 |
block.coinbase () | 返回当前区块矿工的地址。 |
keccak256(...); | 返回(bytes32):计算(紧密打包的)参数的 Ethereum-SHA-3(Keccak-256)哈希。 |
sha3(...) | 返回(bytes32):keccak256 的别名。 |
assert(bool condition) | assert 用于检查条件。它表示在任何情况下都不应该为假的东西。此外,assert 使用 0xfe 操作码来引发错误条件。 |
require(bool condition) | 应该使用 require 函数来确保有效的条件。当用户输入不当的内容时,它可能返回 false。此外,require() 使用 0xfd 操作码来引发错误条件。 |
revert() | revert 仍然会撤消所有状态更改。 |
<address>.balance | 它检查地址的 Wei 余额(uint256)。 |
<address>.send(uint256 amount) returns (bool) | API 将 Wei 量发送到地址,并在失败时返回 false。 |
<address>.transfer(uint256 amount) | API 将 Wei 量转移到地址,并在转移失败时抛出错误。 |
this | 当前合约,显式转换为地址。 |
super | 继承层次结构中更高一级的合约。 |
selfdestruct(address recipient) | self-destruct 将销毁当前合约,并从以太坊的世界状态中删除与之相关的存储。 |
suicide(address recipient) | self-destruct 的别名。 |
以太单位
Solidity 中的以太可分为 Wei、Kwei、Mwei、Gwei、Szabo、Finney、Kether、Mether、Gether 和 Tether。以下是转换单位:
-
1 ether = 1,000 Finney
-
1 Finney = 1,000 Szabo
-
1 Szabo = 1,000 Mwei
-
1 Mwei = 1,000 Kwei
-
1 Kwei = 1,000 Wei
时间单位
一个 Solidity 时间单位可分为秒、分钟、小时、天、周和年。以下是转换单位:
-
1 = 1 秒
-
1 分钟 = 60 秒
-
1 小时 = 60 分钟
-
1 天 = 24 小时
-
1 周 = 7 天
-
1 年 = 365 天
继承、抽象和接口
许多最广泛使用的编程语言(如 C++、Java、Go 和 Python 等)支持面向对象编程(OOP),并支持继承、封装、抽象和多态。继承使代码重用和可扩展性成为可能。Solidity 支持通过复制代码的方式实现多重继承,其中包括多态性。即使合约从多个其他合约继承,也只会在区块链上创建一个单一合约。
在 Solidity 中,继承与经典的面向对象编程语言非常相似。以下是一些示例:
pragma solidity ⁰.4.24;
contract Animal {
constructor() public {
}
function name() public returns (string) {
return "Animal";
}
function color() public returns (string);
}
contract Mammal is Animal {
int size;
constructor() public {
}
function name() public returns (string) {
return "Mammal";
}
function run() public pure returns (int) {
return 10;
}
function color() public returns (string);
}
contract Dog is Mammal {
function name() public returns (string) {
return "Dog";
}
function color() public returns (string) {
return "black";
}
}
Dog 从 Mammal
继承,其父合约是 Animal
。调用 Dog.run()
时,将调用其父方法 run()
并返回十。调用 name,Dog.name()
将覆盖其父方法并返回 Dog
的输出。
在 Solidity 中,没有主体(无实现)的方法称为抽象方法。包含抽象方法的合约无法实例化,但可以用作基类。
如果一个合约从抽象合约继承,那么合约必须实现抽象父类的所有抽象方法,或者它也必须声明为抽象。
Dog 有一个具体的 color()
方法,这是一个具体的合约,可以编译,但父合约—mammal,和祖父合约—animal,仍然是抽象合约。
Solidity 中的接口类似于抽象合约;它们隐式地是抽象的,不能有实现。抽象合约可以具有实例方法,实现默认行为。接口中有更多的限制,如下所示:
-
不能继承其他合约或接口
-
不能定义构造函数
-
不能定义变量
-
不能定义结构体
-
不能定义枚举
pragma solidity ⁰.4.24;
//interface
contract A {
function doSomething() public returns (string);
}
//contract implements interface A
contract B is A {
function doSomething() public returns (string) {
return "Hello";
}
}
在前面的示例中,合约是一个接口,contract B
实现了 interface A
,并且有一个具体的 doSomething()
方法。
常见的智能合约模式
在本节中,我们将讨论智能合约编程语言的一些常见设计和编程模式。
访问限制
限制访问是一种 solidity 安全模式。它只允许授权方访问特定功能。由于区块链的公开性质,区块链上的所有数据对任何人都是可见的。关键是声明你的合约函数、受限访问控制的状态,并提供对智能合约功能的未经授权访问的安全性。
pragma solidity ⁰.4.24;
contract Ownable {
address owner;
uint public initTime = now;
constructor() public {
owner = msg.sender;
}
//check if the caller is the owner of the contract
modifier onlyOwner {
require(msg.sender == owner,"Only Owner Allowed." );
_;
}
//change the owner of the contract
//@param _newOwner the address of the new owner of the contract.
function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}
function getOwner() internal constant returns (address) {
return owner;
}
modifier onlyAfter(uint _time) {
require(now >= _time,"Function called too early.");
_;
}
modifier costs(uint _amount) {
require(msg.value >= _amount,"Not enough Ether provided." );
_;
if (msg.value > _amount)
msg.sender.transfer(msg.value - _amount);
}
}
contract SampleContarct is Ownable {
mapping(bytes32 => uint) myStorage;
constructor() public {
}
function getValue(bytes32 record) constant public returns (uint) {
return myStorage[record];
}
function setValue(bytes32 record, uint value) public onlyOwner {
myStorage[record] = value;
}
function forceOwnerChange(address _newOwner) public payable
onlyOwner onlyAfter(initTime + 2 weeks) costs(50 ether) {
owner =_newOwner;
initTime = now;
}
}
上述示例展示了访问限制模式应用于合约。我们首先定义了一个名为Ownable
的父类,其中包含onlyOwner
、changeOwner
和onlyAfter
函数修饰符。其他合约可以继承自此合约以使用定义的访问限制。SampleContract
继承自Ownable
合约,因此只有所有者可以访问setValue
函数。此外,forceOwnerChange
只能在合同创建后两周内以 50 以太币的成本进行调用,只有所有者才有权限执行该功能。
状态机
状态机是一种行为设计模式。它允许合约在其内部状态改变时改变其行为。智能合约函数调用通常会将合约状态从一个阶段移动到下一个阶段。状态机的基本操作包括两个部分:
-
它遍历一系列状态,其中下一个状态由当前状态和输入条件决定。
-
它根据状态迁移提供输出序列。
为了说明这一点,让我们开发一个简单的状态机。我们以洗碗为例。通常的过程是擦洗,冲洗,晾干,擦洗,冲洗,晾干。我们将状态机阶段定义为一个枚举类型。由于这是一个广泛的用例,此处仅介绍了与状态机相关的代码。省略了任何详细动作实现的逻辑,如rinse()
、dry()
等。请参见以下示例:
pragma solidity ⁰.4.24;
contract StateMachine {
enum Stages {
INIT,
SCRUB,
RINSE,
DRY,
CLEANUP
}
Stages public stage = Stages.INIT;
modifier atStage(Stages _stage) {
require(stage == _stage);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
modifier transitionNext() {
_;
nextStage();
}
function scrub() public atStage(Stages.INIT) transitionNext {
// Implement scrub logic here
}
function rinse() public atStage(Stages.SCRUB) transitionNext {
// Implement rinse logic here
}
function dry() public atStage(Stages.SCRUB) transitionNext {
// Implement dry logic here
}
function cleanup() public view atStage(Stages.CLEANUP) {
// Implement dishes cleanup
}
}
我们定义了函数修饰符atStage
来检查当前状态是否允许该阶段运行函数。此外,transitionNext
修饰符将调用内部方法nextStage()
来将状态移动到下一个阶段。
智能合约安全
一旦智能合约部署在以太坊网络上,它就是不可变的,并且对所有人都是公开的。许多智能合约功能与账户支付相关;因此,在部署到主网络之前,安全性和测试对合约都至关重要。以下是有助于您更好地设计和编写无瑕疵以太坊智能合约的安全实践。
保持合约简单和模块化
尽量保持您的智能合约小、简单和模块化。复杂的代码很难阅读、理解和调试,并且容易出错。
在可能的情况下,使用写好的库工具。
限制本地变量的数量。
将不相关功能移动到其他合约或库中。
使用检查-效果-交互模式
与其他外部合约交互时要非常小心,这应该是你的函数中的最后一步。它可能引入几个意外的风险或错误。外部调用可能执行恶意代码。如果可能的话,应该考虑这些调用作为潜在的安全风险,并尽量避免。
pragma solidity ⁰.4.24;
// THIS CONTRACT is INSECURE - DO NOT USE
contract Fund {
mapping(address => uint) userBalances;
function withdrawBalance() public {
//external call
if (msg.sender.call.value(userBalances[msg.sender])())
userBalances[msg.sender] = 0;
}
}
contract Hacker {
Fund f;
uint public count;
event LogWithdrawFallback(uint c, uint balance);
function Attacker(address vulnerable) public {
f = Fund(vulnerable);
}
function attack() public {
f.withdrawBalance();
}
function () public payable {
count++;
emit LogWithdrawFallback(count, address(f).balance);
if (count < 10) {
f.withdrawBalance();
}
}
}
}
行msg.sender.call.value(userBalances[msg.sender])
是一个外部调用,在调用withdrawBalance
时,它将使用address.call.value()
发送以太。黑客可以通过触发攻击回退函数来攻击资金合约,这将再次调用withdrawBalance
方法。这将允许攻击者多次退款,耗尽账户中的所有以太。
上述合约漏洞称为递归调用。为了避免这种情况,您可以使用检查-效果-交互模式,如下面的示例所示:
pragma solidity ⁰.4.24;
contract Fund {
mapping(address => uint) userBalances;
funct
ion withdrawBalance() public {
uint amt = userBalances[msg.sender];
userBalances[msg.sender] =0;
msg.sender.transfer(amt);
}
}
我们首先需要确定函数的哪一部分涉及到外部调用,uint amt = userBalances[msg.sender]; userBalances[msg.sender] =0;
。
该函数读取 userBalances
的值,并将其分配给一个本地变量,然后重置 userBalances
。这些步骤是为了确保消息发送者只能向自己的帐户转账,但不能对状态变量进行任何更改。在实际转账到用户之前,用户的余额将减少。如果在转账过程中发生任何错误,整个交易将被撤销,包括状态变量中余额减少的转账金额。这种方法可以被描述为 乐观会计,因为在实际发生之前,效果已被写入完成。
用区块燃气限制的 DoS 攻击
以太坊区块链交易由于区块燃气限制只能处理一定数量的燃气,因此要小心观察是否有固定的限制集成。当一系列迭代的成本超出燃气限制时,交易将失败,并且合约可能会在某一点被停滞。在这种情况下,攻击者可能会攻击合约,并操纵燃气。
处理外部调用中的错误
正如我们之前讨论的,Solidity 有一些低级调用方法:address.call()
、address.callcode()
、address.delegatecall()
和 address.send()
。这些方法只在调用遇到异常时返回 false。因此,在合约中处理外部调用的错误非常重要,如下面的代码片段所示:
// good
if(!contractAddress.send(100)) {
// handle error
}
contractAddress.send(20);//don't do this
contractAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result
contractAddress.call.value(50)(bytes4(sha3("withdraw()"))); // if withdraw throws an exception, the raw call() will only return false and transaction will NOT be reverted
案例研究 - 众筹活动
在本节中,我们将为众筹活动使用案例实现并部署智能合约。
众筹的理念是从大众集资为项目或风险创业筹集资金的过程。投资者会收到代表他们投资创业公司股份的代币。项目设定了一个预定的目标和达到该目标的期限。一旦项目未达到目标,资金将被返还,这减少了投资者的风险。这种去中心化的筹资模式可以取代初创企业的资金需求,并且无需集中式的受信任平台。投资者只会在资金返还时支付 gas 费用。任何项目的贡献者都会得到一个代币,他们可以交易、出售或保留这些代币。在某一阶段,代币可以用来换取实际产品作为物理奖励。
定义结构和事件,如下所示:
pragma solidity ⁰.4.24;
contract CrowdFunding {
Project public project;
Contribution[] public contributions;
//Campaign Status
enum Status {
Fundraising,
Fail,
Successful
}
event LogProjectInitialized (
address owner,
string name,
string website,
uint minimumToRaise,
uint duration
);
event ProjectSubmitted(address addr, string name, string url, bool initialized);
event LogFundingReceived(address addr, uint amount, uint currentTotal);
event LogProjectPaid(address projectAddr, uint amount, Status status);
event Refund(address _to, uint amount);
event LogErr (address addr, uint amount);
//campaign contributors
struct Contribution {
address addr;
uint amount;
}
//define project
struct Project {
address addr;
string name;
string website;
uint totalRaised;
uint minimumToRaise;
uint currentBalance;
uint deadline;
uint completeAt;
Status status;
}
//initialized project
constructor (address _owner, uint _minimumToRaise, uint _durationProjects,
string _name, string _website) public payable {
uint minimumToRaise = _minimumToRaise * 1 ether; //convert to wei
uint deadlineProjects = now + _durationProjects* 1 seconds;
project = Project(_owner, _name, _website, 0, minimumToRaise, 0, deadlineProjects, 0, Status.Fundraising);
emit LogProjectInitialized(
_owner,
_name,
_website,
_minimumToRaise,
_durationProjects);
}
定义修饰词,如下代码所示:
//check if project is at the required stage
modifier atStage(Status _status) {
require(project.status == _status,"Only matched status allowed." );
_;
}
//check if msg.sender is project owner
modifier onlyOwner() {
require(project.addr == msg.sender,"Only Owner Allowed." );
_;
}
//check if project pass the deadline
modifier afterDeadline() {
require(now >= project.deadline);
_;
}
//Wait for 6 hour after campaign completed before allowing contract destruction
modifier atEndOfCampain() {
require(!((project.status == Status.Fail || project.status == Status.Successful) && project.completeAt + 6 hours < now));
_;
}
定义智能合约函数,如下所示:
function () public payable {
revert();
}
/* The default fallback function is called whenever anyone sends funds to a contract */
function fund() public atStage(Status.Fundraising) payable {
contributions.push(
Contribution({
addr: msg.sender,
amount: msg.value
})
);
project.totalRaised += msg.value;
project.currentBalance = project.totalRaised;
emit LogFundingReceived(msg.sender, msg.value, project.totalRaised);
}
//checks if the goal or time limit has been reached and ends the campaign
function checkGoalReached() public onlyOwner afterDeadline {
require(project.status != Status.Successful && project.status!=Status.Fail);
if (project.totalRaised > project.minimumToRaise){
project.addr.transfer(project.totalRaised);
project.status = Status.Successful;
emit LogProjectPaid(project.addr, project.totalRaised, project.status);
} else {
project.status = Status.Fail;
for (uint i = 0; i < contributions.length; ++i) {
uint amountToRefund = contributions[i].amount;
contributions[i].amount = 0;
if(!contributions[i].addr.send(contributions[i].amount)) {
contributions[i].amount = amountToRefund;
emit LogErr(contributions[i].addr, contributions[i].amount);
revert();
} else{
project.totalRaised -= amountToRefund;
project.currentBalance = project.totalRaised;
emit Refund(contributions[i].addr, contributions[i].amount);
}
}
}
project.completeAt = now;
}
function destroy() public onlyOwner atEndOfCampain {
selfdestruct(msg.sender);
}
}
让我们使用 Remix 来测试我们的活动。我们选择 JavaScript VM 选项。
- 通过点击以下输入的 Deploy 按钮来初始化活动。这将通过构造函数启动我们的活动。我们将第一个账户指定为项目所有者。最低筹资为 30 以太币,截止期限设置为五分钟用于测试目的。将以下输入代码放入 Deploy 按钮旁边的文本框中。以下是构造函数的输入参数:
0xca35b7d915458ef540ade6068dfe2f44e8fa733c, 30, 100, "smartchart",
"smartchart.tech"
以下是该步骤 Remix 编辑器屏幕的截图:
Remix 编辑器屏幕
- 切换到第二个账户,在 Remix 的价值输入字段中输入
20
以太币,然后点击(回退)按钮。这将向 totalRaised 添加20
以太币。要查看项目信息,点击项目按钮,你应该看到 totalRaised 现在是 20 以太币。在捐款输入字段中输入0
以太币,我们可以看到第二个账户的捐款地址和金额为 20 以太币:
Remix 的价值输入字段
- 切换到第三个账户,在价值字段中输入
15
以太币,为项目增加资金。点击(回退)按钮,我们可以看到项目的总筹款量已经达到了 35 以太币。此时,项目已经实现了筹款目标:
为项目增加资金
- 切换回项目所有者,即第一个账户,并点击 checkGoalReached。我们可以看到交易已成功执行。在日志中,项目状态已更新为"成功"。
LogProjectPaid
被触发。如果我们检查 Remix 账户 1、2、3,项目所有者账户现在包含大约 135 以太币。我们的活动智能合约在 Remix 中经过了成功的测试:
成功测试了智能合约中的活动
摘要
在本章中,我们学习了 Solidity 编程的基本特性。我们还概述了当前流行的智能合约开发工具。通过探索常见模式和安全最佳实践,我们学会了如何编写更好的代码,以避免合约漏洞。最后,我们编写了一个众筹合约,并使用 Remix 部署和测试了我们的示例。
在下一章中,我们将构建一个去中心化应用程序(DApp)用于众筹。