区块链正在迅速增长,并改变着商业的运作方式。领先的组织已经在探索区块链的可能性。通过本书,你将学会如何构建端到端的企业级去中心化应用程序(DApps)并在组织中扩展它们以满足公司的需求。
本书将帮助你了解什么是DApps以及区块链生态系统的运作方式,并通过一些实际的例子来说明。这是一本全面的端到端书籍,涵盖了区块链的各个方面,例如它对于企业和开发人员的应用。它将帮助你了解流程,以便你可以将其纳入到你自己的企业中。你将学会如何使用J.P.摩根的Quorum构建基于区块链的应用程序。你还将介绍如何编写能够在企业区块链解决方案中通信的应用程序。你将学会编写无需审查和第三方干预即可运行的智能合约。
一旦你对区块链有了很好的理解,并学习了关于Quorum的一切,你就会开始构建真实世界的区块链应用,应用于支付和货币转移、医疗保健、云计算、供应链管理等领域。
本书适合创新者、数字化变革者和想要使用区块链技术构建端到端DApps的区块链开发人员。如果你想要在企业范围内扩展你现有的区块链系统,你也会发现这本书很有用。它为你提供了解决企业实际问题所需的实用方法,结合了理论和实践的方法。
你必须具备JavaScript和Python编程语言的使用经验。
你必须之前开发过分布式网络应用。
你必须了解基本的加密概念,如签名、加密和哈希。
你可以通过以下步骤下载代码文件:
下载文件后,请确保使用最新版本的解压缩软件解压缩文件夹:
在本书中使用了许多文本约定。
CodeInText:表示文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟URL,用户输入和Twitter句柄。例如:“在使用raft.addPeer添加节点时,将出现此RaftID。”
代码块设置如下:
警告或重要说明会出现在这里。
提示和技巧会出现在这里。
从互联网诞生以来,所有开发的基于互联网的应用程序都基于客户端-服务器架构,其中有一个中心化的服务器构成应用程序的后端并控制整个应用程序。这些应用程序经常出现一系列问题,比如存在单一故障点,无法阻止网络审查,缺乏透明度,用户不相信他们的数据、活动和身份隐私等。这种中心化的架构甚至使构建某种应用程序成为不可能。例如,你不能使用这种架构构建数字货币。由于这些问题,出现了一种新的架构类型,称为去中心化应用程序(DApps)。在本章中,我们将学习关于DApps的知识。
在本章中,我们将涵盖以下主题:
DApp是一种其后端运行在去中心化对等网络上,并且其源代码是开源的应用程序。网络中没有一个单一的节点完全控制DApp。记住,当我们说一个应用程序是去中心化的,我们指的是技术上是去中心化的,但治理可以是分布式的、去中心化的或中心化的。
区块链应用程序(DApps)的主要优势在于它们没有单一的故障点,并且可以防止审查。DApps确实有一些缺点:一旦部署,很难修复错误或添加功能,因为网络中的每个人都必须更新他们的节点软件,而且将不同的DApps耦合在一起非常复杂,因为与集中式应用程序相比,它们很难构建,并涉及非常复杂的协议。
要使用DApp,您首先需要运行DApp的节点服务器,以便您可以连接到对等网络。然后,您需要一个与DApp对应的客户端,该客户端连接到节点服务器并公开用于使用DApp的UI或命令行界面。
目前,DApps在性能和可扩展性方面还没有像集中式应用程序那样成熟。在这些主题上仍然存在大量研究和开发,如性能、可扩展性、用户身份、隐私、DApp之间的通信、数据冗余等。一个用例可能适用于DApp,但基于当前可用技术使用例可以投入生产可能是一个挑战。去中心化应用程序的流行示例包括Torrent,Bitcoin,Ethereum,Quorum等。
一个DApp可以是公开的或许可的。公开的DApp是任何人都可以参与的,换句话说,它们是无需许可的,而许可的DApp是不是每个人都能加入的,所以你需要许可才能加入。当DApp的参与者是企业和/或政府实体时,许可的DApp被称为联盟DApp。同样地,当许可的DApp的参与者只是企业时,我们可以称之为企业DApp。在这本书中,我们将学习关于许可的DApp的一切。
你刚刚对分布式应用有了基本介绍,你一定想知道分布式应用和去中心化应用的区别是什么。好吧,当一个应用分布在多个服务器上时,就说这个应用是分布式的。去中心化应用默认情况下是分布式的,而中心化应用可能是分布式的也可能不是。中心化应用通常分布在多个服务器上,以防止停机,并处理大量数据和流量。
在我们讨论什么之前,我们需要理解什么是账本。在计算机科学中,账本是存储交易的软件。数据库与账本不同,因为在数据库中我们可以添加、删除和修改记录,而在账本中我们只能追加而不能删除或修改。
区块链基本上是一个实现去中心化账本的数据结构。区块链是相互连接的块链。每个块包含一系列交易和某些其他元数据,比如它是何时创建的,它的前一个块是什么,块号是多少,谁是块的创建者等等。每个块都维护前一个块的哈希值,因此创建了相互链接的块链。网络中的每个节点都应该保存完整的区块链副本,当一个新节点加入时,它将从其他节点请求并下载区块链。
像区块链这样的技术被称为分布式账本技术(DLT)。DLT是在许多地点、国家和/或机构之间复制、共享和同步数字交易的过程。你可以将区块链看作是一种DLT。此外,并不是每个DLT系统都必须是去中心化的。在这本书中,我们只学习构建基于区块链的去中心化应用。
在现实世界中,私有区块链被用于贸易金融、跨境支付、数字身份、代币化和数字资产的结算与清算、产品所有权溯源、关键数据记录、签订合同、多方汇总(即它们可以用作共享的行业信息主库,允许成员查询数据)、支付与支付或支付与交付等等。
每个区块链节点维护一个包含区块链状态的数据库。状态包含运行区块链中所有交易的最终结果。例如,在区块链中,状态表示所有地址的最终余额。因此,当您查询区块链节点的地址余额时,它不必浏览所有交易并计算地址的最终余额;相反,它直接从区块链的状态中获取余额。比特币使用LevelDB来维护区块链的状态。即使数据库损坏,也可以通过简单地运行区块链中的所有交易来恢复数据库。
拜占庭容错(BFT)是分布式系统的一个特性,表示它能容忍拜占庭失败。崩溃故障是指节点停止做任何事情(根本没有消息),而拜占庭失败是指节点根本不做任何事情或展示任意行为。基本上,拜占庭失败包括崩溃故障。
在任何使用区块链数据结构的去中心化计算环境中,都存在一个或多个恶意或不可靠参与者可能导致环境解散的风险。如果服务器集群中的几台服务器不能以一致的方式传递数据给其他服务器,那么服务器集群将无法正常工作。为了可靠,去中心化计算环境必须以一种方式设计,即它有解决这类拜占庭失败的解决方案。
在基于区块链的去中心化应用中,按定义没有中央权威,因此使用一种称为共识协议的特殊协议来实现BFT。
简单来说,你一定想知道如何确保每个人都有相同的区块链副本,以及当两个节点发布不同的区块链时如何确定哪个区块链是正确的?此外,在分布式架构中没有主节点的情况下,如何决定谁创建区块?好吧,共识协议为这些问题提供了答案。共识协议的几个例子包括工作量证明(PoW)、权益证明(PoS)、权威证明(PoA)、PBFT等等。
共识协议专为许可或公共区块链设计。为公共区块链设计的共识协议在实现为许可区块链时可能会产生安全性和性能问题。每个共识协议都有不同的性能和可扩展性向量。在为基于区块链的DApp选择共识协议时必须保持警惕。
Raft和Paxos等共识协议不是BFT;相反,它们只是使系统具有崩溃容错性。因此,在选择共识协议时,您也应考虑这一点。
您可能已经听说过PoA这个术语。PoA是共识协议的一种分类,在其中有一组权限节点,这些节点明确被允许创建新的区块并保护区块链。Ripple的迭代过程,PBFT,Clique,Aura等都是基于PoA的共识协议的例子。
在基于区块链的应用程序中,用户账户使用非对称密钥对进行标识和认证。私钥用于代表用户签署交易。基于用户名和密码的账户系统在区块链上无法工作,因为它无法证明哪个用户发送了交易。使用私钥-公钥对的缺点包括它们不用户友好,如果你丢失了私钥则无法恢复它。因此,它为用户增加了一个新的责任,即保护他们的私钥。用户账户的地址在区块链上充当账户标识符。用户账户的地址是由公钥派生的。
一些区块链应用程序使用UTXO模型进行交易。比特币和MultiChain等区块链应用程序使用此模型。即使像R3Corda这样的分布式账本技术也使用此模型。让我们通过理解比特币交易的工作方式来理解这个模型。
在比特币中,一个交易是零个或多个输入和输出的集合。这些输入和输出对象被称为未使用的交易输出(UTXO)。交易的输出被用作未来交易的输入。一个UTXO只能被使用一次。在比特币中,每个UTXO包含一个面额和一个所有者(比特币地址)。在这个模型中,未使用的UTXO中的地址余额被存储。要使交易有效,必须满足以下要求:
用户的余额被计算为他们拥有的UTXO的面额总和。交易可以消耗零个或多个UTXO并产生零个或多个UTXO。为了向矿工支付奖励,它在区块中包含一个交易,该交易消耗零个UTXO,但产生一个UTXO,其面额分配给它应该授予自己的比特币数量。
当区块链交易涉及资产转移时,UTXO事务模型是合适的,但对于无资产转移交易(如记录事实、调用智能合约等),这种模型就不合适了。
现在,我们已经基本了解了什么是DApp、区块链和DLT,让我们来概述一下可用于构建许可制区块链应用程序和DApp的平台。我们只会介绍市场上流行的、有需求的平台。
主要的以太坊公共网络使用PoW进行共识。如果您想部署自己的私有以太坊网络,那么您必须使用PoA。PoW需要大量的计算能力来保护区块链的安全,因此适用于公共区块链使用,而PoA则没有任何这样的计算能力要求;相反,它需要网络中的少数权威节点来达成共识。
你一定在想为什么我们需要智能合约来构建DApp。为什么不能简单地在区块链上放置格式化消息,以交易的形式在客户端上解释它们呢?好吧,使用智能合约可以为你带来技术和商业上的双重利益。
Quorum是一个分散的平台,允许我们在其上构建基于许可制区块链的DApp。实际上,Quorum是以太坊的一个分叉(实际上Quorum是以太坊的一个分叉,使用Golang实现的以太坊的一个实现),因此如果你曾经在以太坊上工作过,那么你将会发现学习并使用Quorum来构建许可制区块链是很容易的。许多企业选择Quorum来构建区块链,因为以太坊拥有庞大的社区,这使得找到以太坊开发人员变得容易。Quorum与以太坊的不同之处在于,它支持隐私(让各方可以私下进行交易)、对等节点白名单,因此可以指定允许连接到您的节点的其他节点列表(在以太坊中,这需要在网络级别完成),适用于许可制区块链的许多不同类型的共识协议,并提供非常高的性能。
Quorum目前支持三种共识协议,QuorumChain、IBFT和Raft。在本书中,我们将跳过QuorumChain,因为Raft和IBFT满足了我们的所有需求。
MicrosoftAzure提供了BaaS,可以在云上轻松构建自己的Quorum网络。但是,在本书中,我们将学习如何手动安装,并不会使用BaaS。
以太坊的流行节点软件包括GoEthereum、以太坊C++和Parity。Parity还支持两种与以太坊的PoW不同的共识协议,专为权限区块链设计。这些共识协议是Aura和Tendermint。许多以太坊开发者在不需要Quorum提供的额外功能时,会使用Parity而不是Quorum。
由于Parity与Quorum没有提供任何独特的功能,所以我们将在本书中跳过Parity。但是,一旦你完成了这本书,你会发现很容易掌握Parity的概念,并且也能够利用它构建一些东西。
MultiChain是一个构建基于权限的区块链DApps的平台。MultiChain的独特功能包括权限管理、数据流和资产。它不支持智能合约。这是一个构建基于区块链的DApps的非智能合约平台的例子。MultiChain使用循环验证共识。
最初,MultiChain的理念是在区块链上管理资产的所有权和转移。对资产的操作包括发行、再发行、转移、原子交换、托管和销毁。后来,数据流被引入以提供在MultiChain中表示数据的不同方式。在MultiChain中可以创建任意数量的流,每个流都是独立的追加集合。关于流的操作包括创建流、写入、订阅、索引和检索。因此,基本上,MultiChain上的区块链用例可以建立在资产或流的基础上。最后,权限管理用于控制谁能连接、交易、创建资产/流、挖掘/验证和管理。
MultiChain提供与比特币生态系统最大的兼容性,包括点对点协议、交易/区块格式、UTXO模型和比特币核心API/运行时参数。因此,在开始学习MultiChain之前,最好至少了解比特币的工作原理。
HyperledgerFabric是Hyperledger下最受欢迎的项目。IBM是该项目的主要贡献者。IBM的Bluemix还提供了在云上构建自己的Fabric网络的BaaS服务。
HyperledgerFabric1.0是一个构建自己的基于区块链的权限管理应用的平台。当前,在撰写本书时,HyperledgerFabric1.0仅支持分布式架构,并且对于区块的创建,它依赖于一个名为orderer的中心可信节点。它支持智能合约、网络许可、隐私和其他功能。在HLF1.0中,有一种特殊的节点称为OSN,由可信方托管。这个OSN创建区块并分发给网络中的对等方。由于信任这个节点,没有必要进行共识。HLF1.0目前支持CouchDB和LevelDB来存储区块链的状态。网络中的对等方默认在LevelDB数据库中存储区块链的状态。
HLF1.0通过频道的概念实现隐私。频道是网络中的子区块链,并且允许根据配置让某些参与方成为某个频道的一部分。实际上,每个交易都必须属于一个频道,当部署HLF1.0网络时,默认会创建一个默认频道。OSN可以看到所有频道中的所有数据,因此它应该是一个可信的方。从技术上讲,如果你不能信任单个方处理所有频道,可以配置网络来拥有托管不同频道的多个OSN。即使流量庞大或者OSN的可用性至关重要,也可以将Kafka连接到OSN以获得更好的性能和更高的稳定性。如果需要高可用性,我们甚至可以每个频道连接多个经过Kafka连接的OSN。
此外,同一频道的对等方无论是否存在OSN,都会向彼此广播区块,但在缺乏OSN的情况下,无法为频道创建新的区块。对等方使用称为gossip数据传播协议的特殊协议广播区块。
HLF1.0拥有非常先进的成员功能,可以控制网络成员,并且也内部使用于特定组织。在HLF1.0中,您可以用Java或Go编程语言编写链码。将来,Fabric1.0将配备SimpleByzantineFaultTolerance(SBFT)共识协议和其他一些功能,这将使我们能够构建DApps。同样,还有各种新功能正在开发中,并将作为产品的子版本在将来发布。
BigchainDB是一个使用区块链的分布式数据库。BigchainDB具有高度可扩展性和可定制性。它使用区块链数据结构。它支持诸如丰富的权限、PB级容量、高级查询、线性扩展等功能。在编写本书时,BigchainDB尚未达到生产就绪,但可用于构建概念验证(PoCs)。我们将在后面的章节中学习它的工作原理,并使用它创建一个基本的PoC。
星际文件系统(IPFS)是一个分布式文件系统。IPFS使用分布式哈希表(DHT)和Merkle有向无环图(DAG)数据结构。它使用类似于Torrent的协议来决定如何在网络中移动数据。IPFS的一个高级功能是它支持文件版本控制。为了实现文件版本控制,它使用类似于Git的数据结构。
尽管它被称为分布式文件系统,但它并不遵循文件系统的一个主要属性,即,当我们将某物存储在文件系统中时,应该一直存在,直到删除。但是,IPFS的工作方式并不是这样的。每个节点并不存储所有文件,而是仅存储它需要的文件。因此,如果一个文件不受欢迎,那么许多节点将不会拥有该文件,因此文件在网络中消失的可能性很大。由于这个原因,我们可以称IPFS为分布式点对点文件共享应用程序。我们将在后面的章节中了解它的工作方式。
Corda是一个用于构建自己的基于权限的DLT应用程序的平台。Corda是R3的产品。R3是一个与超过100家银行、金融机构、监管机构、贸易协会、专业服务公司和技术公司合作的企业软件公司,致力于开发Corda。Corda的最新版本是1.0,旨在取代用于金融交易的传统软件,并使组织能够数字化使用传统软件系统繁琐的各种业务流程。
上述图表显示了Corda网络的高级架构。让我们从高层次了解Corda的架构。R3的Corda的想法是为金融交易提供共享可信赖的分类账。R3的Corda不是一个区块链平台,因此没有区块、全局广播等概念。所有交易都是点对点的。Corda应用程序不是去中心化的。在Corda中,智能合约称为CorDapps,它们是用Java或Kotlin编写的。
预期由网络不信任的企业承载记帐员,因此记帐员之间需要达成共识,因此Corda提供了各种可插拔的共识协议,如Raft、BFT等。
有时,Corda应用需要依赖外部应用程序API。例如,使用Corda构建的多币种银行间支付应用程序需要获取汇率。在这种情况下,发起交易的节点可以获取汇率并放入交易中,但你如何信任该节点?另外,每个节点都不能简单地重新获取汇率以验证其是否正确,因为其他节点获取汇率时汇率可能已经发生变化,并且这也不是可扩展的解决方案。因此,Corda提供了Oracle来解决这个问题。网络中可以有一个或多个Oracle。Oracle是作为两个应用程序之间通信的桥梁的服务。在Corda中,交易发起者可以从Corda网络外获取信息,并从Oraclize获取签名以证明其有效性。可选地,Oraclize还可以根据请求向交易发起者提供信息。显然,Oraclize应该由信任的方承载,关于他们提供和签名的信息。
Corda支持任何可插拔的RDBMS(当前正在使用H2数据库)来存储智能合约数据。数据隐私由哪些节点可以看到交易来维护。框架还提供了多重签名支持,这使得多个节点可以签署交易。Corda的一个主要缺点是,由于没有全局广播,每个节点必须以传统方式维护自己的备份和故障转移冗余,因为网络中没有内置冗余。节点将存储交易并重试向接收者发送消息,直到接收者成功接收为止。一旦消息被接收,发送方就没有更多的责任。
由于并非所有交易都广播给网络中的所有参与方,为了防止双重支付(双重支付是一种攻击DLT,将相同的资金花费两次,转移相同的资产两次等),我们使用公证员。公证员包含所有未使用的UTXOs,在公证之后,他们将其标记为已使用,并将新的未使用的UTXOs加入其状态。在发送给其他参与方之前,交易发起者会请公证员对交易进行公证。
只有在公证员先前签署了交易的输入状态时,公证员才能签署交易。但是,这并不总是情况,因此Corda还让我们改变状态的指定公证员。这种情况通常是由于以下原因导致的:
在这些交易可以创建之前,状态必须首先被重新指向以使所有状态具有相同的公证员。这是通过特殊的改变公证的交易来实现的。
CorDapps不像其他平台的智能合约那样有状态。它们的目的只是验证所产生的输出是否正确。每个UTXO指向一个CorDapp。CorDapps定义了UTXOs的格式。在一个交易中,我们可以有来自多个CorDapps的UTXOs,在这种情况下,每个CorDapp只运行一次,并验证属于它的所有输入和输出。要使交易有效,它必须在合约上有效;CorDapp应该批准它。
Oracle会以命令的形式向交易提出者提供已签名的信息,这些命令封装了特定的事实,并将Oracle列为必需的签名者。
交易也可以包含附件的哈希值。附件是ZIP/JAR文件。当存在大量数据片段可以在多个不同的交易中重用时,附件非常有用。
在验证提议的交易时,节点可能没有所需的交易链上的所有交易。因此,Corda允许节点从提议者那里请求缺失的交易。交易提出者始终会拥有所需交易链的所有交易,因为在验证交易并创建提议的交易输入状态时,他们会请求这些交易。
最后,一旦交易提交,您可以查询Vault(跟踪未消耗和已消耗的状态)。
Sawtooth是一个用于构建自己的许可DApps的分散平台。Sawtooth的主要贡献者是英特尔。Sawtooth的特殊之处在于它使用了受信任的执行环境(TEE)(目前仅支持Intel的SGX)进行共识,这使得网络非常安全和可信,并增加了对共识最终结果的信任。
TEE是主处理器的安全区域。它保证了加载到内部的代码和数据在机密性和完整性方面的保护。TEE是一个隔离的执行环境,提供了诸如隔离执行、受信任应用程序的完整性以及其资产的机密性等安全功能。
Sawtooth也支持智能合约(具体来说,以太坊智能合约可以在Sawtooth上执行)。在性能方面,Sawtooth在大量交易和节点方面都表现出色。
让我们看一些许可区块链的流行用例。这将帮助我们了解企业可以使用许可区块链做什么以及什么用例适用于许可区块链。
Everledger是一个由区块链支持的钻石数字登记处。它是区块链上的供应链管理的一个例子。之所以使用区块链,是因为在区块链中,记录是不可变的。Everledger使用了40多个特征,包括颜色和净度,来创建钻石的ID。当这些信息被放置在区块链上时,这些信息就成为了一份记录宝石所有权的证书,从矿山到戒指。Everledger已经将超过一百万颗钻石数字化,并与包括巴克莱在内的公司合作。区块链网络的参与者,如商户、银行和保险公司,可以验证钻石是否合法。Everledger建立在HyperledgerFabric平台上。未来,他们还计划将其他珍贵商品添加到他们的区块链中。
让我们举个例子场景,看看区块链如何在这个用例中发挥作用。Alice购买了一颗钻石,对其进行了保险,并在Everledger区块链上注册了它。接下来,她丢失了钻石,并报告了失窃。保险公司随后对她进行了赔偿。最后,小偷Bob试图向珠宝商Eve出售被盗的钻石。她向Everledger请求验证,并发现这是一颗被盗的钻石。保险公司被通知有关被盗的钻石,并将其占有。
物联网技术,如传感器和射频识别标签,使食品产品沿着供应链传递时可以实时写入区块链的数据。
让我们看一个例子,了解在这种情况下区块链记录了什么,以及参与者是谁。参与者包括食物的原产地农场、打包加工厂、运输食物的货运公司、沃尔玛商店等等。记录在区块链上的数据包括农场原产地数据、批号、工厂和加工数据、过期日期、储存温度和运输细节。
BenBen是一支致力于为加纳建设创新产品以改善政府技术的研发工程师团队。他们利用区块链为加纳公民开发了数字土地登记解决方案。
在加纳,银行在发放贷款时不接受土地作为抵押品。这是因为在加纳,纸质登记系统在法庭上不可执行。这导致数百万人无法获得贷款。
BenBen为金融机构提供了一流的栈顶土地登记和验证平台。该平台捕获交易并验证数据。BenBen与金融机构合作更新当前登记册,启用智能交易,并为客户分发私钥,以实现各方之间自动化和可信赖的房地产交易。
迪拜的住房租赁用例是一个区块链应用程序,让个人外籍人士在几分钟内在线租赁公寓或更新他们的住房租赁合同。在迪拜,如果一个人想租一间公寓,那么他们必须提供KYC文件,支票作为合同期限的保证,并创建Ejari(迪拜政府合同,用于合法化迪拜房东和租户之间原本不愉快的关系)。在迪拜,大多数房地产公司只有在您想要长期居住(例如,至少一年)并确保您遵守合同的情况下才会租给您公寓,并要求您提供预期支票作为保证,因为在迪拜,支票被退回被视为犯罪行为。由于租赁公寓和续签租赁合同的过程对租户和房地产公司来说都是繁琐的过程,迪拜智能政府(SmartDubai的技术部门,致力于将迪拜在技术上进行转型的城市范围倡议)启动了一个使整个流程更轻松、更快速的使命,使用区块链。
这个用例非常适合作为一个区块链用例,因为需要一个签名的不可变总账来存储KYC、支票和Ejaris,后者如果客户或任何实体试图进行欺诈,都可以得到证明。例如,当阿联酋NBD发放支票时,如果他们没有使用区块链,而是简单地进行点对点的API调用,那么ENBD、租户和wasl之间关于数字支票的存在或当前状态存在故意和非故意的分歧的可能性就非常大。因此,如果发生任何争议,区块链可以成为最终的参考工具。
Ubin项目是新加坡金融管理局(MAS)和R3合作的数字现金总账项目,参与方包括美国银行(BOA)美林证券、瑞士信贷、星展银行、香港上海汇丰银行有限公司、摩根大通、三菱日联金融集团(MUFG)、华侨银行、新加坡交易所(SGX)和大华银行(UOB),以及BCS信息系统作为技术提供商。
Ubin项目的目标是在分布式账本上构建SGD(新加坡国家货币)的数字化形式,以为新加坡的金融生态系统带来许多好处。这些好处与任何其他加密货币的好处相同。
目前,该应用程序是使用Quorum构建的,但未来可能会转移到Corda,因为R3是合作伙伴之一。
MAS是新加坡的中央银行和金融监管机构。MAS充当新加坡支付、清算和结算系统的结算代理、运营商和监管机构,着重于安全性和效率。
在本章中,我们了解了什么是DApps,并对基于区块链的DApps进行了概述。我们了解了区块链是什么,它的好处是什么,并看到了我们可以使用的各种平台来构建基于区块链的DApps。最后,我们看到了一些用例以及区块链如何为金融和非金融行业带来改变。在下一章中,我们将深入了解以太坊和Quorum,并构建一个基本的示例DApp。
在上一章中,我们了解了什么是DApp、DLT和区块链。我们还看到了一些流行的基于区块链的DApp的概述。目前,以太坊是继比特币之后最流行的公共DApp。在本章中,我们将学习如何使用Quorum构建基于权限的区块链DApp。我们将通过探索Quorum支持的所有不同共识协议、其权限和隐私功能以及最后能够快速部署Quorum网络的工具来深入了解Quorum。
Quorum是一个允许我们在其上部署DApp的权限分散平台。DApp是使用一个或多个智能合约创建的。智能合约是按照程序编写的、没有任何停机、审查、欺诈或第三方接口可能性的程序。在Quorum中,智能合约可以用Solidity、LLL或Serpent编写。Solidity是首选。一个智能合约可以有多个实例。每个实例由唯一地址标识,并且您可以在同一Quorum网络上部署多个DApp。
在以太坊中,有一种名为以太的内部货币。要部署或执行智能合约,您需要向矿工支付以太,而Quorum是以太坊的一个分叉,这里也存在同样的情况。但在Quorum中,以太是无价值的,并且在创世块生成了固定数量的以太,之后不会再生成更多的以太。用户账户和智能合约都可以持有以太。在Quorum中,您需要一些以太来执行网络上的交易,但不会扣除以太,并且向另一个账户发送以太也不会扣除以太,因此可以说,在Quorum中,以太提供了一种跟踪账户所有者的方法,如果有任何可疑情况通过跟踪以太转账并提供一种方式使您需要从网络成员中获取一些以太才能进行交易的话,这就是,从一个允许的成员获取一些以太。
目前,Quorum支持三种共识协议:QuorumChain、IBFT和Raft。在本书中,我们将只学习Raft和IBFT,因为它们是最常用的。对于隐私,它支持两种机制:零知识安全层协议和私有合约。我们将学习私有合约,但不会涵盖ZSL,因为它仍未达到生产就绪状态。
要创建一个账户,我们只需要一个非对称密钥对。有各种算法,例如Rivest–Shamir–Adleman(RSA)和椭圆曲线密码学(ECC)用于生成非对称密钥对。以太坊使用ECC。ECC有各种曲线。这些曲线具有不同的速度和安全性。以太坊使用secp256k1曲线。深入了解ECC及其曲线需要数学知识,但深入理解它并不是构建使用以太坊的DApps所必需的。
以太坊使用256位加密。以太坊的私钥和公钥是一个256位的数字。由于处理器无法表示如此大的数字,因此它总是被编码为长度为64的十六进制字符串。
每个账户都由一个地址表示。一旦我们有了生成地址所需的密钥,这里是生成地址的步骤,以及从公钥生成地址的步骤:
现在,任何人都可以向这个地址发送以太币,然后您可以签名并从这个地址发送交易。
交易是一个数据包,用于将以太币从一个账户转移到另一个账户或合约,调用合约的方法,或部署一个新的合约。交易使用椭圆曲线数字签名算法(ECDSA),这是一种基于ECC的数字签名算法。一笔交易包含一个标识发送者并证明其意图的签名,要转移的以太币金额,交易执行允许的最大计算步骤数(称为燃气限额),以及发送交易的人愿意支付的每个计算步骤的成本(称为燃气价格)。用燃气使用量乘以燃气价格得到的乘积称为交易费用。
在受权限控制的网络中,以太币是无价值的。在Quorum网络中,以太币在创世块中提供,并且不是在运行时动态生成的。您需要在创世块中提供以太币。您需要提供燃气以防止攻击,例如无限循环。交易被挖掘时,以太币不会从账户中扣除。
如果交易的意图是调用合约的方法,它还包含输入数据,或者如果它的意图是部署一个合约,那么它可以包含初始化代码。要发送以太币或执行合约方法,您需要向网络广播一个交易。发送者需要用其私钥签名交易。
如果我们确信一笔交易将始终出现在区块链的同一位置,我们就说该交易已确认。在以太坊的工作证明中,建议在最新区块的下方等待该交易出现15个区块(即等待15个确认)再假定该交易已确认,因为存在分叉的可能性,交易可能从区块链中消失。但是,在Quorum的Raft或IBFT中,一旦交易出现在其中一个区块中,我们就可以说它已确认,因为没有分叉的可能性。
在同步时,当一个节点下载一个区块时,该节点下载区块头和区块的交易。那么,接收节点如何知道这些交易实际上是该区块的一部分,并且按正确的顺序排列的呢?每个区块都由唯一的哈希标识,但是区块哈希不是区块头的一部分,并且是每个节点在下载区块后独立计算的唯一哈希;因此我们不能使用区块哈希的概念。相反,我们可以依赖类似交易哈希的东西;一个存储在区块头中的哈希,通过组合所有交易并对其进行哈希计算得到。这个想法将完美地发挥作用,并且我们可以检测到是否有任何交易丢失或额外交易包含在内,或者交易是否按正确的顺序。
嗯,Merkle根是交易哈希方法的一种替代方法,但提供了另一个主要优势:它允许网络拥有轻节点。当然,我们可以实现没有Merkle根的区块链,但如果网络需要轻节点,则必须使用Merkle根。轻节点是仅下载区块头而不下载交易的节点,但仍应能够为客户端提供所有API。例如:智能手机无法拥有完整的区块链,因为它可能非常庞大;因此,我们可以在智能手机中安装轻客户端。
让我们首先了解二进制Merkle树在区块链中是什么。哈希树或Merkle树是一种树,其中每个叶节点是一个交易的哈希,每个非叶节点是其子节点的哈希的哈希。哈希树允许高效和安全地验证哪些交易是区块的一部分。每个区块都形成自己的Merkle树。当每个父节点都有两个子节点时,Merkle树被称为二进制Merkle树。二进制Merkle树是区块链中使用的树。以下是二进制Merkle树的示例:
在上面的图表中,首先计算每个交易的单独哈希。然后,它们被分成两组。然后,对于每一对,计算两个哈希的哈希。这个过程将持续进行,直到我们有一个称为默克尔根的单一哈希。如果交易数量为奇数,最后一个交易会被复制,以使交易总数为偶数。
现在,在下载完整区块、区块头和区块的交易时,节点可以通过形成二叉默克尔树并检查生成的默克尔根是否与包含在区块头中的默克尔根相同,来验证交易集是否正确。当然,这也可以在没有默克尔树的情况下完成,正如前面讨论过的。
轻节点可以利用默克尔树为客户端提供服务。例如,轻节点可以向完整节点发出请求,询问特定交易是否已经在某个区块中提交,完整节点会回复区块编号和默克尔证明,以证明交易是否已经在某个区块中提交。然而,轻节点不能仅仅相信完整节点提供的区块编号,因此完整节点还需提供默克尔证明。为了理解什么是默克尔证明,让我们来看看前面的图表以及轻节点询问完整节点TxD是否已提交的情况。现在,完整节点返回区块编号以及一个子树,其中包括H[ABCD]、H[AB]、H[CD]、H[C]和H[D]。这个子树就是默克尔证明。现在,轻客户端可以拿到这个默克尔证明并验证它。验证将包括检查默克尔证明是否构造正确以及默克尔证明的默克尔根是否与完整节点声称交易所在的区块头中的默克尔根相同。你可能会想,如果一个完整节点声称交易已提交,但实际上尚未提交,该怎么办呢?在这种情况下,解决这个问题的唯一方法是请求多个完整节点,而且它们都不太可能撒谎。没有默克尔树,这个功能是无法实现的。
以太坊区块链更加复杂。现在,假设以太坊轻节点想要知道某个账户的余额、从智能合约中读取数据、找到交易的燃气估算值等等,那么通过这种简单的交易二叉默克尔树将无法提供这种功能。因此,在以太坊中,每个区块头不仅包含一个默克尔树,而是包含三个默克尔树,用于三种类型的对象:
现在我们有了三棵树,让我们来看一个轻节点向完整节点提出的高级查询示例。查询是假装在这个合约上运行这个交易。交易收据和新状态会是什么?这由状态树处理,但计算方式更为复杂。在这里,我们需要构造一个Merkle状态转换证明。基本上,它是一种证明,宣称:如果在具有根S的状态上运行交易T,则结果将是具有根S’的状态,其中包括交易收据R。为了计算状态交易证明,完整节点在本地创建一个虚假块,将状态设置为S,并假装成为一个轻节点,同时应用交易。也就是说,如果应用交易的过程需要轻节点确定账户余额,则轻节点进行余额查询。如果轻节点需要检查特定合约的存储中的特定项目,则轻节点进行该查询,依此类推。完整节点正确响应自己的所有查询,但跟踪发送回的所有数据。然后完整节点将来自所有这些请求的组合数据作为证明发送给轻节点。然后轻客户端执行完全相同的过程,但使用提供的证明作为其数据库,而不是向完整节点发出请求,如果其结果与完整节点声称的相同,则轻客户端接受输出为完整节点所声称的输出。
在企业区块链中,不使用轻客户端,因为节点代表一个企业,而企业有基础设施来运行完整节点。
当节点之间关于区块链的有效性存在冲突时,即网络中存在多个区块链时,就会发生分叉。分叉有三种类型:常规、软分叉和硬分叉。
当同时存在两个或更多个相同高度的区块时,就会发生常规分叉。这是一种暂时的冲突,会自动解决。节点通过选择最准确的区块链来解决这个问题。例如,在工作量证明中,如果两个矿工同时挖出一个区块,那么就会创建一个常规分叉。这是通过选择具有最高难度的区块链来解决的,因为它被认为是最准确的一个。
相比之下,软分叉是指对区块链协议的任何更改都是向后兼容的。比如,新规则可能只允许1MB区块,而不是2MB区块。非升级的节点仍然会将新的交易视为有效(在本例中,1MB小于2MB)。然而,如果非升级的节点继续创建区块,那么它们创建的区块将被升级的节点拒绝。因此,如果网络中的少数节点升级了,则它们形成的链将变得不太准确,并被非升级节点创建的区块链覆盖。软分叉在网络中的大多数节点升级其节点软件时解决。
硬分叉是一种软件升级,引入了一个与旧软件不兼容的新规则到网络中。你可以把硬分叉看作是规则的扩展。例如,允许区块大小为2MB而不是1MB的新规则将需要进行硬分叉。继续运行旧版本软件的节点将会将新交易视为无效。因此,只有当网络中的所有节点都升级其节点软件时,分叉才能解决。在那之前,网络中将会有两个不同的区块链。
你一定听说过比特币和以太坊的分叉。例如,比特币现金和以太经典是硬分叉的形成。网络中的许多矿工和节点不同意新协议,并选择运行旧软件,从而分裂出网络并形成了一个不同的网络。
让我们看看Raft共识协议的工作原理,以一个足以让我们舒适地构建DApps的水平。我们不会深入研究Raft,因为这并不必要。
网络中的每个节点都会保留网络中所有其他节点的列表,无论它们是否正在运行。Raft集群中的服务器可以是领导者或追随者,并且在选举时可能是候选者,这种情况发生在领导者不可用时。每次只能有一个领导者。领导者负责创建和发送区块给追随者。它通过定期发送心跳消息来告知追随者自己的存在。每个追随者都有一个超时(通常在150和300毫秒之间),称为选举超时,在此期间它期望来自领导者的心跳。每个节点都使用在120-300ms范围内的随机选举超时。收到心跳后,选举超时会被重置。如果没有收到心跳,追随者会将其状态更改为候选者,并开始领导者选举,以选举网络中的新领导者。当候选者启动领导者选举时,它基本上将自己提出为新领导者,并且如果超过50%的节点投票支持它,它就成为领导者。如果在一定的超时内没有选举出领导者,则会启动新的领导者选举过程。深入理解领导者选举过程并非必要。
Raft的设计是这样的,一个Raft网络需要超过50%的节点可用,才能将新的交易提交到区块链;如果集群有2*F+1个节点,则可以容忍F个故障并仍然正常运行。如果超过F个节点失败,则应用程序将失败,并且一旦集群再次有超过F个节点正常工作,它将再次正确地恢复工作。即使领导者选举也会在网络中超过50%的节点不可用时失败。
每个节点的每笔交易都会发送到网络中的每个其他节点。领导者负责创建和广播区块。当领导者创建一个区块时,它首先将区块发送给所有的追随者,一旦超过50%的追随者接收到了该区块,领导者将把该区块提交到其区块链中,然后向追随者发送一个提交消息,以便追随者也将该区块提交到其区块链中。在追随者不可用的情况下,领导者会无限期地重试请求,直到所有的追随者最终都提交了该区块。这个过程确保了一旦一个区块提交到区块链上,就无法撤销。即使领导者选举过程也确保了谁被选为领导者,其区块链是最新的。
让我们看看IBFT共识协议是如何工作的,这将使我们足够放心去构建DApps。我们不会深入研究IBFT,因为这并不是必要的。
在IBFT中,一个轮次涉及创建并提交一个新的区块到区块链中。在(2F+1)个验证者的区块链中提交了一个新的区块后,就会开始一个新的轮次。在每个区块创建轮次之前,验证者将从中选择一个作为提议者。提议者是负责创建区块的验证者。为了将区块提交到区块链上,必须至少有(2F+1)个验证者签名。因此,在每一轮中,提议者和其他验证者之间需要发送和接收各种消息的过程以同意新的区块。
如果网络设法拥有多于F个故障节点,则这些故障节点可以通过拒绝签署区块来阻止新区块的创建。当网络中的崩溃节点重新上线时,它可以从网络中的任何节点获取丢失的区块。超过F个故障节点无法重新编写区块。
验证者可以添加或删除验证者。即使将新验证者添加或删除到网络中也需要2F+1个验证者同意。验证者同意或不同意添加或删除验证者的过程是手动进行的。它不能是一个自动过程,因为验证者可以开始添加多个自己的验证节点并危害网络。因此,手动过程确保其他验证者了解新验证者是谁,并决定是否允许它。
私有合约是夸姆提供的一个开箱即用的功能,用于实现数据隐私。私有合约用于在两个或多个节点之间私下共享信息,而其他节点无法看到。
让我们看看Quorum中的私有合约是什么。使用私有交易部署的合约称为私有合约。私有交易基本上是一种其有效负载(合约部署的合约代码或调用函数的函数参数,交易的数据部分)在区块链之外点对点共享的交易,在发送交易时选择的一组节点之间共享有效负载,并且有效负载的哈希在区块链中被记录,用有效负载的哈希替换实际有效负载。现在,网络中的节点检查它们是否有内容哈希为区块链中存在的有效负载的哈希,并且如果是,则执行原始有效负载。Quorum形成同一区块链的两个不同状态:公共状态和私有状态。私有交易形成私有状态,而公共交易形成公共状态。这些状态之间不能互相交互。但是,私有-私有合约可以相互交互。
Quorum使用constellation来发送和接收私有交易的实际交易有效负载。Constellation是由J.P.Morgan构建的独立软件。Constellation形成一个节点网络,每个节点都会公布一个它们是接收方的公钥列表。每个节点暴露了一个API,允许用户将有效负载发送到一个或多个公钥。在传输到接收节点之前,有效负载将被加密为公钥。它通过IPC公开了应用程序连接到其constellation节点并发送或接收数据的API。在高层次上,如果您连接到constellation网络,则只需提及接收方的公钥,数据就会被加密并发送到与公钥映射的IP地址。在发送私有交易时,仅在有效负载成功发送到所有列出的constellation节点后,才将公钥列表和交易广播到区块链网络。如果任何列出的constellation节点宕机,则交易失败,并且永远不会广播到区块链网络。
所以,在启动您的Quorum节点之前,您需要启动您的constellation节点,并在启动Quorum节点之前提供constellation的IPC路径给Quorum节点。然后,您的Quorum节点使用constellation来发送或接收私有交易。
私有交易并不是在Quorum中实现隐私的最终解决方案。它们有各种缺点。以下是其中一些:
现在我们对Quorum的共识协议、以太坊账户、交易和私有合约非常有信心。是时候构建一个Quorum网络了。在此之前,我们需要学习如何安装Quorum和星座。请记住,星座是可选的,仅在需要私有合约时才应将其集成到Quorum网络中。
以下是从源代码构建Quorum的三个基本命令:
要安装星座(constellation),您需要几个先决条件。在Ubuntu中,运行以下命令安装先决条件:
brewinstallberkeley-dbleveldblibsodiumbrewinstallhaskell-stackstacksetup现在,要安装星座,运行以下命令:
现在,我们已经成功安装了Quorum和星座,现在是时候设置我们的第一个Quorum网络了。在设置网络之前,你需要决定是否要使用Raft还是IBFT,然后相应地进行计划和设置。我们将学习如何设置这两种类型的网络。我们还将设置一个星座网络。
现在,让我们使用星座构建一个Raft网络。一旦网络运行起来,我们还将看到如何添加和删除新节点。我们将构建一个四个节点的网络。
创建一个名为raft的目录。然后,在其中放置geth和constellation-node的二进制文件。你可以使用geth和constellation-node的--help选项来查找各种子命令和可用选项。
现在,让我们首先创建四个星座节点。为了开发目的,我们将在同一台机器上运行所有四个节点。对于每个星座节点,我们必须生成一个单独的非对称密钥对。在raft目录中运行以下命令来创建密钥对:
./constellation-node--generatekeys=node1./constellation-node--generatekeys=node2./constellation-node--generatekeys=node3./constellation-node--generatekeys=node4在这里,我们为每个星座节点生成了一个公钥。但是,你可以为每个星座节点有多个公钥。在运行上述命令时,它将要求你输入一个密码来加密密钥,但是你可以通过按下Enter键来跳过这一步。如果你想在运行星座节点时加密,则必须提供解密密码。为了让事情简单,我们将不设置密码。
以下是constellation1.conf的代码:
现在,在不同的shell窗口中运行以下命令来启动constellation节点:
./constellation-nodeconstellation1.conf./constellation-nodeconstellation2.conf./constellation-nodeconstellation3.conf./constellation-nodeconstellation4.conf生成enode在Raft中,在设置网络之前,你必须确定网络中将有多少个节点,然后为每个节点生成并enodeID。随后,你创建一个列出所有节点的enodeURL的static-nodes.json文件,并将此文件提供给网络中的每个节点。一旦配置好网络,向网络中添加节点涉及不同的流程。
我们将建立一个由三个节点组成的网络,然后动态添加第四个节点。使用以下三条命令生成所有四个节点的节点密钥:
./bootnode-genkeyenode_id_1./bootnode-genkeyenode_id_2./bootnode-genkeyenode_id_3./bootnode-genkeyenode_id_4上述命令将创建私钥。现在,要查找节点ID,需要运行下面的命令:
现在,创建一个名为static-nodes.json的文件,并添加以下代码。确保将节点ID替换为你生成的节点ID:
["enode://480cd6ab5c7910af0e413e17135d494d9a6b74c9d67692b0611e4eefea1cd082adbdaa4c22467c583fb881e30fda415f0f84cfea7ddd7df45e1e7499ad3c680c@127.0.0.1:23000raftport=21000","enode://60998b26d4a1ecbb29eff66c428c73f02e2b8a2936c4bbb46581ef59b2678b7023d300a31b899a7d82cae3cbb6f394de80d07820e0689b505c99920803d5029a@127.0.0.1:23001raftport=21001","enode://e03f30b25c1739d203dd85e2dcc0ad79d53fa776034074134ec2bf128e609a0521f35ed341edd12e43e436f08620ea68d39c05f63281772b4cce15b21d27941e@127.0.0.1:23002raftport=21002"]这里,2300x端口用于以太坊协议通信,2100x端口用于Raft协议通信。
在以太坊中,static-nodes.json被用来列出一些你总是想要连接的节点的enode。并且,使用这些节点,你的节点可以发现网络中的其他节点。但在Quorum的Raft中,这个文件必须包含网络中所有节点的enode,因为在Raft中,这个文件用于达成共识,不同于以太坊中的用途是节点发现。
现在,我们需要生成一个以太坊账户。现在进行这个操作是因为在创建创世块时,我们必须为网络提供以太币。因此,我们将为此生成的账户提供以太币。以下是创建以太坊账户的命令:
./geth--datadir./accountsaccountnew在运行此命令时,它将要求输入密码以加密帐户。您可以按两次Enter键跳过。这将使空字符串成为解密帐户的密码。在这里,--datadir选项用于指定在哪里存储密钥。基本上,在accounts/keystore目录中,您将找到一个格式为UTC--DATE-TIME--ADDRESS的文件。将此文件重命名为key1。此文件存储帐户的私钥和地址。打开文件并复制地址,因为您在创建创世块时将需要它。
现在,最后一步是创建创世块。创世块始终在网络中硬编码。以下是创世块的内容。创建一个genesis.json文件并将以下代码放入其中:
{"alloc":{"0x65d8c00633404140986e5e23aa9de8ea689c1d05":{"balance":"1000000000000000000000000000"}},"coinbase":"0x0000000000000000000000000000000000000000","config":{"homesteadBlock":0},"difficulty":"0x0","extraData":"0x","gasLimit":"0x7FFFFFFFFFFFFFFF","mixhash":"0x00000000000000000000000000000000000000647572616c65787365646c6578","nonce":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x00"}在这里,请确保用您的帐户地址0x65d8c00633404140986e5e23aa9de8ea689c1d05替换帐户地址。在这里,我们向0x65d8c00633404140986e5e23aa9de8ea689c1d05帐户提供了以太币。
如果您想在Quorum网络中摆脱以太币,可以在启动geth时使用--gasPrice0选项。因此,您将不需要在创世块中提供以太币。但是,以太币具有可追溯性的优势。
现在,在我们启动节点之前,我们需要初始化它们并为每个节点创建数据目录;将static-nodes.json文件复制到每个节点的数据目录中,将帐户密钥复制到数据目录中,并使用创世块引导区块链。
以下是对所有节点执行所有初始化操作的命令:
#Startingnode1PRIVATE_CONFIG=constellation1.conf./geth--datadirqdata/node1--port23000--raftport21000--raft--ipcpath"./geth.ipc"#Startingnode2PRIVATE_CONFIG=constellation2.conf./geth--datadirqdata/node2--port23001--raftport21001--raft--ipcpath"./geth.ipc"#Startingnode3PRIVATE_CONFIG=constellation3.conf./geth--datadirqdata/node3--port23002--raftport21002--raft--ipcpath"./geth.ipc"以下是我们提供的不同选项的含义:
geth为客户端提供了用于与其通信的JSON-RPCAPI。geth使用HTTP、WS和IPC提供JSON-RPCAPI。JSON-RPC提供的API分为各种类别。geth还提供了一个交互式的JavaScript控制台,以便使用JavaScriptAPI对其进行编程交互。交互式控制台使用IPC上的JSON-RPC与geth进行通信。我们稍后会详细了解这个。
现在,要打开node1的交互式控制台,请使用以下命令:
./gethattachipc:./qdata/node1/geth.ipc现在,我们已经完成了创建我们的第一个Raft网络。
现在,让我们动态添加第四个节点。任何节点都可以向网络添加第四个节点。让我们从node1添加它。第一步是对node4进行初始化操作。为此运行以下命令:
#ConfiguringNode4mkdir-pqdata/node4/gethcpenode_id_4qdata/node4/geth/nodekey./geth--datadirqdata/node4initgenesis.json请注意,这里我们没有复制static-nodes.json文件,因为我们是动态添加它。现在,从第四个节点的交互式控制台中,运行以下行代码将第四个对等体添加到网络中:
raft.addPeer("enode://27d3105b2c1173792786ab40e466fda80edf9582cd7fa1a867123dab9e2f170be0b7e16d4065cbe81637759555603cc0619fcdf0fc7296d506b9c26c26f3ae0c@127.0.0.1:23003raftport=21003")在这里,请用您生成的节点ID替换节点ID。当您运行以下命令时,将获得一个数字作为返回值。这个数字很重要,被称为节点的RaftID。Raft共识为每个节点分配一个唯一ID。静态nodes.json文件中的第一个节点被赋予RaftID1,下一个节点被赋予RaftID2,依此类推。第四个节点将具有RaftID4。在启动第四个节点时,您将需要这个数字。现在,使用以下命令启动第四个节点:
PRIVATE_CONFIG=constellation4.conf./geth--datadirqdata/node4--port23003--raftport21003--raft--ipcpath"./geth.ipc"--raftjoinexisting4在前面的命令中,一切看起来都很相似,除了一个新选项,--raftjoinexisting。在启动动态添加的节点时,我们需要指定此选项并赋予它节点的RaftID。当使用raft.addPeer添加节点时,这个RaftID将出现。
现在,让我们从网络中移除一个节点。让我们从static-nodes.json文件中删除第三个节点。这个节点的raftID将是3。在节点1的交互式控制台中,运行以下代码从网络中移除第三个节点:
raft.removePeer(3)现在,第三个对等体将从网络中移除。您现在可以使用admin.peersAPI来检查连接到此节点的节点总数列表。列表中应该有两个节点,网络中共有三个节点。
如果一个节点在添加或移除新节点到网络时处于宕机状态,那么宕机的节点将在其恢复运行后知道网络的更改。
我们将构建一个六个节点的网络。前四个将是验证者,另外两个将是非验证者。在这个网络中,我们将不添加星座。如果您想添加一个,指令与之前相同。
在IBFT中,每个验证者都是通过从其节点密钥派生的以太坊账户唯一标识的。类似于Raft,在设置网络之前,我们在IBFT中必须决定网络中的验证者总数,然后为每个验证者生成一个enode。然后,我们创建一个列出所有验证节点enode的static-nodes.json文件,并将此文件提供给网络中的每个验证者。之后,从节点ID派生以太坊地址。最后,我们构造extraData字段并创建genesis文件。
对于IBFT,创建static-nodes.json文件并非必需。你也可以使用admin.addPeer(url)API连接节点。
IBFT软件包含配置IBFT网络、生成enode、从节点密钥生成地址、创建创世块等工具。为IBFT创建创世块并不像为Raft创建那样简单,因为创世块中需要包含一个编码的extraData字段,其中列出了验证者列表。
以下是安装IBFT工具的步骤:
IBFT工具可以自动创建创世块。同时,它还会生成节点密钥、从节点密钥生成的地址和static-nodes.json文件。
运行以下命令以生成所有这些内容:
./istanbulsetup--num4--nodes--verbose现在,你会得到类似的输出:
现在,让我们生成一个以太坊账户,并在创世块中分配一些以太币给它。以太币不是动态生成的,因此我们需要预先提供。使用以下命令生成以太坊账户:
./geth--datadir./accountsaccountnew现在,将accounts/keystore目录中的文件名更改为key1。然后将地址复制,放入genesis文件中,并分配一些余额。例如,如果我新生成的账户地址是0x65d8c00633404140986e5e23aa9de8ea689c1d05,那么我的genesis文件内容将如下所示:
以下是为所有六个节点实现这些的命令:
./geth--datadirqdata/node1--mine--port23000--ipcpath"./geth.ipc"--istanbul.requesttimeout5000--istanbul.blockperiod1--istanbul.blockpausetime20console./geth--datadirqdata/node2--mine--port23001--ipcpath"./geth.ipc"--istanbul.requesttimeout5000--istanbul.blockperiod1--istanbul.blockpausetime20console./geth--datadirqdata/node3--mine--port23002--ipcpath"./geth.ipc"--istanbul.requesttimeout5000--istanbul.blockperiod1--istanbul.blockpausetime20console./geth--datadirqdata/node4--mine--port23003--ipcpath"./geth.ipc"--istanbul.requesttimeout5000--istanbul.blockperiod1--istanbul.blockpausetime20console./geth--datadirqdata/node5--port23004--ipcpath"./geth.ipc"console./geth--datadirqdata/node6--port23005--ipcpath"./geth.ipc"console下面是我们刚刚传递的不同选项的含义:
要获取网络中所有验证者的列表,您可以使用istanbul.getValidators()API。
让我们首先看看如何动态添加新的验证节点。要添加验证节点,我们首先需要生成新验证节点的节点密钥和地址。运行以下命令生成它:
./istanbulsetup--num1--nodes--verbose这是我们之前使用的相同命令。现在,我们不需要genesis文件或static-nodes.json文件。我们只需要节点密钥和地址。创建一个名为node_id_5的文件,并将节点密钥放入其中。运行以下命令初始化新的验证者:
#ConfiguringNode7mkdir-pqdata/node7/gethcpstatic-nodes.jsonqdata/node7cpenode_id_5qdata/node7/geth/nodekey./geth--datadirqdata/node7initgenesis.json现在,在上述命令成功运行后,是时候让(2F+1)其他验证者同意插入新的验证者了。为此,在所有其他验证者中运行以下命令:
istanbul.propose("0x349ec6eefe8453a875c4905f5581ea792806a3e5",true)将第一个参数替换为您获得的新验证节点地址。现在,使用以下命令启动新的验证节点:
./geth--datadirqdata/node7--mine--port23006--ipcpath"./geth.ipc"--istanbul.requesttimeout5000--istanbul.blockperiod1--istanbul.blockpausetime20console现在,您可以运行istanbul.getValidators()来检查网络中所有验证者的列表。现在应该有五个。让我们从网络中移除一个验证者。假设我们想要移除第一个验证者。在第一个验证者的控制台中运行eth.coinbase找到其唯一地址。然后,在(2F+1)个验证者中运行以下命令以从网络中移除第一个验证者:
istanbul.propose("0x05a6245732c2350ba2ed64e840394c2239f8ad1f",false)在此处,使用您生成的第一个验证节点的地址替换第一个参数。
在移除或添加验证节点时,如果某个验证节点宕机,那么一旦它重新运行起来,它将自动了解到这些更改。
在本章中,我们从以太坊区块链的基础知识开始,然后深入探讨了Quorum的特性和共识协议。然后,通过设置星座、Raft和IBFT网络,我们第一次实践了Quorum。现在,您应该对设置网络的过程感到满意了。下一步是学习编写智能合约,并部署我们的第一个智能合约。我们将在下一章中实现这一点。
在上一章中,我们了解了Quorum的工作原理以及各种共识协议是如何保护它的。现在我们了解了Quorum的工作原理,让我们继续编写智能合约。Quorum智能合约可以使用许多语言编写;最流行的是Solidity。在本章中,我们将学习Solidity,并构建一个企业可以用来数字签署文件的DApp。
本章与作者之前的书籍项目区块链中的章节相同。这不是第二版的书籍,它被用来向读者解释基本概念。
Solidity源文件的识别方法是通过.sol扩展名。它有各种版本,就像通常的编程语言一样。在撰写本书时,最新版本是0.4.17。
在源文件中,您可以使用pragmaSolidity指令来指定编写代码的编译器版本。例如:
pragmaSolidity.4.17;需要注意的是,源文件不会在早于0.4.17或晚于0.5.0(此第二个条件使用^添加)的编译器版本下编译。编译器版本在0.4.17和0.5.0之间的情况最有可能包含bug修复,并且不太可能破坏任何内容。
我们可以为编译器版本指定更复杂的规则;表达式遵循npm使用的规则。
A类似于一个类。它可以有函数、修改器、状态变量、事件、结构体和枚举。合约也支持继承。您可以通过在编译时复制代码来实现继承。智能合约也可以是多态的。
以下是一个智能合约的示例:
contractSample{//statevariablesuint256data;addressowner;//eventdefinitioneventlogData(uint256dataToLog);//functionmodifiermodifieronlyOwner(){if(msg.sender!=owner)throw;_;}//constructorfunctionSample(uint256initData,addressinitOwner){data=initData;owner=initOwner;}//functionsfunctiongetData()returns(uint256returnedData){returndata;}functionsetData(uint256newData)onlyOwner{logData(newData);data=newData;}}让我们看看上述代码如何工作:
与其他编程语言不同,Solidity的变量根据上下文存储在内存和数据库中。
总是有一个默认位置,但可以通过附加storage或memory来覆盖复杂类型的数据,例如字符串、数组和结构体。Memory是函数参数(包括return参数)的默认值,而storage适用于局部和状态变量(显然)。
数据位置很重要,因为它们会改变赋值的行为:
Solidity提供了以下数据类型:
Solidity支持通用和字节数组,固定大小和动态数组,以及多维数组。
bytes1、bytes2、bytes3,一直到bytes32都是字节数组的类型。我们将使用字节表示bytes1。
这是一些通用数组语法的示例:
在Solidity中,可以通过两种方式创建字符串:使用bytes和string。bytes用于创建原始字符串,而string用于创建UTF-8字符串。字符串的长度始终是动态的。
以下是显示string语法的示例:
以下是创建和使用mapping的示例:
contractsample{mapping(int=>string)myMap;functionsample(intkey,stringvalue){myMap[key]=value;//myMap2isareferencetomyMapmapping(int=>string)myMap2=myMap;}}delete运算符delete运算符可以应用于任何变量以将其重置为其默认值。默认值是所有位都分配为零。
如果我们对动态数组应用delete,它将删除所有元素并使长度变为零。如果我们对静态数组应用delete,它的所有索引都会被重置。我们也可以对特定的索引应用delete,以重置它们。
然而,如果您将delete应用于映射类型,则不会发生任何事情。但是,如果您将delete应用于映射的key,则与key关联的值将被删除。
让我们看看delete运算符的工作原理,如下所示:
contractsample{structStruct{mapping(int=>int)myMap;intmyNumber;}int[]myArray;StructmyStruct;functionsample(intkey,intvalue,intnumber,int[]array){//mapscannotbeassignedsowhileconstructingstructwe//ignorethemapsmyStruct=Struct(number);//heresetthemapkey/valuemyStruct.myMap[key]=value;myArray=array;}functionreset(){//myArraylengthisnow0deletemyArray;//myNumberisnow0andmyMapremainsasitisdeletemyStruct;}functiondeleteKey(intkey){//herewearedeletingthekeydeletemyStruct.myMap[key];}}基本类型之间的转换除了数组、字符串、结构体、枚举和映射之外的所有内容都被称为基本类型。
如果我们对不同类型的操作数应用运算符,编译器会尝试将其中一个操作数隐式转换为另一个操作数的类型。一般来说,如果在语义上有意义且不会丢失信息,那么值类型之间的隐式转换是可能的:uint8可转换为uint16,int128可转换为int256,但int8无法转换为uint256(因为uint256无法容纳,例如,-1)。此外,无符号整数可以转换为相同或更大尺寸的字节,但反之则不行。任何可以转换为uint160的类型也可以转换为地址。
Solidity还支持显式转换。如果编译器不允许两种数据类型之间的隐式转换,您可以选择显式转换。我们建议避免显式转换,因为它可能会给您带来意外的结果。
显式转换的以下示例,如下所示:
uint32a=0x12345678;uint16b=uint16(a);//bwillbe0x5678now在这里,我们将uint32类型显式转换为uint16,即将大类型转换为小类型;因此,高阶位被截断。
让我们看看var的工作原理,如下所示:
int256x=12;//ytypeisint256vary=x;uint256z=9;//exceptionbecauseimplicitconversionnotpossibley=z;请注意,在定义数组和映射时不能使用var。它也不能用于定义函数参数和状态变量。
Solidity支持if...else,do...while,for,break,continue,return和:控制结构。
这里有一个结构:
contractsample{inta=12;int[]b;functionsample(){//"=="throwsexceptionforcomplextypesif(a==12){}elseif(a==34){}else{}vartemp=10;while(temp<20){if(temp==17){break;}else{continue;}temp++;}for(variii=0;iii 让我们演示一下,如下所示: contractsample1{inta;functionassign(intb){a=b;}}contractsample2{functionsample2(){sample1s=newsample1();s.assign(12);}}异常在某些情况下,异常会自动抛出。您可以使用assert(),revert()和require()来抛出手动异常。异常会停止并撤销当前正在执行的调用(即,对状态和余额的所有更改都将被撤消)。在Solidity中,尚不可能捕获异常。 以下三行是Solidity中抛出异常的不同方式: Solidity有两种类型的函数调用:内部和外部。内部函数调用是指函数调用同一合同中的另一个函数。外部函数调用是指函数调用另一个合同的函数。 以下是一个示例: 现在是深入研究合同的时候了。让我们从一些新特性开始,然后我们将更深入地了解我们已经看到的特性。 状态变量或函数的可见性定义了谁可以看到它。可见性有四种类型:external,public,internal和private。 默认情况下,函数的可见性为public,状态变量的可见性为internal。让我们看看这些可见性函数意味着什么: 这是一个代码示例,用于演示可见性和访问器: contractsample1{intpublicb=78;intinternalc=90;functionsample1(){//externalaccessthis.a();//compilererrora();//internalaccessb=21;//externalaccessthis.b;//externalaccessthis.b();//compilererrorthis.b(8);//compilererrorthis.c();//internalaccessc=9;}functiona()external{}}contractsample2{intinternald=9;intprivatee=90;}//sample3inheritssample2contractsample3issample2{sample1s;functionsample3(){s=newsample1();//externalaccesss.a();//externalaccessvarf=s.b;//compilererrorasaccessorcannotusedtoassignavalues.b=18;//compilererrors.c();//internalaccessd=8;//compilererrore=7;}}函数修饰符我们已经看到了函数修饰符是什么,并且我们编写了一个基本版本。现在让我们详细看一下它。 修饰符由子合约继承,并且它们也可以被子合约覆盖。可以通过在空格分隔的列表中指定它们来向函数应用多个修饰符,并且它们将按顺序进行评估。您还可以向修饰符传递参数。 在修饰符内部,下一个修饰符主体或函数主体,以后出现的,被插入到_;出现的位置。 让我们看一个函数修饰符的复杂代码示例,如下所示: contractsample{inta=90;modifiermyModifier1(intb){intc=b;_;c=a;a=8;}modifiermyModifier2{intc=a;_;}modifiermyModifier3{a=96;return;_;a=99;}modifiermyModifier4{intc=a;_;}functionmyFunction()myModifier1(a)myModifier2myModifier3returns(intd){a=1;returna;}}这是myFunction()的执行方式: intc=b;intc=a;a=96;return;intc=a;a=1;returna;a=99;c=a;a=8;在这里,当你调用myFunction方法时,它将返回0。但之后,当你尝试访问状态变量a时,你将得到8。 return在修饰符或函数体中立即离开整个函数,返回值被分配给需要的变量。 在函数的情况下,return后的代码在调用方的代码执行完成后执行。而在修饰符的情况下,在前一个修饰符中的_;后的代码在调用方的代码执行完成后执行。在上述示例中,第五、六和七行永远不会执行。在第四行之后,执行直接从第八到第十行开始。 修饰符内部的return不能与任何值关联。它总是返回零位。 回退函数是合约可以拥有的唯一无名称的函数。此函数不能有参数,也不能返回任何内容。如果没有其他函数匹配给定的函数标识符,则在调用合约时执行该函数。 这个函数也会在合约在没有任何函数调用的情况下接收以太币时执行;也就是说,交易将以太币发送到合约并不调用任何方法。在这样的情况下,通常只有很少的气体可用于函数调用(精确地说是2,300气体),因此将回退函数尽可能地廉价是很重要的。 当合约收到以太币但没有定义回退函数时,它们会抛出异常,从而将以太币退回。因此,如果你希望你的合约接收以太坊,你必须实现一个回退函数。 下面是一个回退函数的例子: contractsample{function()payable{//NotehowmuchEtherhasbeensentandbywhom}}继承Solidity支持通过复制代码来实现多重继承,包括多态性。即使一个合约从多个其他合约继承,区块链上也只会创建一个合约。此外,父合约的代码始终会被复制到最终的合约中。 让我们来回顾一个继承的例子: contractsample1{functiona(){}functionb(){}}//sample2inheritssample1contractsample2issample1{functionb(){}}contractsample3{functionsample3(intb){}}//sample4inheritsfromsample1andsample2//Notethatsample1isalsoaparentofsample2;yetthereisonlya//singleinstanceofsample1contractsample4issample1,sample2{functiona(){}functionc(){//thisexecutesthe"a"methodofsample3contracta();//thisexecutesthe"a"methodofsample1contractsample1.a();//callssample2.b()becauseitislastintheparentcontractslistandthereforeitoverridessample1.b()b();}}//Ifaconstructortakesanargument,itneedstobeprovidedat//theconstructorofthechildcontract.//InSolidity,childconstructordoesnotcallparentconstructor,//insteadparentisinitializedandcopiedtochildcontractsample5issample3(122){}超级关键字super关键字用于引用最终继承链中的下一个合约。以下是一个例子,帮助你更好地理解: contractsample1{}contractsample2{}contractsample3issample2{}contractsample4issample2{}contractsample5issample4{functionmyFunc(){}}contractsample6issample1,sample2,sample3,sample5{functionmyFunc(){//sample5.myFunc()super.myFunc();}}关于sample6合约的最终继承链是sample6,sample5,sample4,sample2,sample3,sample1。继承链以最派生的合约开始,以最不派生的合约结束。 抽象合约是仅包含函数原型而不包含实现的合约。它们无法被编译(即使它们包含了已实现的函数和未实现的函数)。如果一个合约继承自一个抽象合约并且没有通过覆盖实现所有未实现的函数,那么它本身也会变成抽象的。 提供抽象合约的原因是为了让编译器知道接口。这对于引用已部署的合约并调用其函数是有用的。 让我们通过下面的例子来演示: 库可以包含结构体和枚举,但它们不能有状态变量。它们不支持继承,也不能接收以太币。 一旦Solidity库被部署到区块链上,任何人都可以使用它,只要知道它的地址并且有源代码(只有原型或完整实现)。Solidity编译器需要源代码,以便确保正在访问的方法确实存在于库中。 下面是一个示例: librarymath{functionaddInt(inta,intb)returns(intc){returna+b;}}contractsample{functiondata()returns(intd){returnmath.addInt(1,2);}}库的地址无法添加到合约源代码中。我们需要在编译期间向编译器提供库地址。 库有许多用例。两个主要用例如下: 仅包含内部函数和/或结构/枚举的库不需要部署,因为库中的所有内容都会复制到使用它的合约中。 usingAforB;指令可用于将库函数(来自库A)附加到任何类型B上。这些函数将以调用它们的对象作为第一个参数。 usingAfor*;的效果是将库A的函数附加到所有类型上。 下面是一个演示for的示例: contractsample{functiona()returns(inta,stringc){return(1,"ss");}functionb(){intA;stringmemoryB;//Ais1andBis"ss"(A,B)=a();//Ais1(A,)=a();//Bis"ss"(,B)=a();}}导入其他Solidity源文件Solidity允许一个源文件导入其他源文件。下面是一个示例来演示这一点: 区块和交易属性如下所示: 字面数字可以附加wei、finney、szabo或ether后缀,以在以太币的子单位之间进行转换,其中以太币货币数字没有后缀被假定为wei。例如,2Ether==2000finney计算结果为true。 如今,企业正在使用电子签名解决方案签署协议。然而,这些文件的详细信息存储在可以轻松更改的数据库中,因此不能用于审计目的。区块链可以通过将区块链集成为这些电子签名系统的解决方案来解决此问题。 我们将使用Quorum的私有交易,因为实体之间签署的协议对它们是私有的,细节不会暴露给其他实体。尽管只有文件的哈希将被暴露,但其他实体知道一个实体签署了多少协议仍然不是一个好主意。 以下是实现所有这些的智能合约代码: 使用浏览器Solidity的一个主要优势是它提供了一个编辑器,并生成部署合约的代码。 在编辑器中,复制并粘贴上述合约代码。您会看到它编译并给出了使用Geth交互式控制台部署它的web3.js代码。 没有privateFor属性时,您将获得以下输出: web3.eth.contract的第一个参数是ABI定义。ABI定义包含所有方法的原型,并在创建交易时使用。 现在是部署智能合约的时候了。在进一步操作之前,请确保您启动了我们在上一章中创建的由三个节点组成的raft网络。我们将假设这三个节点来自三个不同的企业。还要确保您已启用constellation,并复制所有constellation成员的公钥。在privateFor数组中,用您生成的公钥替换它们。在这里,我将私有智能合约对所有三个网络成员可见。 privateFor仅在发送私有事务时使用。它被分配给一个接收者的base64编码的公钥数组。在上述代码中,在privateFor数组中,我只有两个公钥。这是因为发送者不必将其公钥添加到数组中。如果添加,那么将会引发错误。 在第一个节点的交互式控制台中,使用personal.unlockAccount(web3.eth.accounts[0],"",0)来无限期地解锁以太坊账户。 现在让我们存储文件详细信息并检索它们。假设前两个实体已签署协议并希望将文件的详细信息存储在区块链上。将以下代码放置以广播一个事务以存储文件的详细信息: 现在运行此代码以查找文件的详细信息: contract_obj.get.call("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");您将获得此输出: [1477591434,"OwnerName"]调用方法用于在EVM上调用合同的方法以及当前状态。它不广播事务。要读取数据,我们不需要广播,因为我们将有自己的区块链副本。 如果在节点3中运行上述代码,则不会得到任何细节,因为数据对第三个实体不可见。但第一个和第二个节点可以读取细节。在接下来的章节中,我们将更多地了解web3.js。 在本章中,我们学习了Solidity编程语言。我们学习了数据位置,数据类型以及合约的高级特性。我们还学习了编译和部署智能合约的最快最简单的方法。现在你应该能够轻松编写智能合约了。 在下一章中,我们将为智能合约构建一个前端,这将使得部署智能合约和运行交易变得容易。