当前位置:   article > 正文

区块链高级开发教程(三)_区块链教程

区块链教程

原文:zh.annas-archive.org/md5/64e2728fdd6fa177d97883a45d7dec42

译者:飞龙

协议:CC BY-NC-SA 4.0

第十章:另类货币

自比特币的初期成功以来,许多另类货币项目已经推出。比特币于 2009 年发布,第一个替代币项目(名为 Namecoin)于 2011 年推出。在 2013 年和 2014 年,另类货币(又称另类币)市场呈爆炸性增长,开启了许多不同类型的另类货币项目。

其中一些取得了成功,而许多因缺乏兴趣而不受欢迎,并因此未获成功。有一些是炒作和抛售骗局,短暂出现但很快就消失了。比特币的替代方法可以大致分为两大类,根据其开发的主要目的。如果主要目标是构建一个去中心化的区块链平台,它们被称为另类链;如果替代项目的唯一目的是推出新的虚拟货币,它就被称为另类货币。

另类区块链将在第十四章“另类区块链”中详细讨论。

本章主要专注于旨在推出新的虚拟货币(币)的另类货币,尽管也会介绍一些建立在比特币之上提供各种服务的另类协议的内容。其中包括 Namecoin 等概念,其主要目的是提供去中心化的命名和身份服务而不是货币。

目前,截止 2018 年底,市场上有数百种另类货币,它们具有一定的货币价值,如 Namecoin、Zcash、Primecoin 等。我们将在本章的后面部分对其中一些进行详细考察。Zcash 是一种成功的另类货币,于 2016 年推出。另一方面,Primecoin 并没有获得太多的关注,但仍在使用。许多另类项目是比特币源代码的直接分叉,尽管有些是从头开始编写的。一些另类货币旨在解决比特币的限制,如隐私问题。其他一些提供不同类型的挖矿、改变区块时间和分配方案。

根据定义,硬分叉的情况下会产生一种替代币。如果比特币发生硬分叉,则其他旧链有效地被认为是另一种货币。然而,尚无确立的规则规定哪条链成为替代币。这在以太坊中已经发生过,其中一次硬分叉导致了一种新的货币以太坊经典ETC)的产生,除了以太坊ETH)货币之外。以太坊经典是旧链,而以太是硬分叉后的新链。这样具有争议的硬分叉出于某些原因是不可取的。首先,它违背了去中心化的真正精神,因为以太坊基金会,一个中央实体,决定继续进行硬分叉,即使并非每个人都同意该提议;第二,它还由于对硬分叉的分歧而分裂了用户社区。尽管硬分叉在理论上会生成一种替代币,但它在提供的内容上受到限制,因为即使更改导致硬分叉,通常在货币的基本参数周围也没有重大变化。它们通常保持不变。出于这个原因,最好要么从头开始编写一个新币,要么分叉比特币(或其他币种的源代码)来创建一个具有所需参数和功能的新货币。

替代币必须能够吸引新用户、交易和矿工,否则该货币将毫无价值。货币获得其价值,特别是在虚拟货币领域,是由于网络效应和社区的可接受性。如果一种货币未能吸引足够多的用户,那么很快它将被遗忘。通过提供初始数量的货币可以吸引用户,并且可以通过使用各种方法实现。然而,存在一种风险,即如果新币表现不佳,则他们的初始投资可能会丢失。提供初始数量的替代币的方法如下所述:

  • 创建一个新的区块链:替代币可以创建一个新的区块链,并向初始矿工分配货币,但由于许多欺诈计划或炒作和倾销计划,这种方法现在不受欢迎,其中初始矿工在推出新货币时获利,然后消失了。

  • 燃烧证明(PoB):另一种为新的替代币分配初始资金的方法是 PoB,也称为单向锚定或价格上限。在这种方法中,用户永久性地销毁一定数量的比特币,与要认领的替代币的数量成比例。例如,如果销毁了十个比特币,则替代币的价值不得高于一些被销毁的比特币。这意味着比特币通过销毁转换为替代币。

  • 所有权证明:与永久销毁比特币不同,一种替代方法是证明用户拥有一定数量的比特币。这种所有权证明可以用来通过将替代币区块与比特币区块绑定来声明替代币。例如,这可以通过合并挖矿来实现,在合并挖矿中,比特币矿工可以在挖掘比特币的同时挖掘替代币区块,而无需额外工作。合并挖矿将在本章后面解释。

  • 固定侧链:侧链,顾名思义,是与比特币网络分开的区块链,但比特币可以转移到它们。替代币也可以转回比特币网络。这个概念被称为双向锚定

投资和交易这些替代币也是一个大生意,尽管不及比特币大,但足以吸引新的投资者和交易者,并为市场提供流动性。综合替代币市值如下所示:

此图表由coinmarketcap.com/生成。

此图表显示,写作时的综合替代币市值超过 2000 亿美元。

当前前十大币的市值(截至 2018 年 3 月)如下所示:

名称市值价格美元
Bitcoin$151,388,873,045$8,951.83
Ethereum$68,472,253,587$697.94
Ripple$31,340,920,806$0.801723
Bitcoin Cash$17,182,167,856$1,010.08
Litecoin$9,952,905,688$179.11
NEO$5,638,100,000$86.74
Cardano$5,450,310,987$0.210217
Stellar$5,438,720,268$0.294010
EOS$4,347,501,290$6.04
Monero$4,211,690,257$266.40

数据来自coinmarketcap.com/

伴随替代币出现了各种因素和新概念。许多概念甚至是在比特币之前就发明了,但随着比特币的出现,不仅引入了新概念,如拜占庭将军问题的解决方案,还巧妙地使用了以前的想法,如 hashcash 和工作证明PoW),并受到了关注。

从那时起,随着替代币项目的推出,各种新技术和概念得到了发展和介绍。要欣赏当前替代加密货币的景观,首先理解一些理论概念是必不可少的。

理论基础

在本节中,介绍了各种理论概念,这些概念是在过去几年引入不同替代币的同时发展起来的。

工作证明的替代方案

在加密货币的背景下,PoW 方案首次在比特币中使用,并作为一种机制来确保矿工已经完成了找到一个区块所需的工作量。作为回报,此过程为区块链提供了去中心化、安全性和稳定性。这是比特币提供去中心化分布式共识的主要手段。PoW 方案需要具有一种非常理想的属性,称为进展自由性,这意味着消耗计算资源的奖励应该是随机的,并且与矿工的贡献成正比。在这种情况下,即使是那些具有相对较少计算能力的矿工也有一定机会赢得区块奖励。

进展自由这个术语是由 Arvind Narayanan 等人在书籍《比特币和加密货币技术》中首次提出的。挖矿计算难题的其他要求包括可调节难度和快速验证。可调节难度确保区块链挖矿的难度目标根据增加的哈希功率和用户数量进行调节。

快速验证是一个属性,意味着挖矿计算难题的验证应该简单而快速。PoW 方案的另一个方面,特别是比特币中使用的方案(双 SHA-256),是自从 ASIC 引入以来,权力正在向能够承担大规模 ASIC 农场运营成本的矿工或矿池转移。这种权力转移挑战了比特币去中心化的核心理念。

已经提出了一些替代方案,例如抗 ASIC 的难题,并且设计得这样一种方式,使得为解决此难题而构建 ASIC 变得不可行,并且不会带来与商品硬件相比的主要性能增益。用于此目的的常见技术之一是应用一类称为内存难的计算难题的计算上难问题。此方法背后的核心思想是,由于解决难题需要大量内存,因此在基于 ASIC 的系统上实现它是不可行的。

这种技术最初是在 Litecoin 和 Tenebrix 中使用的,其中 Scrypt 哈希函数被用作抗 ASIC 的 PoW 方案。尽管最初宣传此方案为 ASIC 抗性,但最近 Scrypt ASIC 现已面世,证明了 Litecoin 最初的声明不正确。这是因为 Scrypt 是一种内存密集型机制,最初认为由于技术和成本限制,构建具有大容量内存的 ASIC 难度较大。但现在情况已经改变,因为内存变得越来越便宜,并且有能力生产纳米级电路,可以构建能够运行 Scrypt 算法的 ASIC。

抵抗 ASIC 的另一种方法是需要计算多个哈希函数来提供 PoW。这也称为 链式哈希方案。这个想法的理论基础是,在 ASIC 上设计多个哈希函数并不是很可行。最常见的例子是 Dash 中实现的 X11 内存硬函数。X11 包括 11 个 SHA-3 竞争者,其中一个算法将计算出的哈希输出到下一个算法,直到所有 11 个算法都按顺序使用。这些算法包括 BLAKE、BMW、Groestl、JH、Keccak、Skein、Luffa、CubeHash、SHAvite、SIMD 和 ECHO。

此方法最初确实对 ASIC 的发展提供了一些阻力,但现在 ASIC 矿工已经在商业上可获得,并支持 X11 和类似方案的挖矿。最近的一个例子是 ASIC Baikal Miner,它支持 X11、X13、X14 和 X15 挖矿。其他例子包括像 iBeLink DM384M X11 矿工和 PinIdea X11 ASIC 矿工这样的矿工。

可能还有另一种方法,就是设计智能或随机更改 PoW 方案或其要求的自变化谜题。这种策略将使其几乎不可能在 ASIC 中实现,因为它将需要为每个功能设计多个 ASIC,并且随机更改方案在 ASIC 中几乎不可能处理。目前尚不清楚如何在实践中实现这一点。

PoW 确实有各种缺点,其中最大的缺点就是能源消耗。据估计,目前比特币矿工消耗的总电量超过孟加拉国,达到了 54.69 太哈希TWh)。这是巨大的,而且所有的能源在某种程度上都是浪费的;事实上,除了挖矿之外并没有任何有用的目的。环保人士对这种情况提出了真实的担忧。除了电力消耗之外,碳足迹目前也非常高,每笔交易约为 387 千克 CO[2]。

以下图表显示了比特币能源消耗规模与其他国家相比的情况。这只会不断增长,据估计,到 2018 年底,能源消耗量可能达到约 125 TWh 每年。

各国的能源消耗

所示的前面的图表摘自跟踪此主题的网站。它可在 digiconomist.net/bitcoin-energy-consumption 上找到。

有人提出,PoW 难题可以被设计成具有两个目的。首先,它们的主要目的是在共识机制中,其次是进行一些有用的科学计算。通过这种方式,不仅可以将方案用于挖矿,还可以有望帮助解决其他科学问题。这种有用工作证明最近由 Primecoin 实行,其要求是找到特殊的素数链,即卡宁汉链和双胞素链。由于素数分布的研究在物理等科学学科中具有特殊意义,通过挖掘 Primecoin,矿工不仅可以获得区块奖励,还可以帮助寻找特殊的素数。

存储证明

也被称为可检索性证明,这是另一种有用工作证明类型,需要存储大量数据。由微软研究引入,这种方案为分布式存储提供了有用的好处。矿工需要存储一部分伪随机选择的大量数据以进行挖矿。

权益证明(PoS)

这种证明也被称为虚拟挖矿。这是另一种替代传统 PoW 方案的挖矿难题。它最早在 2012 年 8 月由 Peercoin 提出。在这种方案中,用户需要证明拥有一定数量的货币(硬币),从而证明他们对该货币有一定的权益。最简单的权益形式是,采用相对更容易的挖矿方式来奖励那些拥有更多数字货币的用户。这种方案的优点是双重的;首先,相对于购买高端 ASIC 设备来说,获得大量的数字货币相对困难,其次,这样做可以节省计算资源。已经提出了各种形式的权益,并在下面的小节中简要讨论。

各种权益类型

不同类型的权益将在下面的小节中介绍。

货币权益证明

货币的年龄指的是货币上次使用或持有的时间。这与通常的 PoS 形式不同,在这种方式中,对于持有另类币中最大权益的用户来说,挖矿变得更容易。在基于货币年龄的方法中,每次挖出一个区块时,货币的年龄(货币权益)都会被重置。矿工因为持有而不使用货币一段时间而获得奖励。这种机制已经在 Peercoin 中以一种创造性的方式与 PoW 结合实施。

挖矿难题(PoW)与货币的时间成反比,这意味着如果矿工使用货币进行货币权益交易,那么 PoW 的要求就会得到缓解。

存款证明(PoD)

这一方案的核心思想是,矿工新生产的区块在一定时间内无法使用。更确切地说,这些硬币在挖矿过程中被锁定了一定数量的区块。该方案允许矿工在冻结一定数量的硬币一段时间的代价下进行挖矿。这是一种 PoS(权益证明)的类型。

燃烧证明

作为计算能力的备用支出,PoB 实际上是销毁一定数量的比特币,以获得相应的另类币。这在启动新的代币项目时通常被使用,作为一种公平分配的手段。这可以被看作是一种替代的挖矿方案,因为新币的价值来自于之前销毁了一定数量的硬币。

活动证明(PoA)

该方案是 PoW 和 PoS 的混合体。在该方案中,区块最初是通过 PoW 产生的,然后每个区块会随机分配三个利益相关者来对其进行数字签名。后续区块的有效性取决于先前随机选择的区块成功签名的情况。

然而,有一个可能的问题被称为“利益无所不利”问题,即一个人可以轻易创建一个区块链的分叉。这是可能的,因为在 PoW(工作量证明)中需要适当的计算资源来进行挖矿,而在 PoS 中没有这样的要求;因此,攻击者可以尝试使用相同的硬币在多个链上进行挖矿。

无法外包的难题

这一难题的关键动机是抵抗挖矿池的发展。先前讨论过的挖矿池向所有参与者提供奖励,比例与他们消耗的计算能力成正比。然而,在这种模式中,挖矿池操作员是一个能够强制执行特定规则的中央当局,所有的奖励都归该操作员所有。此外,在此模式下,所有的矿工只信任彼此,因为他们共同致力于实现共同目标,希望挖矿池经理能获得奖励。非外包难题是一种允许矿工自己声称奖励的方案;因此,由于匿名矿工之间的内在不信任,挖矿池的形成变得不太可能。

也存在各种其他替代方案以替代 PoW,其中一些已在第一章中描述,区块链 101,而另一些将在本书的后续章节中进行解释,包括第十三章,Hyperledger和第十六章,可扩展性和其他挑战。由于这是一个正在进行研究的领域,随着区块链技术的发展,新的替代方案将不断出现。

难度调整和重新定位算法

随着比特币和另类币的出现,另一个概念引入了难度调整算法。在比特币中,难度目标的计算非常简单,只需下面的等式;然而其他的货币要么开发了自己的算法,要么实现了修改版的比特币难度算法:

T = 先前时间 * 实际时间 / 2016 * 10 分钟

比特币中难度调整的理念是,生成 2016 个区块应该大约需要两周时间(区块之间的时间约为 10 分钟)。如果挖掘 2016 个区块超过两周的时间,那么难度会降低,如果挖掘 2016 个区块少于两周的时间,那么难度会增加。当由于高区块生成速率而引入 ASIC 时,难度呈指数增长,这就是非 ASIC 防护 PoW 算法的一个缺陷。这导致了挖矿算力的集中化。

这也带来了另一个问题;如果一个新的加密货币现在开始,采用与比特币相同的基于 SHA-256 的 PoW,那么恶意用户可以很容易地使用 ASIC 矿工来控制整个网络。如果对新的另类币没有太多兴趣,某人决定消耗足够高的计算资源来接管网络,这种攻击就会更加实际。如果其他具有类似计算能力的矿工也加入了另类币网络,这种攻击可能并不可行,因为矿工们会互相竞争。

此外,多矿池也带来了更大的威胁,一群矿工可以自动切换到变得有利可图的货币。这种现象被称为矿池跳跃,它会对区块链产生不利影响,从而影响另类币的增长。矿池跳跃对网络的影响是不利的,因为跳池者只在难度低并且可以获得快速奖励时加入网络;一旦难度提高(或者重新调整),他们就会退出,然后等难度重新调整后再次加入。

例如,如果一个多矿池迅速消耗资源来挖掘新币,难度将非常快地增加;当多矿池离开货币网络时,它将几乎无法使用,因为现在难度已经增加到不再对独立矿工有利并且无法维持的程度。解决这个问题的唯一方法是启动一次硬分叉,而这通常对社区来说是不可取的。

有一些算法出现来解决这个问题,后面的章节会讨论这些算法。所有这些算法都基于对哈希率变化做出响应的想法,这些参数包括先前块的数量、先前块的难度、调整比率以及难度可以进行回调或提高的数量。

在以下部分,读者将介绍各种替代币中正在使用和提出的少数难度算法。

Kimoto 引力井

此算法用于各种替代币来调节难度。该方法首次在 Megacoin 中引入,并用于自适应地调整网络每个区块的难度。算法的逻辑如下所示:

KGW = 1 + (0.7084 * pow((double(PastBlocksMass)/double(144)), -1.228))

该算法运行在一个循环中,通过一组预定的区块(PastBlockMass)并计算新的重新调整值。该算法的核心思想是开发一种自适应难度调节机制,以响应哈希率的快速波动。Kimoto 引力井KGW)确保区块之间的时间大致相同。在比特币中,难度每 2016 个区块调整一次,但在 KGW 中,难度在每个区块中调整。

此算法容易受到时间扭曲攻击的影响,这种攻击允许攻击者在创建新区块时暂时享受较低的难度。这种攻击允许一个时间窗口,其中难度变低,攻击者可以快速以较快的速度生成许多硬币。

更多信息请参见链接cryptofrenzy.wordpress.com/2014/02/09/multipools-vs-gravity-well/

黑暗引力波

黑暗引力波DGW)是一种新算法,旨在解决 KGW 算法中的某些缺陷,比如时间扭曲攻击。这个概念最初是在 Dash 中引入的,以前被称为 Darkcoin。它利用多个指数移动平均和简单移动平均来实现更平滑的重新调整机制。该公式如下所示:

2222222/ (((Difficulty+2600)/9)²)

此公式已在 Dash 币、比特币 SegWit2X 和其他各种替代币中实施,作为重新调整难度的机制。

DGW 版本 3.0 是 DGW 算法的最新实现,与 KGW 相比,可以实现更好的难度重新调整。

更多信息请参见dashpay.atlassian.net/wiki/spaces/DOC/pages/1146926/Dark+Gravity+Wave

DigiShield

这是另一种最近在 Zcash 中使用的难度重新调整算法,经过适当的变化和实验。该算法通过查看固定数量的先前区块来计算它们生成所花费的时间,然后通过将实际时间跨度除以目标时间的平均值来重新调整难度到前一个区块的难度。在这个方案中,重新调整计算得更快,而且从哈希率的突然增加或减少中恢复速度快。这个算法可以防止多重池,这可能导致哈希率的快速增加。

网络难度根据实现方式每个区块或每分钟调整一次。关键创新是相对于 KGW 更快的调整时间。

Zcash 使用 DigiShield v3.0,该版本使用以下公式进行难度调整:

(新难度) = (先前难度) x SQRT [ (150 秒) / (上次解决时间)

有关此问题的详细讨论可在github.com/zcash/zcash/issues/147#issuecomment-245140908找到。

MIDAS

多间隔难度调整系统MIDAS)是一个相对于先前讨论的算法更复杂的算法,因为它使用了更多的参数。这种方法对哈希率的突然变化做出了更快的响应。该算法还可以防止时间扭曲攻击。

有关此事的原始帖子现在可以通过网络档案在web.archive.org/web/20161005171345/http://dillingers.com/blog/2015/04/21/altcoin-difficulty-adjustment-with-midas/找到。

有兴趣的读者可以在前述位置阅读更多信息。

这就结束了我们对各种难度调整算法的介绍。

许多替代加密货币和协议出现,试图解决比特币的各种限制。

比特币的限制

比特币的各种限制也引起了人们对替代币的兴趣,这些替代币专门用于解决比特币的限制。最突出和广泛讨论的限制是比特币缺乏匿名性。我们现在将讨论一些比特币的限制。

隐私和匿名性

由于区块链是所有交易的公开账本,并且是公开可用的,因此分析它变得微不足道。结合流量分析,交易可以追溯到其源 IP 地址,因此可能会揭示交易的发起者。从隐私的角度来看,这是一个很大的问题。

即使在比特币中建议并常见的做法是为每笔交易生成一个新的地址,从而实现一定程度的不可链接性,但这并不足够,已经开发并成功使用各种技术来追踪整个网络中的交易流动,并将其追溯到其发起者。这些技术通过使用交易图、地址图和实体图分析区块链,从而帮助将用户与交易联系起来,引发了隐私方面的担忧。前述分析中提到的技术可以通过使用有关交易的公开信息并将其与实际用户联系起来得到进一步丰富。有可用的开源区块解析器可用于从区块链数据库中提取交易信息、余额和脚本。

一个可在Rust 语言中使用的解析器提供了先进的区块链分析能力。

已经提出了各种提案来解决比特币的隐私问题。这些提案可分为三类:混合协议、第三方混合网络和固有匿名。

每个类别的简要讨论如下。

混合协议

这些方案被用来为比特币交易提供匿名性。在这种模式中,使用混币服务提供商(中介或共享钱包)。用户将硬币发送到这个共享钱包作为存款,然后,共享钱包可以将其他用户存入的价值相同的一些其他硬币发送到目的地。用户也可以通过这个中介收到他人发送的硬币。这样,输出与输入之间的链接将不复存在,交易图分析将无法揭示发送者和接收者之间的实际关系。

三名用户将其交易合并为单个更大的 CoinJoin 交易的 CoinJoin 交易

CoinJoin 是混合协议的一个例子,其中两个交易合并在一起形成一笔交易,同时保持输入和输出不变。CoinJoin 背后的核心思想是构建一个由所有参与者签署的共享交易。这种技术提高了参与交易的所有参与者的隐私性。

第三方混合协议

有各种第三方混币服务可供选择,但如果服务是集中的,那么追踪发送者和接收者之间的映射就会带来威胁,因为混币服务知道所有的输入和输出。此外,完全集中式的矿工甚至会带来服务的管理员偷取硬币的风险。

基于 CoinJoin(混合)交易的各种服务,复杂程度各不相同,例如 CoinShuffle、Coinmux 和 Dash(币)中的 Darksend。CoinShuffle 是传统混合服务的分散替代方案,因为它不需要可信第三方。

基于 CoinJoin 的方案,然而,存在一些弱点,其中最突出的是用户可能发动拒绝服务攻击,这是由于最初承诺签署交易的用户现在没有提供他们的签名,从而延迟或停止了联合交易。

内在匿名性

这个类别包括支持隐私的硬币,隐私已经内置到货币的设计中。最流行的是 Zcash,它使用零知识证明ZKP)来实现匿名性。后面的章节将对此进行详细讨论。其他例子包括 Monero,它利用环签名提供匿名服务。

下一节介绍了已经或正在提出的各种增强功能,以扩展比特币协议。

在比特币上扩展的协议

在下文讨论的几个协议中,已经提出并实施了一些协议,以增强和扩展比特币协议,并将其用于各种其他目的,而不仅仅是作为一种虚拟货币。

彩色硬币

彩色硬币是一组用于在比特币区块链上表示数字资产的方法。俗称为给比特币着色,是指使用一些元数据更新它,这些元数据代表着数字资产(智能资产)。硬币仍然作为比特币工作和运行,但另外携带一些表示某些资产的元数据。这可以是与资产相关的一些信息,与交易相关的一些计算或任何任意数据。此机制允许发行和跟踪特定的比特币。可以使用比特币的OP_RETURN操作码或可选地在多重签名地址中记录元数据。如果需要解决任何隐私问题,此元数据也可以加密。一些实现还支持在公开可用的种子网络上存储元数据,这意味着可以存储几乎无限量的元数据。通常这些是表示彩色硬币各种属性的 JSON 对象。此外,还支持智能合约。这样的实现例子是 Colu,可在colu.co/找到。

彩色硬币可以用来代表多种资产,包括但不限于商品、证书、股票、债券和投票。需要注意的是,要使用彩色硬币,需要一个解释彩色硬币的钱包,而普通的比特币钱包则不起作用。普通的比特币钱包无法区分彩色硬币非彩色硬币

可以使用www.coinprism.com/提供的服务在线设置彩色硬币钱包。通过使用此服务,可以创建并发行任何数字资产。

彩色硬币的概念非常吸引人,因为它不需要对现有比特币协议进行任何修改,可以利用已经存在的安全比特币网络。除了传统的数字资产表示外,还有可能创建根据其定义的参数和条件行为的智能资产。这些参数包括时间验证、转让限制和费用。

重要的用例可以是在区块链上发行金融工具。这将确保低交易费用、有效且数学上安全的所有权证明、快速的可转让性,无需中介即可快速支付股息给投资者。

丰富的 API 可在coloredcoins.org/找到彩色硬币。

Counterparty

这是另一个服务,用于创建充当加密货币的自定义代币,可用于各种用途,例如在比特币区块链上发行数字资产。这是一个非常强大的平台,以比特币区块链为核心,但开发了自己的客户端和其他组件来支持发行数字资产。架构包括 counterparty 服务器、counter block、counter wallet 和armory_utxsvr。 Counterparty 基于与彩色硬币相同的想法,通过嵌入数据到常规比特币交易中,但提供了更加高效的库和一套强大的工具来支持数字资产的处理。这种嵌入也被称为嵌入式共识,因为 counterparty 交易嵌入在比特币交易中。嵌入数据的方法是使用比特币中的OP_RETURN操作码。

Counterparty 生产和使用的货币被称为 XCP,并被智能合约用作运行合约的费用。撰写时,其价格为 2.78 美元。 XCP 是通过使用先前讨论过的 PoB 方法创建的。

Counterparty 允许使用 solidity 语言在以太坊上开发智能合约,并允许与比特币区块链进行交互。为实现这一目的,使用 BTC Relay 作为在以太坊和比特币之间提供互操作性的手段。这是一个巧妙的概念,其中以太坊合约可以与比特币区块链和交易进行通信。转发器(运行 BTC Relay 的节点)获取比特币块头并将其传输到以太坊网络上的智能合约,验证 PoW。此过程验证了在比特币网络上发生了交易。

可在btcrelay.org/找到。

从技术上讲,这是一种以太坊合约,能够存储和验证比特币区块头,就像比特币简单支付验证轻量级客户端使用布隆过滤器一样。SPV 客户端在前几章中详细讨论过。这个想法可以用以下图表来可视化:

BTC 中继概念

Counterparty 可以在counterparty.io/找到。

山寨币的发展

从编码的角度来看,通过简单地分叉比特币或其他币的源代码,可以非常快速地启动山寨币项目,但这可能还不够。当启动一个新的币项目时,需要考虑几件事情,以确保成功启动和币的长期存在。通常,代码库以 C++编写,就像比特币的情况一样,但几乎任何语言都可以用来开发币项目,例如 Golang 或 Rust。

编写代码或分叉现有币的代码是微不足道的,挑战性的问题是如何启动一种新货币,以吸引投资者和用户。通常,以下步骤被采取以启动一个新的币项目。

从技术角度来看,如果在另一个币的代码上进行分叉,例如比特币,有各种参数可以更改以有效地创建一个新的币。为了创建一个新币,需要调整或引入这些参数。这些参数可以包括但不限于以下内容。

共识算法

有多种共识算法可供选择,例如比特币中使用的 PoW 或 Peercoin 中使用的 PoS。还有其他可用的算法,如容量证明PoC)和其他一些算法,但 PoW 和 PoS 是最常见的选择。

哈希算法

这要么是 SHA-256、Scrypt、X11、X13、X15,或者是任何其他适合用作共识算法的哈希算法。

难度调整算法

在这个领域有多种选择可提供难度重新定位机制。最突出的例子是 KGW、DGW、Nite’s Gravity Wave 和 DigiShield。此外,所有这些算法都可以根据要求进行调整以产生不同的结果;因此,可能存在许多变体。

区块间时间

这是每个区块生成之间经过的时间。对于比特币,区块每 10 分钟生成一次,对于莱特币,每 2.5 分钟生成一次。任何值都可以使用,但适当的值通常在几分钟之间;如果生成时间太快,可能会使区块链不稳定,如果太慢,可能不会吸引许多用户。

区块奖励

区块奖励是给解决挖矿难题的矿工的,他们被允许拥有一个包含奖励的 coinbase 交易。比特币最初为 50 个硬币,现在许多山寨币将这个参数设置为非常高的数字;例如,狗狗币目前为 10,000 个。

奖励减半率

这是另一个重要因素;在比特币中,它每 4 年减半一次,现在设置为 12.5 个比特币。这是一个可变的数字,可以根据要求设置为任何时间段或根本不设置。

区块大小和交易大小

这是确定网络上交易速率高低的另一个重要因素。比特币中的区块大小限制为 1 MB,但在替代币中,它可以根据要求而变化。

利率

这个属性仅适用于 PoS 系统,在这些系统中,货币的所有者可以按照网络定义的利率获得利息,以换取在网络上保留的一些货币,作为保护网络的抵押。这个利率可以控制通货膨胀。如果利率太低,那么可能会导致恶性通货膨胀。

货币

这个参数定义了货币必须保持未使用多长时间才能成为符合抵押资格。

货币的总供应量

这个数字设置了可以生成的货币的总限制。例如,在比特币中,限制是 2100 万,而在狗狗币中是无限的。这个限制是由前面讨论的区块奖励和减半时间表固定的。

创建自己的虚拟货币有两种选择:分叉现有的已建立的加密货币源代码或从头开始编写一个新的。后者选项不太流行,但第一个选项更容易,并且在过去几年中已经允许了许多虚拟货币的创建。基本上,思路是首先分叉加密货币源代码,然后在源代码的不同战略位置进行适当的更改,从而有效地创建一个新的货币。NEM 币是其中一个完全从头开始编写其代码的新创建的币种。

在下一节中,读者将介绍一些替代币项目。本章节不可能涵盖所有的替代货币,但在下一节中将讨论一些精选的币种。选择是基于其长期性、市值和创新性。每种币种都从不同的角度进行讨论,如理论基础、交易和挖掘。

姓名币

姓名币是比特币源代码的第一个分叉。姓名币背后的关键思想不是制造一种替代币,而是提供改进的去中心化、抗审查、隐私、安全和更快的去中心化命名。去中心化命名服务旨在应对传统互联网上使用的域名系统(DNS)协议的固有局限,如缓慢和集中控制。姓名币也是解决 Zooko 三角形问题的第一个解决方案,这个问题在第一章区块链 101中简要讨论过。

Namecoin 主要用于提供注册键/值对的服务。Namecoin 的一个主要用例是,它可以提供基于区块链的分布式和去中心化共识驱动的传输层安全TLS)证书验证机制。

它基于与比特币相同的技术,但具有自己的区块链和钱包软件。

Namecoin 核心的源代码可在github.com/namecoin/namecoin-core获得。

总之,Namecoin 提供以下三项服务:

  • 保证名字(键)的安全存储和传输

  • 将一些值附加到名称上,通过附加高达 520 字节的数据

  • 生产数字货币(Namecoin)

Namecoin 还首次引入了合并挖矿,这允许矿工同时在多个链上进行挖矿。这个想法很简单,但非常有效:矿工创建一个 Namecoin 区块并生成该区块的哈希。然后将该哈希添加到比特币区块中,并且矿工解决该区块的难度等于或大于 Namecoin 区块难度,以证明已经为解决 Namecoin 区块做出了足够的工作。

Coinbase 交易用于在 Namecoin 的交易哈希(或任何其他山寨币,如果与该币种合并挖矿)中包含哈希。挖矿任务是解决比特币区块,其 coinbase scriptSig包含对 Namecoin(或任何其他山寨币)区块的哈希指针。如下图所示:

合并挖矿可视化

如果矿工设法以比特币区块链难度水平解决哈希,则比特币区块建立并成为比特币网络的一部分。在这种情况下,比特币区块链会忽略 Namecoin 哈希。另一方面,如果矿工以 Namecoin 区块链难度水平解决一个区块,则在 Namecoin 区块链中将创建一个新区块。该方案的核心优势在于,矿工所花费的所有计算能力都有助于保护 Namecoin 和比特币。

交易 Namecoin

截至 2018 年 3 月,Namecoin 的当前市值为 29143884 美元,可在coinmarketcap.com/购买和出售。可在各种交易所进行交易,例如:

获得 Namecoin

即使 Namecoin 可以独立挖矿,通常也会通过利用合并挖矿技术作为比特币的一部分进行挖矿。这样,Namecoin 可以作为比特币挖矿的副产品。正如前面所述的难度图表所表明的,独立挖矿已经不再有利可图;相反,建议使用合并挖矿,使用矿池,甚至使用加密货币交易所购买 Namecoin。

Namecoin 难度如下所示:https://bitinfocharts.com/comparison/difficulty-nmc.html(自 2016 年 12 月以来)

各种挖矿池,比如slushpool.com,也提供合并挖矿的选项。这允许矿工主要挖掘比特币,同时也因此获得 Namecoin。

另一种快速获得一些 Namecoins 的方法是用你已有的硬币与 Namecoins 交换,例如,如果你已经有一些比特币或其他可以用来与 Namecoin 交换的加密货币。

一个在线服务,shapeshift.io/,可提供此服务。该服务允许使用简单易用的界面从一种加密货币转换为另一种加密货币。

例如,支付 BTC 以接收 NMC 如下所示:

  1. 首先选择存款货币,本例中是比特币,然后选择要收到的货币,本例中是 Namecoin。在顶部的编辑框中,输入您希望接收交换的 Namecoin 的 Namecoin 地址。在底部的第二个编辑框中,输入比特币退款地址,在任何情况下交易失败时,货币将退还到该地址。

  2. 一旦选定存款和交换货币,汇率和矿工费用会在选择后立即计算。汇率受市场条件驱动,而矿工费用根据选择的目标货币以及目标网络的矿工收费算法计算。

比特币到 Namecoin 交换

  1. 点击“开始交易”后,交易开始并指示用户发送比特币到特定的比特币地址。当用户发送所需金额时,转换过程如下截图所示。整个过程需要几分钟:

Namecoin 交付通知

前面的截图显示,在发送存款后,交换发生,最终显示“All Done!”消息,表明交换成功。

页面上显示了一些其他订单细节,比如存入的货币以及交换后收到的货币。在这种情况下,它是比特币到 Namecoin 的交换。值得注意的是,与每个币种图标下面也显示了相关地址。还有一些其他选项,比如可以调用邮件收据来接收交易的邮件收据。

在流程完成时,可以在 Namecoin 钱包中查看交易,如下所示:

Namecoin 钱包

确认交易可能需要一些时间(通常约 1 小时),在此期间不可能使用 Namecoins 来管理名称。一旦 Namecoins 在钱包中可用,就可以使用“管理名称”选项来生成 Namecoin 记录。

生成 Namecoin 记录

Namecoin 记录是键值对的形式。名称是形式为 d/examplename 的小写字符串,而值是大小写敏感的、UTF-8 编码的 JSON 对象,最大长度为 520 字节。名称应符合 RFC1035(tools.ietf.org/html/rfc1035)的规定。

一个一般的 Namecoin 名称可以是最多 255 字节长的任意二进制字符串,附带 1024 位的相关标识信息。一个在 Namecoin 链上的记录仅在大约 200 天或 36,000 个块后才需要更新。Namecoin 还引入了 .bit 顶级域,可以使用 Namecoin 注册,并且可以使用专用的 Namecoin 启用的解析器进行浏览。如下图所示的 Namecoin 钱包软件可用于注册 .bit 域名。

输入名称后,点击提交按钮,将要求输入配置信息,如 DNS、IP 或身份:

Namecoin 钱包:域名配置

如下面的截图所示,masteringblockchain 将在 Namecoin 区块链上注册为 masteringblockchain.bit

Namecoin 钱包:显示已注册的名称

Litecoin

Litecoin 是源自于 2011 年发布的比特币源代码的分叉。它使用 Scrypt 作为 PoW,最初在 Tenebrix 币中引入。由于较快的区块生成时间为 2.5 分钟,Litecoin 允许比特币更快的交易。此外,由于更快的区块生成时间,大约每 3.5 天就进行一次难度调整。总的货币供应量是 8400 万。

Scrypt 是第一个替代 SHA-256 基础 PoW 算法的顺序内存硬函数。它最初被提议作为一个基于密码的密钥派生函数PBKDF)。其关键思想是,如果函数需要大量内存才能运行,那么定制硬件如 ASIC 将需要更多的 VLSI 区域,这将无法建造。Scrypt 算法需要在内存中保存一个大数组的伪随机位,并以伪随机方式从中派生密钥。

该算法基于一种称为时间-内存权衡TMTO)的现象。如果放宽了内存要求,则会导致计算成本增加。换句话说,如果给程序更多的内存,TMTO 将缩短其运行时间。这种权衡使得攻击者难以获取更多内存,因为这既昂贵又难以在定制硬件上实现,或者如果攻击者选择不增加内存,则由于高处理需求而导致算法运行缓慢。这意味着 ASIC 对于该算法难以构建。

Scrypt 使用以下参数生成派生密钥(Kd):

  • 口令:这是一个要进行哈希的字符字符串

  • Salt: 这是提供给 Scrypt 函数(通常是所有哈希函数)的随机字符串,以防御使用彩虹表的暴力字典攻击。

  • N: 这是必须是大于 1 的 2 的幂的内存/CPU 成本参数。

  • P: 这是并行化参数。

  • R: 这是块大小参数。

  • dkLen: 这是派生密钥的预期长度,以字节为单位。

形式上,此函数可写为:

Kd = scrypt (P, S, N, P, R, dkLen)

在应用核心 Scrypt 函数之前,该算法将 PS 作为输入,并应用 PBKDF2 和基于 SHA-256 的 HMAC。然后将输出馈送到称为 ROMix 的算法中,该算法内部使用 Blockmix 算法使用 Salsa20/8 核心流密码来填充内存,需要大内存才能运行,从而实现了顺序内存硬特性。

该算法的此步骤的输出最终再次馈送到 PBKDF2 函数中,以产生派生密钥。该过程如下图所示:

Scrypt 算法

Scrypt 在特定参数下用于莱特币挖矿,其中 N= 1024R = 1P=1S = 随机 80 字节 生成 256 位输出。

看起来,由于选择了这些参数,为莱特币挖矿的 Scrypt ASIC 的开发并不是非常困难。在用于莱特币挖矿的 ASIC 中,可以开发出一种顺序逻辑,将数据和随机数作为输入,并应用带有 HMAC-SHA256 的 PBKDF2 算法;然后将结果比特流输入到产生哈希的 SALSA20/8 函数中,该哈希再次馈入到 PBKDF2 和 HMAC-256 函数中以产生 256 位哈希输出。与比特币 PoW 的情况类似,在 Scrypt 中,如果输出哈希小于目标哈希(已在开始时作为输入传递,并存储在内存中,并在每次迭代中检查),则函数终止;否则,随机数递增,然后再次重复该过程,直到找到低于难度目标的哈希。

Scrypt ASIC 设计简化流程图

  • 莱特币交易: 与其他货币一样,莱特币的交易可以在各种在线交易所轻松进行。莱特币的当前市值为 10448974615 美元。莱特币的当前价格(截至 2018 年 3 月)为每个莱特币 188.04 美元。

  • 挖矿: 莱特币的挖矿可以单独进行,也可以在矿池中进行。目前,常用于挖掘莱特币的 ASIC 可用。

使用 CPU 进行 Litecoin 挖矿已经不再盈利,就像现在许多其他数字货币一样。现在有在线云挖矿提供商和可用的 ASIC 挖矿机,可以用来挖掘 Litecoin。Litecoin 挖矿从 CPU 开始,经过 GPU 挖矿机的发展,最终现在已经达到了一个点,需要使用专门的 ASIC 挖矿机,例如 Ehsminer 的 ASIC Scrypt Miner Wolf,以期能够获得一些硬币。一般来说,即使使用 ASIC,也最好在矿池中进行挖矿,而不是独立挖矿,因为由于矿池采用的比例奖励方案,独立挖矿并不像在矿池中挖矿那样有盈利。这些矿工能够为 Scrypt 算法产生 2 Gh/s 的哈希速率。

Primecoin

Primecoin 是市场上第一种引入有用 PoW 的数字货币,与比特币基于 SHA256 的 PoW 相对。Primecoin 使用寻找素数作为 PoW。并非所有类型的素数都符合被选为 PoW 的要求。三种类型的素数(称为第一类 Cunningham 链,第二类 Cunningham 链和双生链)满足了加密货币中使用 PoW 算法的要求。

Primecoin 区块链通过一个连续的难度评估方案动态调整难度。基于素数的 PoW 的高效验证也非常重要,因为如果验证速度慢,那么 PoW 就不适用。因此,素数链被选为 PoW,因为随着链的增长,找到素数链变得困难,而验证仍然足够快,可以作为高效的 PoW 算法使用。

一旦 PoW 在一个区块上验证过,它就不能在另一个区块上被重用,这一点非常重要。在 Primecoin 中,通过将 PoW 证书与子区块中的父区块头部进行哈希的方式来实现这一点。

PoW 证书是通过将素数链链接到区块头部哈希来生成的。它还要求区块头部的原点能够被区块头部哈希整除。如果可以,就进行除法运算,然后将商作为 PoW 证书使用。PoW 算法的可调节难度的另一个属性是,每个区块都要引入难度调整,而不是像比特币那样每 2,016 个区块进行一次。这是一个比比特币更平稳的方法,可以在哈希算力突然增加的情况下重新调整。此外,产生的总硬币数量是由社区驱动的,Primecoin 可以生成的硬币数量没有明确的限制。

Primecoin 交易

Primecoin 可以在主要虚拟货币交易所交易。截至撰写本文时(2018 年 3 月),Primecoin 的当前市值为 $17,482,507。虽然不是很大,但由于 Primecoin 基于一种新颖的想法,并且有一支专注的社区支持,这仍然保持一定的市场份额。

一张显示与 Primecoin 相关统计数据的图表

数据来源:https://coinmarketcap.com/currencies/primecoin/

挖矿指南

第一步是下载一个钱包。Primecoin 支持在钱包内进行本地挖矿,就像原始的比特币客户端一样,但也可以通过各种在线云服务提供商在云上挖矿。

以下是一个快速的 Windows 指南,Linux 客户端也可在 primecoin.io/downloads.php 上获得。

  1. 第一步是从 primecoin.io/index.php 下载 Primecoin 钱包。

  2. 一旦钱包安装并与网络同步,可以通过以下步骤开始挖矿。可以通过单击帮助菜单并选择调试窗口菜单项在 Primecoin 钱包中打开调试窗口。在调试窗口的控制台窗口中键入 help 可以调用其他帮助,用于启用 Primecoin 挖矿功能:

Primecoin 挖矿

  1. 一旦前述命令成功执行,挖矿将在单机模式下开始。如果您使用的是配置较低的个人电脑并且 CPU 较慢,这可能不会很快也不会很有利可图,但由于这是一种使用 CPU 挖掘的加密货币,矿工可以使用配备强大 CPU 的个人电脑。作为替代,可以使用托管强大服务器硬件的云服务:

Primecoin 钱包软件,与网络同步

Primecoin 源代码可在 github.com/primecoin/primecoin 上获得。

Primecoin 是一个新颖的概念,它引入的 PoW 具有很大的科学意义。它仍在使用,市值为 $17,034,198 美元,但从 GitHub 的不活跃可以看出,没有进行进一步的开发。

读者可以通过阅读 Sunny King(化名)的 Primecoin 白皮书进一步探索 Primecoin:primecoin.io/bin/primecoin-paper.pdf

Zcash

Zcash 在 2016 年 10 月 28 日推出。这是第一种使用特定类型的 ZKPs(称为零知识简洁非交互式知识证明ZK-SNARKs)为用户提供完全隐私的货币。这些证明简洁易验证;但是,设置初始公共参数是一个复杂的过程。后者包括两个密钥:证明密钥和验证密钥。该过程需要对一些随机数进行采样以构造公共参数。问题在于这些随机数,也称为有毒废物,必须在参数生成后销毁,以防止伪造 Zcash。

为此,Zcash 团队提出了一种多方计算协议,可以从独立位置协作生成所需的公共参数,以确保不会产生有毒废物。由于这些公共参数需要由 Zcash 团队创建,这意味着仪式参与者是受信任的。这就是为什么仪式非常开放,并通过使用多方计算机制进行的原因。

此机制具有一种属性,即必须破坏所有仪式参与者才能破坏最终参数。当仪式完成时,所有参与者都会摧毁用于生成私钥的设备。这一举动消除了设备上参与者私钥部分的任何痕迹。

ZK-SNARKs 必须满足完备性、准确性、简洁性和非交互性的属性。完备性意味着存在一种明确的策略,使证明者能够满足验证者断言为真。另一方面,准确性意味着没有证明者能够说服验证者错误的声明为真。简洁性意味着证明者和验证者之间传递的消息大小很小。

最后,非交互性质意味着可以在没有任何交互或非常少的交互情况下验证断言的正确性。此外,作为零知识证明,还需要满足零知识性质(在第六章中讨论,公钥密码学)

Zcash 开发人员引入了去中心化匿名支付方案DAP 方案)的概念,该方案用于在 Zcash 网络中实现直接和私密支付。交易不透露有关支付的来源、目的地和金额的任何信息。Zcash 中有两种类型的地址,Z 地址和 T 地址。Z 地址基于 ZKPs 并提供隐私保护,而 T 地址类似于比特币的地址。Zcash 各种属性的快照(经过初始缓慢启动后)如下所示:

Zcash 属性摘要

Zcash 使用一种名为非对称 PoW(Equihash)的高效 PoW 方案,它基于广义生日问题。它允许非常高效的验证。这是一种内存硬算法,抗 ASIC。Zcash 引入了一个新颖的概念(初始慢挖),这意味着区块奖励会在一段时间内逐渐增加,直到达到第 20,000 个区块。这使得网络可以进行初始扩展和早期矿工的试验,并在需要时由 Zcash 开发人员进行调整。由于稀缺性,慢启动对价格产生了影响,因此 ZEC 在推出的第一天达到了约 25,000 美元的价格。Zcash 中实现了稍作修改的 DigiShield 难度调整算法。公式如下所示:

(下一个难度) = (上一个难度) x 根号 [ (150 秒) / (上一个解决时间) ]

交易 Zcash

Zcash 可以在 CryptoGo (cryptogo.com) 等主要数字货币交易所购买。另一个可以购买或出售 Zcash 的交易所是 Crypto Robot 365 (cryptorobot365.com)。Zcash 推出时价格非常高。如下图所示,价格一度飙升至每个 Zcash 约十个比特币。一些交易所的订单甚至高达 2,500 BTC 每个 ZEC。撰写时(2018 年 3 月)ZEC 的价格约为 311 美元:

Zcash 市值和价格

挖矿指南

有多种方法可以挖掘 Zcash。目前,CPU 和 GPU 挖掘都是可能的。各种商业云挖矿池还提供挖掘 Zcash 的合约。要在 Ubuntu Linux 上使用 CPU 进行独立挖掘,可以按照以下步骤进行:

  1. 第一步是使用以下命令安装先决条件:
 $ sudo apt-get install \ 
        build-essential pkg-config libc6-dev m4 g++-multilib \   
        autoconf libtool ncurses-dev unzip git python \ 
        zlib1g-dev wget bsdmainutils automake 
  • 1
  • 2
  • 3
  • 4

如果先决条件已安装,将显示一条消息,指示组件已是最新版本。如果尚未安装或版本旧于最新包,则安装将继续,将下载所需软件包,并完成安装。

  1. 接下来,根据以下截图中所示的命令从 Git 克隆 Zcash:
 $ git clone https://github.com/zcash/zcash.git 
  • 1

请注意,如果您是第一次运行 git,则可能需要接受一些配置更改,这些更改将自动完成,但您可能需要进行交互式操作。

此命令将在本地克隆 Zcash Git 仓库。输出如下所示的截图:

克隆 Zcash Git 仓库

  1. 下一步是通过以下命令下载证明和验证密钥:
 $ ./zcutil/fetch-param.sh
  • 1

此命令将产生与此处所示相似的输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Zcash 设置获取 ZK-SNARK 参数

  1. 运行此命令后,将下载约 911 MB 的密钥到~/.zcash-params/目录中。该目录包含用于证明和验证密钥的文件:
 $ pwd 
      /home/drequinox/.zcash-params 
      $ ls -ltr 
      sprout-verifying.key 
      sprout-proving.key
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 一旦前述命令成功完成,就可以使用以下命令构建源代码:
       $ ./zcutil/build.sh -j$(nproc) 
  • 1

这将产生非常长的输出;如果一切顺利,将产生一个zcashd二进制文件。请注意,此命令将nproc作为参数,nproc基本上是一个查找系统中核心或处理器数量并显示该数字的命令。如果您没有该命令,则将nproc替换为系统中的处理器数量。

  1. 构建完成后,下一步是配置 Zcash。这通过在~/.zcash/目录中创建名为zcash.conf的配置文件来实现。

示例配置文件如下所示:

      addnode=mainnet.z.cash 
      rpcuser=drequinox 
      rpcpassword=xxxxxxoJNo4o5c+F6E+J4P2C1D5izlzIKPZJhTzdW5A= 
      gen=1 
      genproclimit=8 
      equihashsolver=tromp 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

前述配置启用了各种功能。第一行添加了主网节点并启用了主网连接。rpcuserrpcpassword是 RPC 接口的用户名和密码。gen = 1用于启用挖矿。genproclimit是可用于挖矿的处理器数量。最后一行启用了更快的挖矿求解器;如果要使用标准 CPU 挖矿,则不需要此项。

  1. 现在可以使用以下命令启动 Zcash:
 $ ./zcashd --daemon 
  • 1

启动后,可以通过zcash-cli命令行界面与 RPC 接口进行交互。这几乎与比特币命令行界面相同。一旦 Zcash 守护进程启动并运行,可以运行各种命令来查询 Zcash 的不同属性。可以使用 CLI 或区块链浏览器在本地查看交易。

Zcash 的区块链浏览器位于:explorer.zcha.in/

地址生成

可以使用以下命令生成新的 Z 地址:

$ ./zcash-cli z_getnewaddress zcPDBKuuwHJ4gqT5Q59zAMXDHhFoihyTC1aLE5Kz4GwgUXfCBWG6SDr45SFLUsZhpcdvHt7nFmC 3iQcn37rKBcVRa93DYrA 
  • 1

使用带有getinfo参数运行zcash-cli命令会产生以下截图中显示的输出。它显示了诸如blocksdifficultybalance之类的有价值信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

显示 getinfo 输出的截图

可以使用以下命令生成新的 T 地址:

$ ./zcash-cli getnewaddress  
t1XRCGMAw36yPVCcxDUrxv2csAAuGdS8Nny 
  • 1
  • 2

GPU 挖矿

除了 CPU 挖矿外,还提供了 GPU 挖矿选项。目前尚无官方 GPU 矿工;然而,开源开发人员已经制作了各种概念验证和可用的矿工。Zcash 公司举办了一场公开竞赛,鼓励开发人员构建并提交 CPU 和 GPU 矿工。截至撰写时尚未宣布获奖者。

读者可以通过访问网站zcashminers.org/获取更多信息。

还有另一种挖矿方式:使用来自各种在线云挖矿提供商提供的云挖矿合约。云挖矿服务提供商代表客户进行挖矿。除了云挖矿合约,矿工还可以使用自己的设备通过矿池使用 stratum 或其他协议进行挖矿。一个重要的例子是 NiceHash 提供的 Zcash 矿池,链接在:www.nicehash.com。使用该矿池,矿工可以出售他们的哈希功率。

下图显示了在 Zcash 挖矿矿池上构建并使用 CPU 矿工的示例。

下载并编译 nheqminer

可以使用以下步骤在 Ubuntu Linux 发行版上下载和编译nheqminer

$ sudo apt-get install cmake build-essential libboost-all-dev git clone https://github.com/nicehash/nheqminer.git 
$ cd nheqminer/nheqminer  
$ mkdir build 
$ cd build  
$ cmake .. make 
  • 1
  • 2
  • 3
  • 4
  • 5

所有步骤都成功完成后,可以使用以下命令运行nhequminer

$ ./nhequminer -l eu -u <btc address> -t <number of threads>  
  • 1

nheqminer的版本可在以下链接下载,适用于 Windows 和 Linux 系统:

github.com/nicehash/nheqminer/releases

nheqminer需要多个参数,如位置(-l)、用户名(-u)和用于挖矿的线程数(-t)。

下图显示了在 Linux 上运行 Zcash 的样本nheqminer。在此截图中,支付是以比特币地址进行的,用于出售哈希功率:

使用 BTC 地址接收出售哈希功率的支付

此处显示的截图展示了在 Windows 上运行nheqminer并将支付发到 Zcash T 地址的样本运行情况:

使用 Zcash T 地址接收出售哈希功率的支付

Zcash 以创新的方式使用了零知识证明技术,并为未来需要固有隐私的应用铺平了道路,如银行、医学或法律。

本节介绍了 Zcash 的简介;读者可以在z.cash上在线探索更多关于 Zcash 的信息。

首次代币发行(ICOs)

ICOs 类似于首次公开发行(IPO)。就像 IPO 是公司为了筹集资金而启动的一样,ICO 是为了为初创项目筹集资金而启动的。关键区别在于 IPO 受到监管,并且属于证券市场的范畴(公司的股份),而 ICO 则不受监管,不属于任何已建立市场结构的严格类别。

然而,鉴于最近几个月推出的一些骗局 ICO 方案以及对投资者保护日益增长的担忧,有一些建议认为 ICO 应被视为证券。最近,证券交易委员会(SEC)建议所有硬币、ICO、数字资产均属于证券的定义。这意味着对 ICO、比特币和其他数字硬币适用的法律将与证券适用的相同。此外,正式的“了解您的客户”(KYC)和“反洗钱”(AML)也被建议引入以解决与洗钱有关的问题。专家建议豪伊测试作为将任何 ICO 视为证券的一些标准。

另一个区别是,ICO 通常要求投资者使用加密货币投资,并使用加密货币支付回报,最常见的是 ICO 引入的新代币(新加密货币)。这也可以是法定货币,但最常见的是使用加密货币。例如,在以太坊众筹活动中,引入了一种新代币 Ether。代币拍卖众筹的术语也非常流行,这两个术语可互换使用。ICO 也被称为众筹销售。

当一个基于新区块链的应用程序或组织发布时,可以同时推出一个新代币作为访问和使用该应用程序的凭证,并以已建立的加密货币(例如比特币或以太币)或法定货币交换公开发行这个代币。优势在于,随着应用程序或产品的使用量增加,新代币的价值也会随之增加。这样,最初投资的投资者将获得良好的激励。

在 2017 年,ICO 已成为新创企业筹集资本的主要工具。第一个成功的 ICO 是以太坊,在 2014 年筹集了 1800 万美元。最近的成功案例是 Tezos,在几周内筹集了 2.32 亿美元。另一个例子是 Filecoin,筹集了超过 2.5 亿美元。

在以太坊区块链上创建新代币的过程已经标准化,因此相对容易启动一个 ICO,并以以太币、比特币或其他加密货币交换新代币。这个标准称为 ERC20,将在下一节中详细描述。值得注意的是,使用 ERC20 并不是必须的,完全可以在新的区块链上创造一个全新的加密货币来启动 ICO,但最近多个 ICO 中都使用了 ERC20,并为 ICO 构建代币提供了相对较简单的方式。

最近,ICO 在以太坊之外的平台上也提供,如 NEM(nem.io)和 Stellar(www.stellar.org)。

ERC20 代币

ERC20 代币是一个接口,定义了各种功能,规定了代币的要求。然而,它并没有提供实现细节,而是留给实施者决定。ERC 基本上是以太坊请求评论的缩写,相当于比特币的 BIPs,用于提出以太坊区块链的改进建议。

这在 EIP 20 下定义,你可以在此处阅读更多信息:github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md

由于能够创建新代币并且具有 ERC20 标准,以太坊正在成为 ICO 的选择平台,它变得更加易于访问。

ERC20 代币标准定义了描述新代币的各种属性、规则和特性的各种功能。这些包括货币的总供应量、持有者的总余额、转账功能、批准和授权功能。

还有其他标准,如 ERC223、ERC777 和 ERC20 的扩展称为 ERC827 也正在开发中。

您可以参考以下链接了解更多信息:

概要

在本章中,我们向您介绍了整个加密货币领域。我们详细讨论了许多替代币,尤其是 Zcash 和 Namecoin。加密货币是一个非常活跃的研究领域,尤其是围绕可扩展性、隐私和安全方面。一些研究也已经进行,以发明新的难度重新调整算法,以阻止加密货币中央化的威胁。

需要在隐私和尤其是区块链的可扩展性领域进行进一步研究。

现在您应该能够理解代币概念及其背后的各种动机。我们还讨论了一些实际方面,例如挖矿和启动新的货币项目,这希望能够为您奠定坚实的基础,使您能够进一步探索这些领域。代币是一个令人着迷的研究领域,它为去中心化的未来打开了许多可能性。

第十一章:开发工具和框架

本章介绍了用于以太坊智能合约开发的开发工具、语言和框架。我们将研究开发以太坊区块链智能合约的不同方法。我们将详细讨论 Solidity 语言的各种结构,这是目前以太坊智能合约开发中最流行的开发语言。

本章将介绍以下主题:

  • 开发工具、集成开发环境和客户端

    • Remix

    • Ganache

    • EthereumJS

    • TestRPC

    • MetaMask

    • Truffle

  • 先决条件

    • Node

    • Node 包管理器(NPM)

  • 其他工具和实用程序

有许多工具可用于以太坊开发。下图显示了用于以太坊的各种开发工具、客户端、集成开发环境和开发框架的分类:

以太坊开发生态系统组件的分类

上述分类并未包括所有用于以太坊开发的框架和工具。它展示了最常用的工具和框架,也是我们在本章中将要用到的工具和框架。

有许多与以太坊开发工具相关的资源可在以下地址找到:ethdocs.org/en/latest/contracts-and-transactions/developer-tools.html#developer-tools

本章的主要重点将放在 Geth、Remix IDE、Solidity、Ganache、MetaMask、solc 和 Truffle 上。其他元素,如先决条件(Node),也将简要讨论。

语言

以太坊区块链的智能合约可以用多种语言编程。有五种语言可用于编写合同:

  • Mutan:这是一种类似 Go 的语言,于 2015 年初被废弃,已不再使用。

  • LLL:这是一种低级别的 Lisp 式语言,因此被命名为 LLL。这也不再使用。

  • Serpent:这是一种简单、清洁的类似 Python 的语言。它不再用于合同开发,并且不再得到社区支持。

  • Solidity:此语言现已成为几乎是以太坊合同编写的标准。本章将重点讨论这种语言,并在后续章节中进行详细讨论。

  • Vyper:这种语言是一种类似 Python 的实验性语言,旨在为智能合约开发带来安全性、简单性和可审计性。

编译器

编译器用于将高级合同源代码转换为以太坊执行环境能理解的格式。Solidity 编译器是最常用的一个,并在此处讨论。

Solidity 编译器(solc)

solc 将高级 Solidity 语言转换为以太坊虚拟机EVM)字节码,以便在区块链上由 EVM 执行。

在 Linux 上安装

solc 可以在 Linux Ubuntu 操作系统上安装,使用以下命令:

$ sudo apt-get install solc 
  • 1

如果 PPA 尚未安装,可以通过运行以下命令来安装它们:

$ sudo add-apt-repository ppa:ethereum/ethereum  
$ sudo apt-get update 
  • 1
  • 2

为了验证 solc 的现有版本并验证其是否已安装,可以使用以下命令:

$ solc --version
solc, the solidity compiler commandline interface
Version: 0.4.19+commit.c4cbbb05.Darwin.appleclang  
  • 1
  • 2
  • 3

在 macOS 上安装

要在 macOS 上安装 solc,请执行以下命令:

$ brew tap ethereum/ethereum $ brew install solidity $ brew linkapps solidity  
  • 1

solc 支持各种功能。一些示例如下所示:

  • 以二进制格式显示合约:
 $ solc --bin Addition.sol  
  • 1

该命令将生成类似以下的输出。这显示了二进制翻译的内容。

Addition.sol 合约代码:

Solidity 编译器的二进制输出

  • 估算 gas:
 $ solc --gas Addition.sol
  • 1

这将产生以下输出:

使用 solc 估算 gas

  • 生成 ABI:
 $ solc --abi Addition.sol 
  • 1

以下是 Addition.abi 的内容:

      ======= Addition.sol:Addition ======= 
      Contract JSON ABI  
      [{"constant":false,"inputs":[{"name":"y","type":"uint8"},   
      {"name":"z","type":"uint8"}],"name":"addx","outputs":
      [],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":
      [],"name":"retrievex","outputs":   
      [{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"}] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 编译:

另一个有用的命令,用于编译并生成一个二进制编译文件以及一个 ABI,如下所示:

 $ solc --bin --abi --optimize -o bin Addition.sol
  • 1

该命令将在输出目录 bin 中产生两个文件:

    • Addition.abi:这包含智能合约的 Application Binary Interface,以 JSON 格式表示

    • Addition.bin:这包含智能合约代码的二进制表示

两个文件的输出显示在以下截图中:

Solidity 编译器的 ABI 和二进制输出

ABIApplication Binary Interface 的缩写。ABI 对智能合约的函数和事件的信息进行编码。它充当 EVM 级字节码和高级智能合约程序代码之间的接口。要与部署在以太坊区块链上的智能合约进行交互,外部程序需要 ABI 和智能合约的地址。

solc 是一个非常强大的命令,可以使用 --help 标志来探索更多选项,该选项将显示详细选项。但是,用于编译、ABI 生成和 gas 估算的前述命令应该对大多数开发和部署需求足够了。

集成开发环境(IDE)

有各种各样的 IDE 可用于 Solidity 开发。大多数 IDE 都可以在线使用,并通过 web 界面呈现。Remix(曾用名为浏览器 Solidity)是构建和调试智能合约最常用的 IDE。在此讨论它。

Remix

Remix 是基于 web 的环境,用于使用 Solidity 开发和测试合约。它是一个功能丰富的 IDE,不在实时区块链上运行;实际上,它是一个模拟环境,合约可以在其中部署、测试和调试。

可在 remix.ethereum.org 找到。

示例接口如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Remix IDE

在左侧,有一个带有语法高亮和代码格式化的代码编辑器,右侧有许多可用于部署、调试、测试和与合约交互的工具。

可以使用各种功能,例如交易交互,连接到 JavaScript VM 的选项,执行环境的配置,调试器,形式验证和静态分析。它们可以配置为连接到执行环境,例如 JavaScript VM,注入的 Web3——Mist、MetaMask 或类似环境提供了执行环境——或者 Web3 提供程序,它允许通过 IPC 或 RPC over HTTP(Web3 提供程序端点)连接到本地运行的以太坊客户端(例如 geth)。

Remix 还具有针对 EVM 的调试器,非常强大,可用于执行详细级别的跟踪和分析 EVM 字节码。下面是一个示例:

Remix IDE,调试

上面的截图显示了 Remix IDE 的不同元素。左上角显示了源代码。下面是输出日志,显示了与合约的编译和执行相关的信息消息和数据。

下面的截图显示了 Remix 调试器的更多细节。它将源代码解码为 EVM 指令。用户可以逐步执行每个指令,并且可以检查当执行时源代码的作用:

Remix 调试器

工具和库

有各种工具和库可用于以太坊。最常见的是在这里讨论的。

在本节中,我们将首先安装开发以太坊应用程序所需的先决条件。首先要求是 Node,接下来将看到。

Node 版本 7

由于大多数工具和库都需要 Node,可以使用以下命令安装它:

$ curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash - sudo apt-get install -y nodejs 
  • 1

EthereumJS

有时候,在测试网上测试不可能,并且主网显然不是测试合约的地方。私有网有时候设置起来可能很耗时。当需要快速测试而又没有合适的测试网时,EthereumJS 的 TestRPC 就非常方便了。它使用 EthereumJS 模拟 Ethereum geth 客户端的行为,并允许进行更快的开发测试。TestRPC 可以通过npm作为一个 Node 软件包获得。

在安装 TestRPC 之前,Node 应该已经被安装,并且npm软件包管理器也应该可用。

可以使用此命令安装 TestRPC:

$ npm install -g ethereumjs-testrpc 
  • 1

要启动 testrpc,只需发出此命令并使其在后台运行,然后打开另一个终端以处理合约:

$ testrpc  
  • 1

当 TestRPC 运行时,它将显示类似于以下截图所示的输出。它将自动生成十个帐户和私钥,以及 HD 钱包。它将开始监听 TCP 端口8545上的传入连接。

TestRPC

Ganache

Ganache 是为以太坊开发的众多开发工具和库的最新增加。这在某种程度上是 TestRPC 的替代品,使用用户友好的图形用户界面来查看交易和块以及相关细节。这是一个完全工作的启用了 Byzantium 的个人区块链,用于为区块链提供本地测试环境。

Ganache 基于以太坊区块链的 JavaScript 实现,内置区块浏览器和挖矿功能,使在系统上进行本地测试非常容易。

如下截图所示,您可以在前端详细查看交易、块和地址:

Ganache,一个个人以太坊区块链

Ganache 可以从truffleframework.com/ganache/下载。

MetaMask

MetaMask 允许通过 Firefox 和 Chrome 浏览器与以太坊区块链交互。它会在运行网站的 JavaScript 环境中注入一个web3对象,从而实现对 DApps 的即时接口功能。这种注入允许 DApps 直接与区块链交互。

它可以在metamask.io/获得。

MetaMask 也允许账户管理。这在任何交易在区块链上执行之前充当验证方法。用户会看到一个安全界面来审查交易,然后批准或拒绝它,才能到达目标区块链。

它可以在github.com/MetaMask/metamask-plugin获得。

MetaMask

它允许与各种以太坊网络连接,如下截图所示。这是 MetaMask 的截图,它允许用户选择他们喜欢的网络:

MetaMask 网络如 MetaMask 用户界面所示

值得注意的一个有趣功能是 MetaMask 也可以连接到任何自定义的 RPC,这允许您运行自己的区块链,例如本地或远程的私有网络,并允许您的浏览器连接到它。它还可以用于连接到本地运行的区块链,如 Ganache 和 TestRPC。

MetaMask 允许账户管理,并记录所有这些账户的交易。这在下面的截图中显示:

MetaMask 账户和交易视图

Truffle

Truffle(在 truffleframework.com/ 上可用)是一个开发环境,使得测试和部署以太坊合约更加容易和简单。Truffle 提供合约编译和链接以及使用 Mocha 和 Chai 的自动化测试框架。它还使得更容易将合约部署到任何私有网络、公共网络或测试网络以太坊区块链中。此外,提供了资产管道,使所有 JavaScript 文件都可以被处理,使其可以被浏览器使用。

安装

在安装之前,假设node可用,可以像下面显示的那样查询。如果node不可用,则首先需要安装node才能安装truffle

$ node -version 
v7.2.1 
  • 1
  • 2

安装truffle非常简单,可以使用以下命令通过Node Package Managernpm)完成:

$ sudo npm install -g truffle 
  • 1

这将花费几分钟的时间;安装完成后,可以使用truffle命令显示帮助信息并验证它是否正确安装:

$ sudo npm install -g truffle Password: /us/local/bin/truffle -> /usr/local/lib/node_modules/truffle/build/cli.bundled.js /usr/local/lib └── truffle@4.0.1  
  • 1

在终端输入truffle以显示使用帮助:

$ truffle 
  • 1

这将显示以下输出:

Truffle 帮助

或者,存储库可在 github.com/ConsenSys/truffle 上找到,可以将其克隆到本地以安装truffle。可以使用以下命令使用 Git 克隆存储库:

$ git clone https://github.com/ConsenSys/truffle.git
  • 1

合约开发和部署

开发和部署合约需要采取各种步骤。广义上来说,这些可以分为四个步骤:编写、测试、验证和部署。部署后,下一个可选步骤是创建用户界面并通过 Web 服务器向最终用户呈现。在不需要人类输入或监视的合约中,有时不需要 Web 界面,但通常需要创建 Web 界面来与合约交互。

写作

写作步骤涉及在 Solidity 中编写合约源代码。这可以在任何文本编辑器中完成。对于 Vim、Atom 和其他编辑器,有各种插件和附加组件可用于提供 Solidity 合约源代码的语法高亮和格式化。

Visual studio code 已经变得非常流行,并且通常用于 Solidity 开发。有一个 Solidity 插件可供使用,可以实现语法高亮、格式化和智能。可以通过 Visual Studio Code 中的扩展选项进行安装。

Visual studio code

测试

测试通常是通过自动化手段进行的。在本章的前面,您已经了解了 Truffle,它使用 Mocha 框架来测试合约。但是,也可以通过使用 Remix 手动运行函数并验证结果来执行手动功能测试。

在下一节中,你将会被介绍到 Solidity 语言。这是对 Solidity 的简要介绍,应提供编写合约所需的基本知识。其语法与 C 和 JavaScript 非常相似,编程起来相当容易。

Solidity 语言

Solidity 是以太坊编写合约的领域特定语言的首选。然而,还有其他可用的语言,比如 Serpent、Mutan 和 LLL,但在撰写本文时,Solidity 是最流行的。它的语法更接近 JavaScript 和 C。

Solidity 在过去几年中发展成为一种成熟的语言,非常容易使用,但在成为像 Java、C 或 C Sharp 等其他成熟语言一样先进、标准化和功能丰富之前,还有很长的路要走。尽管如此,这仍然是目前编写合约最广泛使用的语言。

它是一种静态类型语言,这意味着在 Solidity 中,变量类型检查是在编译时进行的。每个变量,无论是状态变量还是局部变量,都必须在编译时指定一个类型。这在某种意义上是有益的,因为任何验证和检查都是在编译时完成的,某些类型的错误,比如数据类型的解释,可以在开发周期的早期被捕获,而不是在运行时,这可能是昂贵的,特别是在区块链/智能合约范式的情况下。语言的其他特性包括继承、库和定义复合数据类型的能力。

Solidity 也被称为面向合约的语言。在 Solidity 中,合约相当于其他面向对象编程语言中的类的概念。

类型

Solidity 有两类数据类型:值类型引用类型

值类型

这些在这里详细解释:

布尔

此数据类型有两个可能的值,truefalse,例如:

bool v = true; 
bool v = false;  
  • 1
  • 2

此语句将值 true 分配给 v

整数

此数据类型表示整数。以下表格显示了用于声明整数数据类型的各种关键字:

关键字类型详情
int有符号整数int8int256,这意味着关键字从 int8 增加到 int256,例如,int8int16int24
uint无符号整数uint8uint16、… 到 uint256,表示从 8 位到 256 位的无符号整数。变量的存储需求取决于需要存储多少位。

例如,在这段代码中,注意 uintuint256 的别名:

uint256 x;  
uint y;  
uint256 z; 
  • 1
  • 2
  • 3

这些类型也可以用 constant 关键字声明,这意味着编译器不会为这些变量保留存储槽。在这种情况下,每次出现都将被实际值替换:

uint constant z=10+10; 
  • 1

状态变量在函数体外声明,并且根据分配给它们的可访问性和合约的持续时间保持可用。

地址

此数据类型持有 160 位长(20 字节)的值。该类型具有几个成员,可用于与合约交互和查询。这些成员在这里描述:

  • 余额balance 成员返回地址的 Wei 余额。

  • 发送:此成员用于向地址(以太坊的 160 位地址)发送一定数量的以太,并根据交易结果返回truefalse,例如:

      address to = 0x6414cc08d148dce9ebf5a2d0b7c220ed2d3203da; address from = this; 
      if (to.balance < 10 && from.balance > 50) to.send(20); 
  • 1
  • 2
  • 调用函数callcallcodedelegatecall 用于与没有 ABI 的函数交互。由于对合约的类型安全性和安全性的影响,应谨慎使用这些函数。

  • 数组值类型(固定大小和动态大小的字节数组):Solidity 具有固定大小和动态大小的字节数组。固定大小关键字从 bytes1bytes32,而动态大小关键字包括 bytesstringbytes 关键字用于原始字节数据,string 用于以 UTF-8 编码的字符串。由于这些数组是按值返回的,调用它们将产生 gas 成本。length 是数组值类型的一个成员,并返回字节数组的长度。

静态(固定大小)数组的示例如下:

      bytes32[10] bankAccounts; 
  • 1

动态大小数组的示例如下:

      bytes32[] trades;  
  • 1

使用以下代码获取交易长度:

      trades.length; 
  • 1

字面量

这些用于表示固定值。下面的小节描述了不同类型的字面量。

整数字面量

整数字面量是一系列范围在 0-9 的十进制数字。示例如下:

uint8 x = 2; 
  • 1

字符串字面量

字符串字面量指定用双引号或单引号编写的一组字符。示例如下:

'packt' "packt" 
  • 1

十六进制字面量

十六进制字面量以关键字 hex 为前缀,并在双引号或单引号内指定。示例如下:

(hex'AABBCC'); 
  • 1

枚举

这允许创建用户定义的类型。示例如下:

enum Order {Filled, Placed, Expired };  
Order private ord; 
ord=Order.Filled; 
  • 1
  • 2
  • 3

枚举类型允许对所有整数类型进行显式转换。

函数类型

有两种函数类型:内部函数和外部函数。

内部函数

这些只能在当前合约的上下文中使用。

外部函数

外部函数可以通过外部函数调用来调用。

在 Solidity 中,函数 可以标记为常量。常量函数不能更改合约中的任何内容;只有在调用时返回值,而且不消耗任何 gas。这是对 call 概念的实际实现。

声明函数的语法如下所示:

function <nameofthefunction> (<parameter types> <name of the variable>) 
{internal|external} [constant] [payable] [returns (<return types> <name of the variable>)] 
  • 1
  • 2

引用类型

正如名称所示,这些类型是按引用传递的,并在以下部分讨论。这些也被称为复杂类型

数组

数组表示内存位置处以相同大小和类型排列的一系列连续元素。该概念与任何其他编程语言相同。数组有两个成员名为lengthpush

uint[] OrderIds; 
  • 1

结构体

这些结构可用于将一组不同类型的数据分组到一个逻辑组中。这些可以用于定义新类型,如下例所示:

pragma solidity ⁰.4.0; 
contract TestStruct { 
  struct Trade 
  { 
    uint tradeid; 
    uint quantity; 
    uint price;  
    string trader; 
  } 

  //This struct can be initialized and used as below 

  Trade tStruct = Trade({tradeid:123, quantity:1, price:1, trader:"equinox"}); 

} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

数据位置

数据位置指定特定复杂数据类型将存储在何处。根据默认值或指定的注释,位置可以是存储或内存。这适用于数组和结构体,并可以使用storagememory关键字指定。

由于在内存和存储之间进行复制可能相当昂贵,因此有时指定位置可以帮助控制燃气消耗。调用数据是另一个内存位置,用于存储函数参数。

外部函数的参数使用调用数据内存。默认情况下,函数的参数存储在内存中,而所有其他局部变量都使用存储。另一方面,状态变量需要使用存储。

映射

映射用于键值映射。这是一种将值与键关联的方法。此映射中的所有值都已初始化为零,例如以下内容:

mapping (address => uint) offers; 
  • 1

此示例显示 offers 被声明为映射。另一个示例使此更清晰:

mapping (string => uint) bids;  
bids["packt"] = 10; 
  • 1
  • 2

这基本上是一个字典或哈希表,其中字符串值映射到整数值。名为bids的映射将字符串packt映射到值10

全局变量

Solidity 提供了一些始终可用于全局命名空间的全局变量。这些变量提供有关块和交易的信息。此外,加密函数和与地址相关的变量也可用。

显示的可用函数和变量子集如下所示:

keccak256(...) returns (bytes32) 
  • 1

此函数用于计算提供给函数的参数的 Keccak-256 哈希值:

ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) 
  • 1

此函数返回椭圆曲线签名的公钥关联地址:

block.number 
  • 1

这返回当前块编号。

控制结构

Solidity 语言中可用的控制结构是if...elsedowhileforbreakcontinuereturn。它们的工作方式与其他语言(如 C 语言或 JavaScript)完全相同。

一些示例显示在这里:

  • if:如果x等于0,则将值0赋给y,否则将1赋给z
      if (x == 0) 
          y = 0; 
      else 
          z = 1; 
  • 1
  • 2
  • 3
  • 4
  • do:在z大于1时递增x
      do{ 
          x++; 
      } (while z>1); 
  • 1
  • 2
  • 3
  • while:在x大于0时递增z
      while(x > 0){ 
          z++; 
      } 
  • 1
  • 2
  • 3
  • for、break 和 continue:执行一些工作,直到x小于或等于10。如果z5,那么这个for循环将运行10次,然后中断:
      for(uint8 x=0; x<=10; x++) 
      { 
          //perform some work 
          z++ 
          if(z == 5) break; 
      } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

它将继续类似的工作,但在条件满足时,循环将重新开始。

  • return:Return 用于停止函数的执行并返回一个可选值。例如:
      return 0; 
  • 1

它将停止执行并返回值0

事件

Solidity 中的事件可用于记录 EVM 日志中的某些事件。当需要通知外部接口发生变化或事件发生时,这些事件非常有用。这些日志存储在区块链中的交易日志中。合约无法访问日志,但它们用作通知合约状态变化或事件发生(满足条件)的机制。

在这里的一个简单示例中,valueEvent事件将在函数Matcher传递的x参数等于或大于10时返回true

pragma solidity ⁰.4.0;  
contract valueChecker  
{  
    uint8 price=10; 
    event valueEvent(bool returnValue);  
    function Matcher(uint8 x) public returns (bool) 
    { 
        if (x>=price) 
        { 
            valueEvent(true);  
            return true; 
        } 
    } 
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

继承

Solidity 支持继承。is关键字用于从另一个合约派生合约。在下面的示例中,valueChecker2是从valueChecker合约派生出来的。派生合约具有对父合约的所有非私有成员的访问权限:

pragma solidity ⁰.4.0;  
contract valueChecker 
{ 
    uint8 price = 20; 
    event valueEvent(bool returnValue);  
    function Matcher(uint8 x) public returns (bool) 
    { 
        if (x>=price) 
        { 
            valueEvent(true);  
            return true; 
        } 
    } 
} 
contract valueChecker2 is valueChecker 
{ 
    function Matcher2() public view returns (uint) 
    { 
        return price+10; 
    } 
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在前面的示例中,如果将 uint8 price = 20 更改为 uint8 private price = 20,那么valueChecker2合约将无法访问它。这是因为现在该成员被声明为私有,不允许任何其他合约访问。在 Remix 中您将看到的错误信息为

browser/valuechecker.sol:20:8: DeclarationError: Undeclared identifier. 
return price+10; 
       ^---^ 
  • 1
  • 2
  • 3

库只在特定地址部署一次,它们的代码通过 EVM 的 CALLCODEDELEGATECALL 操作码调用。库背后的关键思想是代码的重复使用性。它们类似于合约,并作为调用合约的基础合约。一个库可以声明如下:

library Addition 
{ 
    function Add(uint x,uint y) returns (uint z) 
    { 
        return x + y; 
    } 
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后可以在合约中调用此库,如下所示。首先需要导入它,然后可以在代码的任何地方使用它。一个简单的示例如下所示:

import "Addition.sol" 
function Addtwovalues() returns(uint) 
{ 
    return Addition.Add(100,100); 
} 
  • 1
  • 2
  • 3
  • 4
  • 5

库存在一些限制;例如,它们不能拥有状态变量,也不能继承或被继承。此外,它们也不能接收以太币;这与合约相反,合约可以接收以太币。

函数

Solidity 中的函数是与合约关联的代码模块。函数声明包括名称、可选参数、访问修饰符、可选constant关键字和可选返回类型。如下例所示:

function orderMatcher (uint x)  
private constant returns(bool return value)  
  • 1
  • 2

在前面的示例中,function是声明函数所使用的关键字。orderMatcher是函数名,uint x是一个可选参数,private是控制外部合约访问该函数的访问修饰符说明符constant是一个可选关键字,用于指定该函数不会改变合约中的任何内容,而仅用于从合约中检索值,returns (bool return value)是函数的可选返回类型。

  • 如何定义函数:定义函数的语法如下所示:
      function <name of the function>(<parameters>) <visibility specifier> returns 
      (<return data type> <name of the variable>) 
      { 
          <function body> 
      } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 函数签名:Solidity 中的函数由其签名标识,即其完整签名字符串的 Keccak-256 哈希的前四个字节。这也可以在 Remix IDE 中看到,如下面的屏幕截图所示。f9d55e21 是名为 Matcher 的函数的 32 字节 Keccak-256 哈希的前四个字节。

在 Remix IDE 中显示的函数哈希

在这个示例函数中,Matcher 的签名哈希为 d99c89cb。这些信息对于构建接口是有用的。

  • 函数的输入参数:函数的输入参数以 <数据类型> <参数名> 的形式声明。这个例子阐明了 checkValues 函数的输入参数 uint xuint y 的概念:
      contract myContract 
      { 
          function checkValues(uint x, uint y) 
          { 
          } 
      } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 函数的输出参数:函数的输出参数以 <数据类型> <参数名> 的形式声明。这个例子显示了一个简单的返回 uint 值的函数:
      contract myContract 
      { 
          function getValue() returns (uint z) 
          { 
              z=x+y; 
          } 
      } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

一个函数可以返回多个值。在前面的示例函数中,getValue 只返回一个值,但一个函数最多可以返回 14 个不同数据类型的值。未使用的返回参数的名称可以选择性地省略。

  • 内部函数调用:在当前合约的上下文中,可以直接调用当前合约中的函数。这些调用导致 EVM 字节码级别的简单 JUMP 调用。

  • 外部函数调用:外部函数调用是通过合约从一个合约向另一个合约发送消息调用的。在这种情况下,所有函数参数都会被复制到内存中。如果使用 this 关键字调用内部函数,它也被视为外部调用。this 变量是一个指针,指向当前合约。它可以显式转换为地址,并且所有合约的成员都是从地址继承的。

  • 回退函数:这是一个没有参数和返回数据的合约中的未命名函数。每当接收到以太时,此函数都会执行。如果合约打算接收以太,则必须在合约内实现此函数;否则,将抛出异常并返回以太。如果合约中没有其他函数签名匹配,此函数也会执行。如果合约预期接收以太,则回退函数应该用可支付的 修饰符 声明。这个修饰符是必需的;否则,此函数将无法接收任何以太。此函数可以使用 address.call() 方法调用,例如:

      function () 
      { 
          throw; 
      } 
  • 1
  • 2
  • 3
  • 4

在这种情况下,如果根据前述条件调用了回退函数,则会调用 throw,这将使状态回滚到调用前的状态。它也可以是除 throw 之外的其他构造,例如,它可以记录一个事件,该事件可以作为反馈调用结果给调用应用程序的警报。

  • 修改器函数:这些函数用于改变函数的行为,并且可以在其他函数之前调用。通常,它们用于在执行函数之前检查某些条件或验证。在修改器函数中使用 _(下划线),当调用修改器时将其替换为实际函数体。基本上,它象征着需要被守护的函数。这个概念类似于其他语言中的守护函数。

  • 构造函数:这是一个可选函数,其名称与合约相同,并且在创建合约时执行。构造函数不能由用户后来调用,一个合约中只允许一个构造函数。这意味着没有重载功能可用。

  • 函数可见性修饰符(访问修饰符):函数可以用四个访问修饰符来定义,如下所示:

    • 外部:这些函数可以从其他合约和交易中访问。除非使用 this 关键字,否则不能在内部调用它们。

    • 公共:默认情况下,函数是公共的。它们可以在内部调用,也可以通过消息调用。

    • 内部:内部函数对来自父合约的其他派生合约可见。

    • 私有:私有函数只对声明它们的同一合约可见。

  • 函数修改器

    • 纯净:此修饰符禁止对状态进行访问或修改

    • 视图:此修饰符禁止对状态进行任何修改

    • 可支付的:此修饰符允许通过调用支付以太币

    • 常量:此修饰符不允许对状态进行访问或修改

  • 其他重要关键字/函数 throwthrow 用于停止执行。因此,所有状态更改都将被还原。在这种情况下,没有气体返回给交易发起者,因为所有剩余的气体都被消耗掉了。

Solidity 源代码文件的布局

在以下子节中,我们将看看 Solidity 源代码文件的组成部分。

版本声明

为了解决未来版本的 solc 版本可能出现的兼容性问题,pragma 可以用来指定兼容编译器的版本,例如:

pragma solidity ⁰.5.0 
  • 1

这将确保源文件不会与小于 0.5.0 的版本以及从 0.6.0 开始的版本编译。

导入

在 Solidity 中,import 允许将现有 Solidity 文件中的符号导入到当前的全局范围。这类似于 JavaScript 中可用的 import 语句,例如:

import "module-name"; 
  • 1

注释

注释可以以类似于 C 语言的方式添加在 Solidity 源代码文件中。多行注释用 /**/ 括起来,而单行注释以 // 开头。

以下是一个 Solidity 程序示例,展示了pragmaimport和注释的用法:

在 Remix IDE 中显示的 Solidity 程序示例

这完成了对 Solidity 语言的简要介绍。该语言非常丰富并在不断改进中。详细文档和编码指南可在网上查看:solidity.readthedocs.io/en/latest/

摘要

本章从介绍以太坊的开发工具开始,如 Remix IDE。然后我们讨论了一些框架,比如 Truffle,还有用于开发和测试的本地区块链解决方案,例如 Ganache、EthereumJS 和 TestRPC。还探讨了其他工具,比如 MetaMask。引入了 Node 的安装,因为大多数工具都是基于 JavaScript 和 Node 的。

第十二章:构建一个投丨注应用程序

有时,智能合约需要访问其他 dapp 或全球网络中的数据是必要的。但由于技术和共识挑战,让智能合约访问外部数据确实很复杂。因此,目前,以太坊智能合约没有原生支持访问外部数据。但是有第三方解决方案供以太坊智能合约访问一些热门 dapp 和全球网络的数据。在本章中,我们将学习如何使用 Oraclize 从以太坊智能合约向全球网络发出 HTTP 请求以访问数据。我们还将学习如何访问存储在 IPFS 中的文件,使用字符串库处理字符串等等。我们将通过构建一个足球投丨注智能合约和一个客户端来学习所有这些。

在本章中,我们将涵盖以下主题:

  • Oraclize 是如何工作的?

  • Oraclize 有哪些不同的数据来源,它们各自是如何工作的?

  • Oraclize 中的共识如何工作?

  • 将 Oraclize 集成到以太坊智能合约中

  • 使用字符串库Solidity,使处理字符串变得更加简单。

  • 构建一个足球投丨注应用程序。

Oraclize 简介

Oraclize 是一项旨在使智能合约能够从其他区块链和全球网络中获取数据的服务。该服务目前在比特币和以太坊的测试网和主网上已经上线。使 Oraclize 如此特殊的是您无需信任它,因为它提供了所有提供给智能合约的数据的真实性证明。

本章的目标是学习以太坊智能合约如何使用 Oraclize 服务从全球网络获取数据。

它是如何工作的?

让我们看一下以太坊智能合约如何使用 Oraclize 从其他区块链和全球网络中获取数据的过程。

要获取存在于以太坊区块链之外的数据,以太坊智能合约需要向 Oraclize 发送查询,提及数据源(表示从哪里获取数据)和数据源的输入(表示获取什么)。

向 Oraclize 发送查询意味着向 Oraclize 合约发送合同调用(即内部交易)。

Oraclize 服务器不断寻找向其智能合约发出的新查询。每当它看到新的查询时,它就会获取结果并通过调用您合约的_callback方法将其发送回给您的合约。

数据来源

以下是 Oraclize 允许智能合约获取数据的来源列表:

  • URL:URL 数据源使您能够进行 HTTP GET 或 POST 请求,即从全球网络获取数据。

  • WolframAlphaWolframAlpha数据源使您能够向WolframAlpha知识引擎提交查询并获取答案。

  • 区块链区块链 数据源允许您访问来自其他 区块链 的数据。可以提交给 区块链 数据源的可能查询有 比特币区块链高度莱特币哈希率比特币难度1NPFRDJuEdyqEn2nmLNaWMfojNksFjbL4S 余额 等。

  • IPFSIPFS 数据源使您能够获取存储在 IPFS 中的文件的内容。

  • 嵌套嵌套 数据源是一个元数据源;它不提供对其他服务的访问。它被设计为提供简单的聚合逻辑,使得可以基于任何可用的数据源进行单一查询,并生成单一的字符串作为结果;例如:

[WolframAlpha] ${[IPFS] QmP2ZkdsJG7LTw7jBbizTTgY1ZBeen64PqMgCAWz2koJBL} 中的温度

  • 计算计算 数据源使给定应用程序在安全的链下环境中进行可审计的执行;也就是说,它允许我们获取应用程序的链下执行结果。该应用程序必须在退出之前(在标准输出上)打印查询结果。执行上下文必须由 Dockerfile 描述,其中构建和运行应用程序应该立即启动您的主应用程序。Dockerfile 初始化加上应用程序执行应尽快终止:在 AWS t2.micro 实例上的最大执行超时时间为 5 分钟。在这里,我们考虑的是 AWS t2.micro 实例,因为这是 Oraclize 将用来执行该应用程序的实例类型。由于数据源的输入是包含这些文件的 ZIP 存档的 IPFS 多哈希值(Dockerfile 加上任何外部文件依赖项,Dockerfile 必须放置在存档根目录中),您应该注意准备此存档并将其推送到 IPFS。

这些数据源是在撰写本书时可用的。但未来可能会有更多的数据源可用。

真实性证明

尽管 Oraclize 是一个值得信赖的服务,但您可能仍希望检查 Oraclize 返回的数据是否真实,即它是否在传输过程中被 Oraclize 或其他人操纵。

可选地,Oraclize 提供了从 URL、区块链和嵌套和计算数据源返回的 TLSNotary 结果证明。这个证明对于 WolframAlphaIPFS 数据源不可用。目前,Oraclize 只支持 TLSNotary 证明,但在未来,他们可能支持一些其他的认证方式。目前,TLSNotary 证明需要手动验证,但是 Oraclize 已经在进行链上证明验证的工作;也就是说,你的智能合约代码可以在从 Oraclize 接收数据的同时验证 TLSNotary 证明,以便如果证明无效则丢弃这些数据。

这个工具(github.com/Oraclize/proof-verification-tool)是 Oraclize 提供的开源工具,用于验证 TLSNotary 证据,如果您愿意的话。

理解 TLSNotary 的工作原理并不是使用 Oraclize 或验证证据的必需条件。验证 TLSNotary 证据的工具是开源的;因此,如果其中包含任何恶意代码,那么它可以很容易被发现,因此可以信任这个工具。

让我们来看一下 TLSNotary 如何工作的高层概述。要理解 TLSNotary 的工作原理,首先需要了解 TLS 的工作原理。TLS 协议提供了一种让客户端和服务器创建加密会话的方式,以便其他人无法读取或操纵客户端和服务器之间传输的内容。服务器首先向客户端发送其证书(由受信任的 CA 颁发给域所有者),该证书将包含服务器的公钥。客户端使用 CA 的公钥解密证书,以便可以验证证书实际由 CA 颁发并获取服务器的公钥。然后,客户端生成对称加密密钥和 MAC 密钥,并使用服务器的公钥对其进行加密,然后将其发送给服务器。服务器只能解密此消息,因为它有解密该消息的私钥。现在客户端和服务器共享相同的对称和 MAC 密钥,除此之外,没有其他人知道这些密钥,它们可以开始相互发送和接收数据。对称密钥用于加密和解密数据,其中 MAC 密钥和对称密钥一起用于为加密消息生成签名,以便在消息被攻击者修改时,另一方可以知道。

TLSNotary 是 TLS 的修改版,由 Oraclize 使用以提供密码学证明,证明它们提供给您的智能合约的数据确实是数据源在特定时间提供给 Oraclize 的数据。实际上,TLSNotary 协议是开源技术,由 PageSigner 项目开发和使用。

TLSNotary 的工作原理是将对称密钥和 MAC 密钥分成三个方(即服务器、被审计者和审计者)。TLSNotary 的基本思想是被审计者可以向审计者证明特定结果是在特定时间由服务器返回的。

现在这里是一个关于 TLSNotary 如何实现这一目标的概述。审核员计算对称密钥和 MAC 密钥,并仅将对称密钥给审计对象。审计对象不需要 MAC 密钥,因为 MAC 签名检查确保了从服务器传输的 TLS 数据没有被篡改。使用对称加密密钥,审计对象现在可以解密来自服务器的数据。由于所有消息都由银行使用 MAC 密钥“签名”,并且只有服务器和审核员知道 MAC 密钥,正确的 MAC 签名可以作为证明,证明某些消息确实来自银行,并且没有被审计对象伪造。

对于 Oraclize 服务,Oraclize 是审计对象,而一个锁定的 AWS 实例(一个特殊设计的开源亚马逊机器镜像)是审核员。

他们提供的证明数据是 AWS 实例签名认证,证明了 TLSNotary 证明确实发生了。他们还提供了有关 AWS 实例上运行的软件的一些额外证明,即它自初始化以来是否被修改过。

定价

来自任何以太坊地址的第一个 Oraclize 查询调用完全免费。Oraclize 在测试网使用时也是免费的!这只适用于在测试环境中适度使用。

从第二次调用开始,你需要支付以太币来查询。当向 Oraclize 发送查询时(即进行内部事务调用),费用通过将以太币从调用合约转移到 Oraclize 合约的方式扣除。扣除的以太币数量取决于数据源和证明类型。

下面是一个表格,显示了发送查询时扣除的以太币数量:

数据源没有证明有 TLSNotary 证明
URL$0.01$0.05
区块链$0.01$0.05
WolframAlpha$0.03$0.03
IPFS$0.01$0.01

因此,如果你发出 HTTP 请求,并且你也想要 TLSNotary 证明,那么调用合约必须有价值 $0.05 的以太币,否则会抛出异常。

开始使用 Oraclize API

要使用 Oraclize 服务,合约需要继承 usingOraclize 合约。你可以在github.com/Oraclize/Ethereum-api找到这个合约。

usingOraclize 合约充当 OraclizeIOraclizeAddrResolverI 合约的代理。实际上,usingOraclize 使得调用 OraclizeIOraclizeAddrResolverI 合约变得容易,也就是说,它提供了更简单的 API。如果你觉得舒服,你也可以直接调用 OraclizeIOraclizeAddrResolverI 合约。你可以查看这些合约的源代码以找到所有可用的 API。我们只会学习最必要的。

让我们看看如何设置证明类型,设置证明存储位置,进行查询,找到查询的成本,等等。

设置证明类型和存储位置

无论你是否需要来自 Oraclize 的 TLSNotary 证明,你都必须在发出查询之前指定证明类型和证明存储位置。

如果你不需要证明,那么把这段代码放在你的合约中:

oraclize_setProof(proofType_NONE)
  • 1

如果你需要证明,那么把这段代码放在你的合约中:

oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS)
  • 1

目前,proofStorage_IPFS 是唯一可用的证明存储位置;也就是说,TLSNotary 证明仅存储在 IPFS 中。

你可以执行这些方法中的任何一个,例如,在构造函数中或在任何其他时间,如果,例如,你只需要某些查询的证明。

发送查询

要向 Oraclize 发送查询,你需要调用 oraclize_query 函数。该函数至少需要两个参数,即数据源和给定数据源的输入。数据源参数不区分大小写。

以下是 oraclize_query 函数的一些基本示例:

oraclize_query("WolframAlpha", "random number between 0 and 100"); 

oraclize_query("URL", "https://api.kraken.com/0/public/Ticker?pair=ETHXBT"); 

oraclize_query("IPFS", "QmdEJwJG1T9rzHvBD8i69HHuJaRgXRKEQCP7Bh1BVttZbU"); 

oraclize_query("URL", "https://xyz.io/makePayment", '{"currency": "USD", "amount": "1"}');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

以下是前述代码的工作原理:

  • 如果第一个参数是字符串,则假定它是数据源,第二个参数被假定为数据源的输入。在第一个调用中,数据源是 WolframAlpha,我们发送给它的搜索查询是 random number between 0 and 100

  • 在第二次调用中,我们向第二个参数中提供的 URL 发出 HTTP GET 请求。

  • 在第三次调用中,我们从 IPFS 获取 QmdEJwJG1T9rzHvBD8i69HHuJaRgXRKEQCP7Bh1BVttZbU 文件的内容。

  • 如果数据源之后的两个连续参数都是字符串,则假定是一个 POST 请求。在最后一次调用中,我们向 https://xyz.io/makePayment 发出了一个 HTTP POST 请求,而 POST 请求正文内容是第三个参数中的字符串。Oraclize 足够智能,可以根据字符串格式检测内容类型头。

调度查询

如果你希望 Oraclize 在将来的预定时间执行你的查询,只需将延迟(以秒为单位)从当前时间指定为第一个参数。

这是一个例子:

oraclize_query(60, "WolframAlpha", "random number between 0 and 100");
  • 1

前述查询将在被接收到后的 60 秒后由 Oraclize 执行。因此,如果第一个参数是一个数字,则假定我们正在调度一个查询。

自定义 gas

从 Oraclize 到你的 __callback 函数的交易会花费 gas,就像任何其他交易一样。你需要支付给 Oraclize gas 的费用。用于发出查询的以太 oraclize_query 费用也用于在调用 __callback 函数时提供 gas。默认情况下,当调用 __callback 函数时,Oraclize 提供 200,000 gas。

此返回 gas 费用实际上由你控制,因为你在 __callback 方法中编写代码,因此可以估算。因此,当向 Oraclize 发出查询时,你还可以指定 __callback 交易上的 gasLimit 应该是多少。然而,请注意,由于 Oraclize 发送了交易,任何未使用的 gas 都将退还给 Oraclize,而不是你。

如果默认值和最小值 200,000 gas 不够用,你可以通过在此方式中指定不同的 gasLimit 来增加它:

oraclize_query("WolframAlpha", "random number between 0 and 100", 500000);
  • 1

在这里,您可以看到如果最后一个参数是一个数字,则假定为自定义 gas。在前面的代码中,Oraclize 将使用 500k 的gasLimit来进行回调事务,而不是 200k。因为我们要求 Oraclize 提供更多的 gas,所以在调用oraclize_query时,Oraclize 将扣除更多的以太币(取决于需要多少 gas)。

请注意,如果提供的gasLimit太低,并且您的__callback方法很长,您可能永远看不到回调。还请注意,自定义的 gas 必须超过 200k。

回调函数

一旦您的结果准备好,Oraclize 将向您的合约地址发送一个事务,并调用以下三种方法之一:

  • 要么__callback(bytes32 myid, string result)Myid是每个查询的唯一 ID。该 ID 由oraclize_query方法返回。如果您的合约中有多个oraclize_query调用,则用于匹配此结果的查询。

  • 如果您请求 TLS Notary 证明,这是结果:__callback(bytes32 myid, string result, bytes proof)

  • 作为最后的手段,如果其他方法不存在,则回退函数为function()

这是__callback函数的示例:

function __callback(bytes32 myid, string result) { 
    if (msg.sender != oraclize_cbAddress()) throw; // just to be sure the calling address is the Oraclize authorized one 

    //now doing something with the result.. 
}
  • 1
  • 2
  • 3
  • 4
  • 5

解析助手

从 HTTP 请求返回的结果可以是 HTML、JSON、XML、二进制等。在 Solidity 中,解析结果是困难且昂贵的。因此,Oraclize 提供了解析辅助程序,让它在其服务器上处理解析,并且您只获得您需要的结果部分。

要求 Oraclize 解析结果,您需要将 URL 包装在以下其中一个解析助手中:

  • xml(..)json(..)助手可让您要求 Oraclize 仅返回 JSON 或 XML 解析的部分;例如,看看以下内容:

    • 为了获得完整的响应,您使用带有api.kraken.com/0/public/Ticker?pair=ETHUSD URL 参数的URL数据源

    • 如果您只想要最后价格字段,则需要使用 JSON 解析调用,如json(api.kraken.com/0/public/Ticker?pair=ETHUSD).result.XETHZUSD.c.0

  • html(..).xpath(..)助手对 HTML 抓取很有用。只需将要作为xpath(..)参数的 XPATH 指定为您想要的;例如,看看以下内容:

    • 要获取特定推文的文本,请使用html(https://twitter.com/oraclizeit/status/671316655893561344).xpath(//*[contains(@class, 'tweet-text')]/text())
  • binary(..)助手对获取二进制文件(例如证书文件)很有用:

    • 要获取二进制文件的部分内容,可以使用slice(offset,length);第一个参数是偏移量,第二个参数是您想要返回的片段的长度(以字节为单位)。

    • 示例:仅从二进制 CRL 中获取前 300 字节,binary(https://www.sk.ee/crls/esteid/esteid2015.crl).slice(0,300)。二进制助手必须与切片选项一起使用,并且只接受二进制文件(未编码)。

如果服务器无响应或不可访问,我们将发送空响应给你。你可以使用 app.Oraclize.it/home/test_query 测试查询。

获取查询价格

如果你想知道一个查询在进行实际查询之前会花费多少成本,那么你可以使用 Oraclize.getPrice() 函数来获取所需的 wei 金额。它接受的第一个参数是数据源,第二个参数是可选的,是自定义 gas。

这的一个常见用例是通知客户端添加以太到合约中,如果没有足够的以太来进行查询的话。

加密查询

有时,你可能不想透露数据源和/或数据源的输入。例如:如果存在 API 密钥,你可能不想在 URL 中透露它。因此,Oraclize 提供了一种将查询加密存储在智能合约中的方法,只有 Oraclize 的服务器有解密的密钥。

Oraclize 提供了一个 Python 工具(github.com/Oraclize/encrypted-queries),它可用于加密数据源和/或数据输入。它生成一个非确定性加密字符串。

加密任意文本字符串的 CLI 命令如下:

    python encrypted_queries_tools.py -e -p 044992e9473b7d90ca54d2886c7addd14a61109af202f1c95e218b0c99eb060c7134c4ae46345d0383ac996185762f04997d6fd6c393c86e4325c469741e64eca9 "YOUR DATASOURCE or INPUT"
  • 1

你看到的长十六进制字符串是 Oraclize 服务器的公钥。现在你可以使用上述命令的输出来替代数据源和/或数据源的输入。

为了防止加密查询的滥用(即重放攻击),首个使用特定加密查询向 Oraclize 查询的合约将成为其合法所有者。任何其他重复使用完全相同字符串的合约将不被允许使用它,并将收到空结果。因此,在重新部署使用加密查询的合约时,请记住始终生成新的加密字符串。

解密数据源

还有另一个名为 decrypt 的数据源。它用于解密一个加密字符串。但是这个数据源不会返回任何结果;否则,任何人都将有能力解密数据源并为数据源输入。

它专门设计用于在嵌套数据源内部使用,以实现部分查询加密。这是它唯一的用例。

Oraclize Web IDE

Oraclize 提供了一个 Web IDE,你可以在其中编写、编译和测试基于 Oraclize 的应用程序。你可以在 dapps.Oraclize.it/browser-Solidity/ 找到它。

如果你访问链接,你会发现它看起来与浏览器 Solidity 完全相同。实际上,它就是浏览器 Solidity 多了一个额外的功能。要理解这个功能是什么,我们需要更深入地了解浏览器 Solidity。

浏览器 Solidity 不仅让我们能够为我们的合约编写、编译和生成 web3.js 代码,而且还可以在其中测试这些合约。到目前为止,为了测试我们的合约,我们一直在设置以太坊节点并向其发送交易。但是浏览器 Solidity 可以在不连接到任何节点的情况下执行合约,一切都发生在内存中。它通过使用 ethereumjs-vm 实现了这一点,ethereumjs-vm 是以太坊虚拟机的 JavaScript 实现。使用 ethereumjs-vm,您可以创建自己的 EVM 并运行字节码。如果需要,我们可以配置浏览器 Solidity 使用提供连接的以太坊节点的 URL。用户界面非常信息丰富,因此您可以自己尝试所有这些。

Oraclize web IDE 的特殊之处在于它在内存执行环境中部署了 Oraclize 合约,因此您不必连接到测试网络或主网络节点,但如果使用浏览器 Solidity,则必须连接到测试网络或主网络节点以测试 Oraclize API。

您可以在 dev.Oraclize.it/ 找到更多关于集成 Oraclize 的资源。

处理字符串

在 Solidity 中处理字符串不像在其他高级编程语言(如 JavaScript、Python 等)中那样简单。因此,许多 Solidity 程序员创建了各种库和合约,以便轻松处理字符串。

strings 库是最受欢迎的字符串实用程序库。它让我们可以将字符串转换为称为切片的东西,从而实现连接、拼接、拆分、比较等操作。切片是一个结构体,它保存了字符串的长度和地址。由于切片只需指定偏移量和长度,复制和操作切片比复制和操作它们引用的字符串要便宜得多。

为了进一步减少 gas 成本,大多数需要返回切片的切片函数会修改原始切片而不是分配一个新的;例如,s.split(".") 将返回到第一个 "." 之前的文本,修改 s 以仅包含 "." 之后的字符串。在不想修改原始切片的情况下,可以使用 .copy() 复制,例如,s.copy().split(".")。尽量避免在循环中使用这种习惯用法;由于 Solidity 没有内存管理,这将导致分配许多后来将被丢弃的短期切片。

有必要复制字符串数据的函数将返回字符串而不是切片;如果需要,这些可以转换回切片进行进一步处理。

让我们看一些使用 strings 库处理字符串的示例:

pragma Solidity ⁰.4.0; 

import "github.com/Arachnid/Solidity-stringutils/strings.sol"; 

contract Contract 
{ 
    using strings for *; 

    function Contract() 
    { 
        //convert string to slice 
        var slice = "xyz abc".toSlice(); 

        //length of string 
        var length = slice.len(); 

        //split a string 
        //subslice = xyz 
        //slice = abc 
        var subslice = slice.split(" ".toSlice()); 

        //split a string into an array 
        var s = "www.google.com".toSlice(); 
        var delim = ".".toSlice(); 
        var parts = new string[](s.count(delim)); 
        for(uint i = 0; i < parts.length; i++) { 
            parts[i] = s.split(delim).toString(); 
        } 

        //Converting a slice back to a string 
        var myString = slice.toString(); 

        //Concatenating strings 
        var finalSlice = subslice.concat(slice); 

        //check if two strings are equal 
        if(slice.equals(subslice)) 
        { 

        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

前述代码不言自明。

返回两个切片的函数有两个版本:一个是非分配版本,将第二个切片作为参数传入,修改原地;另一个是分配版本,分配并返回第二个切片;例如,让我们看一下以下示例:

var slice1 = "abc".toSlice(); 

//moves the string pointer of slice1 to point to the next rune (letter) 
//and returns a slice containing only the first rune 
var slice2 = slice1.nextRune(); 

var slice3 = "abc".toSlice(); 
var slice4 = "".toSlice(); 

//Extracts the first rune from slice3 into slice4, advancing the slice to point to the next rune and returns slice4\. 
var slice5 = slice3.nextRune(slice4);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

您可以在github.com/Arachnid/Solidity-stringutils了解更多关于 strings 库的信息。

构建赌注合同

在我们的下注应用程序中,两个人可以选择在一场足球比赛上进行下注,一个人支持主队,另一个人支持客队。他们两人都应该下相同金额的赌注,获胜者将赢得所有的钱。如果比赛平局,那么他们两人将拿回他们的钱。

我们将使用 FastestLiveScores API 来查找比赛结果。它提供了一个免费的 API,让我们每小时可以免费进行 100 次请求。首先,去创建一个账号,然后生成一个 API 密钥。要创建一个账号,请访问customer.fastestlivescores.com/register,一旦账号创建完成,您将在customer.fastestlivescores.com/看到 API 密钥。您可以在docs.crowdscores.com/找到 API 文档。

在我们的应用程序中,每个人之间的每笔赌注都将部署一个赌注合同。合同将包含从FastestLiveScoresAPI 检索的比赛 ID、各方需要投资的 wei 金额以及各方的地址。一旦双方都投资了合同,他们将会得知比赛的结果。如果比赛尚未结束,那么他们将尝试在每 24 小时后检查结果。

这是合同的代码:

pragma Solidity ⁰.4.0; 

import "github.com/Oraclize/Ethereum-api/oraclizeAPI.sol"; 
import "github.com/Arachnid/Solidity-stringutils/strings.sol"; 

contract Betting is usingOraclize 
{ 
    using strings for *; 

    string public matchId; 
    uint public amount; 
    string public url; 

    address public homeBet; 
    address public awayBet; 

    function Betting(string _matchId, uint _amount, string _url)  
    { 
        matchId = _matchId; 
        amount = _amount; 
        url = _url; 

        oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS); 
    } 

    //1 indicates home team 
    //2 indicates away team 
    function betOnTeam(uint team) payable 
    { 

        if(team == 1) 
        { 
            if(homeBet == 0) 
            { 
                if(msg.value == amount) 
                { 
                    homeBet = msg.sender;    
                    if(homeBet != 0 && awayBet != 0) 
                    { 
                        oraclize_query("URL", url); 
                    } 
                } 
                else 
                { 
                    throw; 
                } 
            } 
            else 
            { 
                throw; 
            } 
        } 
        else if(team == 2) 
        { 
            if(awayBet == 0) 
            { 
                if(msg.value == amount) 
                { 
                    awayBet = msg.sender;           

                    if(homeBet != 0 && awayBet != 0) 
                    { 
                        oraclize_query("URL", url); 
                    } 
                } 
                else 
                { 
                    throw; 
                } 
            } 
            else 
            { 
                throw; 
            } 
        } 
        else 
        { 
            throw; 
        } 
    } 

    function __callback(bytes32 myid, string result, bytes proof) { 
        if (msg.sender != oraclize_cbAddress()) 
        { 
            throw;     
        } 
        else 
        { 
            if(result.toSlice().equals("home".toSlice())) 
            { 
                homeBet.send(this.balance); 
            } 
            else if(result.toSlice().equals("away".toSlice())) 
            { 
                awayBet.send(this.balance); 
            } 
            else if(result.toSlice().equals("draw".toSlice())) 
            { 
                homeBet.send(this.balance / 2); 
                awayBet.send(this.balance / 2); 
            } 
            else 
            { 
                if (Oraclize.getPrice("URL") < this.balance)  
                { 
                    oraclize_query(86400, "URL", url); 
                } 
            } 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

合同代码是不言自明的。现在使用solc.js或浏览器 Solidity 编译上述代码,具体取决于您所熟悉的情况。您将不需要链接strings库,因为其中的所有函数都设置为internal可见性。

在浏览器 Solidity 中,当指定从 HTTP URL 导入库或合同时,请确保它托管在 GitHub 上;否则,它将无法获取。在该 GitHub 文件 URL 中,请确保删除协议以及blob/{branch-name}

构建一个用于赌注合同的客户端

为了方便查找比赛 ID、部署和投资合同,我们需要构建一个 UI 客户端。所以让我们开始构建一个客户端,它将有两条路径,即主页路径用于部署合同和在比赛上下注,另一条路径用于查找比赛列表。我们将让用户使用他们自己的离线账户部署和下注,这样下注过程将以分散的方式进行,没有人可以作弊。

在我们开始构建客户端之前,请确保您已同步测试网,因为 Oraclize 仅在以太坊的测试网/主网上运行,而不在私有网络上运行。您可以通过用--testnet选项替换--dev选项来切换到测试网并开始下载测试网区块链。例如,请看以下内容:

geth --testnet --rpc --rpccorsdomain "*" --rpcaddr "0.0.0.0" --rpcport "8545"
  • 1

预测结构

在本章的练习文件中,您将找到两个目录,即 Final 和 Initial。Final 包含项目的最终源代码,而 Initial 包含空源代码文件和库,可以快速开始构建应用程序。

要测试 Final 目录,您需要在其中运行 npm install,然后使用 node app.js 命令在 Final 目录内运行应用程序。

Initial 目录中,您将找到一个 public 目录和两个名为 app.jspackage.json 的文件。package.json 文件包含了我们应用的后端依赖,而 app.js 则是您要放置后端源代码的地方。

public 目录包含与前端有关的文件。在 public/css 中,您将找到 bootstrap.min.css,这是 bootstrap 库。在 public/html 中,您将找到 index.htmlmatches.ejs 文件,您将在其中放置我们应用的 HTML 代码,以及在 public/js 目录中,您将找到 web3.js 和 ethereumjs-tx 的 js 文件。在 public/js 中,您还将找到一个 main.js 文件,您将在其中放置我们应用的前端 JS 代码。您还将找到 Oraclize Python 工具来加密查询。

构建后端

让我们先构建应用程序的后端。首先,在初始目录内运行npm install来安装我们的后端所需的依赖。

这里是运行 express 服务并提供 index.html 文件和静态文件以及设置视图引擎的后端代码:

var express = require("express"); 
var app = express(); 

app.set("view engine", "ejs"); 

app.use(express.static("public")); 

app.listen(8080); 

app.get("/", function(req, res) { 
    res.sendFile(__dirname + "/public/html/index.html"); 
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

上面的代码是不言而喻的。现在让我们继续。我们的应用将有另一个页面,该页面将显示最近匹配的 ID 列表和结果(如果匹配已经结束)。这是端点的代码:

var request = require("request"); 
var moment = require("moment"); 

app.get("/matches", function(req, res) { 
    request("https://api.crowdscores.com/v1/matches?api_key=7b7a988932de4eaab4ed1b4dcdc1a82a", function(error, response, body) { 
        if (!error && response.statusCode == 200) { 
            body = JSON.parse(body); 

            for (var i = 0; i < body.length; i++) { 
             body[i].start = moment.unix(body[i].start / 
               1000).format("YYYY MMM DD hh:mm:ss"); 
            } 

            res.render(__dirname + "/public/html/matches.ejs", { 
                matches: body 
            }); 
        } else { 
            res.send("An error occured"); 
        } 
    }) 
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在这里,我们正在进行 API 请求以获取最近匹配的列表,然后将结果传递给 matches.ejs 文件,以便它可以在用户友好的界面中呈现结果。API 结果以时间戳的形式给出了匹配的开始时间;因此,我们使用 moment 将其转换为人类可读的格式。我们从后端而不是从前端进行此请求,以便我们不会向用户公开 API 密钥。

我们的后端将为前端提供一个 API,通过该 API 前端可以在部署合同之前加密查询。我们的应用程序不会提示用户创建 API 密钥,因为这将是一个糟糕的用户体验实践。应用程序的开发者控制 API 密钥不会造成任何伤害,因为开发者无法修改来自 API 服务器的结果;因此,即使应用程序的开发者知道 API 密钥,用户仍将信任该应用程序。

这里是加密端点的代码:

var PythonShell = require("python-shell"); 

app.get("/getURL", function(req, res) { 
    var matchId = req.query.matchId; 

    var options = { 
        args: ["-e", "-p", "044992e9473b7d90ca54d2886c7addd14a61109af202f1c95e218b0c99eb060c7134c4ae46345d0383ac996185762f04997d6fd6c393c86e4325c469741e64eca9", "json(https://api.crowdscores.com/v1/matches/" + matchId + "?api_key=7b7a988932de4eaab4ed1b4dcdc1a82a).outcome.winner"], 
        scriptPath: __dirname 
    }; 

    PythonShell.run("encrypted_queries_tools.py", options, function 
      (err, results) { 
        if(err) 
        { 
            res.send("An error occured"); 
        } 
        else 
        { 
            res.send(results[0]); 
        } 
    }); 
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

我们已经看到如何使用这个工具。为了成功运行这个端点,请确保你的系统上安装了 Python。即使 Python 已经安装,端点可能仍然会显示错误,表明 Python 的加密和 base58 模块没有安装。因此,如果工具提示你安装这些模块,请确保你安装了它们。

构建前端

现在让我们构建应用程序的前端。我们的前端将允许用户查看最近比赛的列表、部署投丨注合约、投丨注比赛,并查看有关投丨注合约的信息。

我们首先来实现 matches.ejs 文件,它将显示最近比赛的列表。下面是该文件的代码:

<!DOCTYPE html>
<html lang="en">
    <head> 
         <meta charset="utf-8"> 
         <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 
         <meta http-equiv="x-ua-compatible" content="ie=edge"> 
         <link rel="stylesheet" href="/css/bootstrap.min.css"> 
     </head> 
     <body> 
         <div class="container"> 
             <br> 
             <div class="row m-t-1"> 
                 <div class="col-md-12"> 
                     <a href="/">Home</a> 
                 </div> 
             </div> 
             <br> 
             <div class="row"> 
                 <div class="col-md-12"> 
                     <table class="table table-inverse"> 
                           <thead> 
                             <tr> 
                                 <th>Match ID</th> 
                                 <th>Start Time</th> 
                                 <th>Home Team</th> 
                                 <th>Away Team</th> 
                                 <th>Winner</th> 
                             </tr> 
                           </thead> 
                           <tbody> 
                               <% for(var i=0; i < matches.length; i++) { %> 
                                   <tr> 
                                       <td><%= matches[i].dbid %></td> 
                                       <% if (matches[i].start) { %> 
                                        <td><%= matches[i].start %></td> 
                                     <% } else { %> 
                                         <td>Time not finalized</td> 
                                     <% } %> 
                                       <td><%= matches[i].homeTeam.name %></td> 
                                       <td><%= matches[i].awayTeam.name %></td> 
                                       <% if (matches[i].outcome) { %> 
                                        <td><%= matches[i].outcome.winner %></td> 
                                     <% } else { %> 
                                         <td>Match not finished</td> 
                                     <% } %> 
                                 </tr> 
                             <% } %> 
                           </tbody> 
                     </table> 
                 </div> 
             </div> 
         </div> 
     </body> 
 </html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

前面的代码不言自明。现在让我们编写主页的 HTML 代码。我们的主页将显示三个表单。第一个表单是用于部署投丨注合约,第二个表单是用于投资投丨注合约,第三个表单是用于显示已部署的投丨注合约的信息。

这是主页的 HTML 代码。将这个代码放在 index.html 页面中:

<!DOCTYPE html> 
 <html lang="en"> 
     <head> 
         <meta charset="utf-8"> 
         <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 
         <meta http-equiv="x-ua-compatible" content="ie=edge"> 
         <link rel="stylesheet" href="/css/bootstrap.min.css"> 
     </head> 
     <body> 
         <div class="container"> 
             <br> 
             <div class="row m-t-1"> 
                 <div class="col-md-12"> 
                     <a href="/matches">Matches</a> 
                 </div> 
             </div> 
             <br> 
             <div class="row"> 
                 <div class="col-md-4"> 
                     <h3>Deploy betting contract</h3> 
                     <form id="deploy"> 
                         <div class="form-group"> 
                             <label>From address: </label> 
                             <input type="text" class="form-control" id="fromAddress"> 
                         </div> 
                         <div class="form-group"> 
                             <label>Private Key: </label> 
                             <input type="text" class="form-control" id="privateKey"> 
                         </div> 
                         <div class="form-group"> 
                             <label>Match ID: </label> 
                             <input type="text" class="form-control" id="matchId"> 
                         </div> 
                         <div class="form-group"> 
                             <label>Bet Amount (in ether): </label> 
                             <input type="text" class="form-control" id="betAmount"> 
                         </div> 
                         <p id="message" style="word-wrap: break-word"></p> 
                         <input type="submit" value="Deploy" class="btn btn-primary" /> 
                     </form> 
                 </div> 
                 <div class="col-md-4"> 
                     <h3>Bet on a contract</h3> 
                     <form id="bet"> 
                         <div class="form-group"> 
                             <label>From address: </label> 
                             <input type="text" class="form-control" id="fromAddress"> 
                         </div> 
                         <div class="form-group"> 
                             <label>Private Key: </label> 
                             <input type="text" class="form-control" id="privateKey"> 
                         </div> 
                         <div class="form-group"> 
                             <label>Contract Address: </label> 
                             <input type="text" class="form-control"
id="contractAddress"> 
                         </div> 
                         <div class="form-group"> 
                             <label>Team: </label> 
                             <select class="form-control" id="team"> 
                                 <option>Home</option> 
                                 <option>Away</option> 
                             </select> 
                         </div> 
                         <p id="message" style="word-wrap: break-word"></p> 
                         <input type="submit" value="Bet" class="btn btn-primary" /> 
                     </form> 
                 </div> 
                 <div class="col-md-4"> 
                     <h3>Display betting contract</h3> 
                     <form id="find"> 
                         <div class="form-group"> 
                             <label>Contract Address: </label> 
                             <input type="text" class="form-control"  
 d="contractAddress"> 
                         </div> 
                         <p id="message"></p> 
                         <input type="submit" value="Find" class="btn btn-primary" /> 
                     </form> 
                 </div> 
             </div> 
         </div> 

         <script type="text/javascript" src="img/web3.min.js"></script> 
         <script type="text/javascript" src="img/ethereumjs-tx.js"></script> 
         <script type="text/javascript" src="img/main.js"></script> 
     </body> 
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

前面的代码不言自明。现在让我们编写用于实际部署合约、投资合约和显示合约信息的 JavaScript 代码。这里是所有这些的代码。将这个代码放在 main.js 文件中:

var bettingContractByteCode = "6060604..."; 
var bettingContractABI = [{"constant":false,"inputs":[{"name":"team","type":"uint256"}],"name":"betOnTeam","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"myid","type":"bytes32"},{"name":"result","type":"string"}],"name":"__callback","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"myid","type":"bytes32"},{"name":"result","type":"string"},{"name":"proof","type":"bytes"}],"name":"__callback","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"url","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"matchId","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"amount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"homeBet","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"awayBet","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"_matchId","type":"string"},{"name":"_amount","type":"uint256"},{"name":"_url","type":"string"}],"payable":false,"type":"constructor"}]; 

var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 

function getAJAXObject() 
{ 
   var request; 
   if (window.XMLHttpRequest) { 
       request = new XMLHttpRequest(); 
   } else if (window.ActiveXObject) { 
       try { 
           request = new ActiveXObject("Msxml2.XMLHTTP"); 
       } catch (e) { 
           try { 
               request = new ActiveXObject("Microsoft.XMLHTTP"); 
           } catch (e) {} 
       } 
   } 

   return request; 
} 

document.getElementById("deploy").addEventListener("submit", function(e){ 
   e.preventDefault(); 

   var fromAddress = document.querySelector("#deploy #fromAddress").value; 
   var privateKey = document.querySelector("#deploy #privateKey").value; 
   var matchId = document.querySelector("#deploy #matchId").value; 
   var betAmount = document.querySelector("#deploy #betAmount").value; 

   var url = "/getURL?matchId=" + matchId; 

   var request = getAJAXObject(); 

   request.open("GET", url); 

   request.onreadystatechange = function() { 
       if (request.readyState == 4) { 
           if (request.status == 200) { 
               if(request.responseText != "An error occured") 
               { 
           var queryURL = request.responseText; 

           var contract = web3.eth.contract(bettingContractABI); 
           var data = contract.new.getData(matchId, 
             web3.toWei(betAmount, "ether"), queryURL, { 
               data: bettingContractByteCode 
                }); 

           var gasRequired = web3.eth.estimateGas({ data: "0x" + data
             }); 

      web3.eth.getTransactionCount(fromAddress, function(error, nonce){ 

       var rawTx = { 
            gasPrice: web3.toHex(web3.eth.gasPrice), 
             gasLimit: web3.toHex(gasRequired), 
              from: fromAddress, 
               nonce: web3.toHex(nonce), 
                data: "0x" + data, 
                 }; 

      privateKey = EthJS.Util.toBuffer(privateKey, "hex"); 

       var tx = new EthJS.Tx(rawTx); 
       tx.sign(privateKey); 

      web3.eth.sendRawTransaction("0x" + 
       tx.serialize().toString("hex"), function(err, hash) { 
            if(!err) 
                {document.querySelector("#deploy #message").
                   innerHTML = "Transaction Hash: " + hash + ". 
                     Transaction is mining..."; 

            var timer = window.setInterval(function(){ 
            web3.eth.getTransactionReceipt(hash, function(err, result){ 
            if(result) 
             {window.clearInterval(timer); 
       document.querySelector("#deploy #message").innerHTML = 
         "Transaction Hash: " + hash + " and contract address is: " + 
             result.contractAddress;} 
               }) 
                }, 10000) 
                 } 
             else 
           {document.querySelector("#deploy #message").innerHTML = err; 
             } 
           }); 
          }) 

          } 
           } 
       } 
   }; 

   request.send(null); 

}, false) 

document.getElementById("bet").addEventListener("submit", function(e){ 
   e.preventDefault(); 

   var fromAddress = document.querySelector("#bet #fromAddress").value; 
   var privateKey = document.querySelector("#bet #privateKey").value; 
   var contractAddress = document.querySelector("#bet #contractAddress").value; 
   var team = document.querySelector("#bet #team").value; 

   if(team == "Home") 
   { 
         team = 1; 
   } 
   else 
   { 
         team = 2; 
   }  

   var contract = web3.eth.contract(bettingContractABI).at(contractAddress); 
   var amount = contract.amount(); 

   var data = contract.betOnTeam.getData(team); 

   var gasRequired = contract.betOnTeam.estimateGas(team, { 
         from: fromAddress, 
         value: amount, 
         to: contractAddress 
   }) 

   web3.eth.getTransactionCount(fromAddress, function(error, nonce){ 

         var rawTx = { 
           gasPrice: web3.toHex(web3.eth.gasPrice), 
           gasLimit: web3.toHex(gasRequired), 
           from: fromAddress, 
           nonce: web3.toHex(nonce), 
           data: data, 
           to: contractAddress, 
           value: web3.toHex(amount) 
       }; 

       privateKey = EthJS.Util.toBuffer(privateKey, "hex"); 

       var tx = new EthJS.Tx(rawTx); 
         tx.sign(privateKey); 

         web3.eth.sendRawTransaction("0x" + tx.serialize().toString("hex"), function(err, hash) { 
               if(!err) 
               { 
    document.querySelector("#bet #message").innerHTML = "Transaction 
      Hash: " + hash; 
        } 
      else 
       { 
       document.querySelector("#bet #message").innerHTML = err; 
      } 
     }) 
   }) 
    }, false) 

document.getElementById("find").addEventListener("submit", function(e){ 
   e.preventDefault(); 

   var contractAddress = document.querySelector("#find 
     #contractAddress").value; 
   var contract =  
      web3.eth.contract(bettingContractABI).at(contractAddress); 

   var matchId = contract.matchId(); 
   var amount = contract.amount(); 
   var homeAddress = contract.homeBet(); 
   var awayAddress = contract.awayBet(); 

   document.querySelector("#find #message").innerHTML = "Contract balance is: " + web3.fromWei(web3.eth.getBalance(contractAddress), "ether") + ", Match ID is: " + matchId + ", bet amount is: " + web3.fromWei(amount, "ether") + " ETH, " + homeAddress + " has placed bet on home team and " + awayAddress + " has placed bet on away team"; 
}, false)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174

前面代码的工作原理如下:

  1. 首先,我们将合约的字节码和 ABI 分别存储在 bettingContractByteCodebettingContractABI 变量中。

  2. 然后,我们创建一个连接到测试网节点的 Web3 实例。

  3. 接下来,我们有 getAJAXObject 函数(一个跨浏览器兼容的函数),它返回一个 AJAX 对象。

  4. 然后,我们将 submit 事件监听器附加到第一个表单上,该表单用于部署合约。在事件监听器的回调中,我们通过传递 matchIdgetURL 端点发出请求,以获取加密的查询字符串。然后,我们生成用于部署合约的数据。接着,我们找出所需的 gas。我们使用函数对象的 estimateGas 方法来计算所需的 gas,但你也可以使用 web3.eth.estimateGas 方法。两者在参数上有所不同;也就是说,在前一种情况下,你不需要传递交易数据。请记住,如果函数调用抛出异常,estimateGas 会返回区块 gas 限制。然后,我们计算随机数。这里,我们只是使用 getTransactionCount 方法,而不是我们之前学到的实际流程。这样做只是为了简化代码。然后,我们创建原始交易,签署它并广播。一旦交易被挖掘,我们就显示合约地址。

  5. 然后,我们为第二个表格附加了一个submit事件侦听器,用于投资合约。在这里,我们生成了交易的data部分,计算所需的 gas,创建原始交易,签名并广播。在计算交易所需的 gas 时,我们从帐户地址和 value 对象属性传递合约地址的值,因为这是一个函数调用,gas 因取决于值、from 地址和合约地址而异。请记住,在寻找调用合同函数所需的 gas 时,您可以传递to,fromvalue属性,因为 gas 取决于这些值。

  6. 最后,我们为第三个表单添加了一个submit事件侦听器,即显示已部署投丨注合同的信息。

测试客户端

现在我们已经完成了建设我们的投丨注平台,是时候进行测试了。在测试之前,请确保测试网区块链已完全下载并正在寻找新的入块。

现在使用我们之前建立的钱包服务,生成三个帐户。使用faucet.ropsten.be:3001/给每个帐户加一 ether。

然后,在Initial目录中运行node app.js,然后访问http://localhost:8080/matches,您将看到以下屏幕截图中显示的内容:

在这里,您可以复制任何比赛 ID。假设您想要测试第一场比赛,即 123945。现在访问http://localhost:8080,您将看到以下屏幕截图中显示的内容:

现在通过填写第一个表格中的输入字段并单击“部署”按钮来部署合同,如图所示。使用您的第一个帐户来部署合同。

现在从第二个账户对合同的主队进行下注,从第三个账户对客队进行下注,如以下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在将合约地址放在第三个表格上,然后单击“查找”按钮,以查看有关合约的详细信息。您将看到类似于以下屏幕截图的内容:

一旦两个交易都被挖掘,再次检查合同的详细信息,您将看到类似于以下屏幕截图的内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这里,您可以看到该合约没有任何以太币,所有以太币都转入了下注主队的账户。

总结

在这一章中,我们深入学习了关于 Oraclize 和 strings 库。我们将它们结合起来构建了一个去中心化的投丨注平台。现在,你可以根据自己的需求前进并定制合同和客户端。为了增强应用程序,你可以向合同添加事件,并在客户端上显示通知。我们的目标是理解去中心化投丨注应用程序的基本架构。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/923013
推荐阅读
相关标签
  

闽ICP备14008679号