第一天-应用程序介绍、安装和设置
区块链是一个如此庞大的话题,它甚至有可能变得富有成效,启动你的区块链项目,或者在短短一周内获得区块链开发者的工作吗?区块链技术出现在每个行业,从我们如何银行到我们如何旅行,我们如何证明我们的身份,甚至我们如何获得医疗保健,等等。所以,这让我们想到了一个问题,你真的能在短短七天内学会创建一个区块链应用程序吗?
在这个项目中,你将在七天内学会如何使用以太坊区块链创建一个在线游戏应用程序!在这个过程中,您将学习如何使用 Solidity 创建和使用变量,以及如何使用函数执行业务逻辑;您将通过学习编写测试来消除代码中的错误,并且您将通过使用 React 和 Redux 的用户界面与以太坊区块链进行交互。您将编写代码从您的分散式应用程序发送和接收资金,最后您将学习如何将您的分散式应用程序部署到以太坊网络和 Amazon Web Services。在这本书里,我们将把一切都分解到最基本的部分,所以你可以相信,想要成功,你唯一需要的就是成为区块链开发者的愿望和付出努力的意愿。
本章将作为开始这个项目的踏脚石,包括以下主题:
- 我们的应用程序简介
- 安装所需的工具
- 创建我们的第一个智能合同
- 理解基本语法
- 编写您的第一个测试
我们的应用程序简介
现在,让我们来看看我们将要构建的应用程序。我们将构建一个游戏应用程序,向玩家显示 0 到 9 之间的数字,然后他们可以选择打赌一个神秘数字是高于还是低于显示的数字,然后它可以打赌他们愿意赌多少。他们的游戏结果显示在历史窗口中,当他们赢或输时,钱会自动添加到他们的以太坊帐户中或从其帐户中扣除。下面的屏幕截图展示了该应用程序的演示:
为了构建这个,您将使用 Solidity 来创建包含并执行区块链网络游戏规则的契约。用户界面将用 React 编写,您将使用 Solidity 来编写合同代码。您将了解一个名为 Ganache 的工具,它允许您在本地工作站上托管自己的以太坊区块链。我们现在准备开始构建应用程序。让我们从在我们的工作站上设置所有需要的工具开始。
安装所需的工具
让我们来看看你需要的所有工具,不仅仅是为了这本书,也是为了成为一名成功的区块链开发者。我们将了解用于托管我们的应用程序、编译和迁移我们的代码以及测试我们的应用程序的所有技术。那么让我们开始使用我们的第一个工具吧!
Visual Studio 代码
我们将使用的第一个工具是 Visual Studio 代码。最简单的形式,它是一个文本编辑器,但是随着你对它越来越熟悉,它会成为你作为区块链开发者灵魂的一部分。它包含了一系列可以让你的生活变得更简单的特性,比如语法高亮、智能感知,以及针对特定编程语言的扩展,比如我们将在本书中使用的语言:JavaScript 和 Solidity。我们将使用 Visual Studio 代码,通过 Solidity 编程语言来创建我们的智能契约。
要安装它,请访问以下网站:https://code.visualstudio.com/,并为您的操作系统下载安装包。您应该会看到一个类似于以下屏幕截图的登录页面:
这是一个非强制性的工具;你真正需要的是一个你熟悉的代码编辑器,所以如果你已经有了一个编辑器,你也可以在本书中继续使用它。
我们的智能合约为我们的应用程序提供了两条规则——它是用一种叫做 Solidity 的编程语言编写的,它存在于以太坊区块链网络上。另一种看待合同的方式是想象购买一辆汽车。这样做,你同意购买价格,首期付款,并可能融资条款。所有这些细节都写进了你签署的购车合同中。我们将把这些细节放入智能合同中,由你或买家用密码签署,而不是打印出合同并签署一张纸。
我们还将使用 Visual Studio 代码来编写 JavaScript,为我们的应用程序呈现我们网站的用户界面。我们将特别使用 React 和 Redux 来创建用户界面。最终,我们希望有人访问我们的网站,服务器向他们发送包含我们应用程序的网页。为此,我们将使用 Node.js,因此您需要安装它。
节点. js
正如我们在上一节中提到的,我们需要一个网络框架,我们可以使用它来发送和接收来自我们网站的数据。Node.js 是世界范围内用于此目的的最流行的框架之一。
Node.js 主要用于实现可以在浏览器外运行 JavaScript 代码的后端 API。在本书中,我们将使用它作为一种方式来连接我们的合同,图形用户界面和编程后端到我们的网站。你可以去https://nodejs.org/en/download/安装 Node.js,并为你的操作系统选择合适的软件包。Node.js 的登录页面类似于下面的屏幕截图:
块菌框架和加纳切
当用户在我们的应用程序中采取一个需要写入区块链的动作时,它被称为一个事务。交易不是立即写的;相反,它被发送到网络,在那里等待,直到它被矿工确认为有效的交易。一旦矿工确认了它,它就被写入区块链,此时我们可以向用户提供更新的状态信息。
现在,以太坊网络上的所有这些都代表了数十万台服务器,但我们没有数十万台服务器只是躺在那里,你也不想在开发期间每次需要测试时都在外部服务器上等待。
所以,我们要用 Ganache 来模拟我们自己的以太坊网络。这是一个独立的应用程序,可以在你自己的工作站上创建一个以太坊测试网络。要安装 Ganache,我们将前往https://truffleframework.com/ganache,并下载其安装包。下面的屏幕截图显示了其登录页面:
一旦完成,我们需要安装 Truffle 框架。这是一个以太坊开发框架,使我们更容易做事情,比如编译我们的合同,与区块链网络交互,以及将我们的合同迁移到以太坊网络。
为此,我们将打开一个终端,无论是 Bash shell 还是 Windows 命令提示符,我们将键入以下命令:
$ npm install -g truffle
注意,npm
被打包成 Node.js 的一部分,这意味着如果您跳过了安装 Node.js 这一步,您需要返回并完成它,然后这个命令才能起作用。
现在,为了获得我们的应用程序所需的代码,我们将使用以下命令:
$ truffle unbox github_url
该命令为您下载并设置代码。请确保用该书的实际 GitHub 库替换前面块中的github_url
值!
运行truffle
命令时可能会出现一些错误。这是一个已知问题,已作为官方文档的一部分嵌入。你可以参考下面的链接找到它的解决方法:https://truffle framework . com/docs/truffle/reference/configuration # resolving-naming-conflicts-on-windows。
我们的先决条件已经完成,是时候开始一些更有趣的东西了,包括编写一些代码。在下一节中,我们将看看写合同意味着什么,以及作为区块链网络的一部分,它是如何交互的。
创建我们的第一个智能合同
在本节中,您将学习以太坊合约的基础知识。您将了解它们是什么,它们住在哪里,如何创建它们,以及如何将它们部署到以太坊网络。
一个契约是由它的功能和数据,或者由契约的当前状态表示的代码的集合。它驻留在以太坊网络上的特定地址,需要记住的重要一点是,以太坊网络是公开的,这意味着任何人都可以查看你的合同及其数据。
这不同于你可能熟悉的传统应用。在传统应用程序中,代码通常存储在应用程序中,而数据则存储在其他地方,要么是磁盘上的文件,要么是数据库。现在,让我们来看看合同是由什么组成的。
分析合同
我们合同的源代码存储在contracts
文件夹中。当我们编译它们时,它们会被送到区块链以太坊。编译还会创建一个存储在build/contracts
文件夹中的合同元数据。下面的屏幕截图显示了我们将要构建的应用程序的结构:
契约本身以一个pragma
语句开始,pragma
语句告诉编译器关于代码的信息。在我们的例子中,我们想告诉编译器这段代码是用 Solidity 编程语言编写的,它使用的是 Solidity 语言的0.5.0
版本。因此,提到这一点的代码如下所示:
pragma solidity 0.5.0
您在前面的代码块中看到的版本信息是 SemVer 或语义版本化。基本上,在 SemVer 中,第一个数字代表包的主要版本,中间的数字代表次要版本,第三个数字代表补丁级别。
升级时要记住的一个关键是,升级一个版本可能会使某些东西不兼容;然而,SemVer 之后的应用程序的一个好处是,如果您引入了与以前版本不向后兼容的更改,您可以增加主版本号,以告诉编译器使用该工具的最新补丁。此外,这并不常见,但一些应用程序也会引入小补丁编号的突破性变化,这就是为什么我们在代码中使用^
符号,用于旧版本的 Solidity。这个^
符号告诉编译器可以使用从0.4.24
到0.5.0
的任何版本的 Solidity 编程语言。这确保了你正在使用的编译器版本与你编写它的版本兼容。然而,因为我们在这里使用了0.5.0
,所以我们不会合并它。
为了声明一个契约,我们使用关键字contract
后跟我们的契约名称,通常的惯例是这个契约名称跟在文件名称后面,所以这个Gaming
契约的文件应该是Gaming.sol
。我们有一组开始括号{
,然后如果你需要包含注释,你可以这样做,就像你在 SQL 编程语言中做的那样,用/*
符号,我们用}
符号结束我们的合同。这可以在下面的代码片段中看到:
pragma solidity 0.5.0;
contract Gaming {
/* Our Online gaming contract*/
}
Solidity 中有一个特殊的函数叫做构造函数,它只在契约创建时运行一次。它通常用于初始化合同数据。例如,让我们看看下面的代码片段:
pragma solidity 0.5.0;
contract Gaming {
/* Our Online gaming contract*/
address owner;
bool online;
constructor() public {
owner = msg.sender;
online = true;
}
}
如前面的代码片段所示,我们有一个名为owner
的变量和一个名为online
的变量。当契约被创建时,我们将owner
变量设置为将契约推入网络的以太坊地址,我们还将online
变量设置为 true。
Solidity 是一种编译语言,为了在区块链上使用契约,它必须被编译并迁移到那个网络。为了编译它,我们将使用 Truffle 框架。我们可以使用truffle compile
命令来完成,该命令创建一个 JSON 文件,其中包含关于您的合同的元数据信息,我们将使用该 JSON 文件与合同进行交互,并验证其来源。
为了在区块链网络上使用我们的合同,我们必须把它从我们的工作站传到网络上,这就是所谓的迁移。因为我们使用的是 Truffle 框架,所以我们可以用truffle migrate
命令轻松做到这一点。
现在,如果我们看一下我们的目录布局,在migrations
目录中,我们会发现一个名为1_initial_migration.js
的文件。该文件由 Truffle 框架提供,它处理合同的部署。让我们看看该文件中的代码:
var Migrations =
artifacts.require("./Migrations.sol");
module.exports = function(deployer){
deployer.deploy(Migrations);
};
有一个名为Migrations
的变量需要松露库Migrations.sol
,然后它导出一个函数,该函数采用一个deployer
对象来部署迁移。
我们还下载了一个名为2_deploy_contracts.js
的文件。这是实际上要迁移我们作为本书的一部分编写的合同的文件。现在让我们来看看该文件中的代码:
var Gaming =
artifacts.require("./Gaming.sol");
module.exports = function(deployer){
deployer.deploy(Gaming);
};
与前面的文件类似,有一个名为Gaming
的变量需要我们创建的Gaming.sol
契约文件,然后它运行 deploy 方法来部署Gaming
契约。
测试合同
为了帮助巩固这种迁移思想,我们将实际迁移一些合同,然后我们将分析网络会发生什么。为此,我们将采用以下步骤:
- 我们的第一步是启动 Ganache 应用程序,它将启动并运行我们的私有区块链,如下面的屏幕截图所示:
正如前面的截图所示,我们已经运行了 Ganache 应用程序。我们来看看余额,目前是 100 以太,还没有进行过区块挖掘,也没有交易。
- 切换到控制台,我们将输入以下命令将合同迁移到网络:
$ truffle migrate
- 一旦迁移成功,我们将返回到 Ganache,在那里我们将看到类似于此屏幕截图的内容:
我们可以看到,地址 0 或帐户 0 已经花费了一些以太网。用来支付合同迁移的费用。查看这些块,我们可以看到挖掘了四个块,以及用于创建和迁移这些合同的四个事务。
- 如果我们切换到代码编辑器,在本例中是 Visual Studio 代码,在
build/contracts
文件夹中,您可以在这里看到作为编译结果而创建的合同元数据文件:
如果我们打开Migrations.json
文件并一直滚动到底部,您可以看到它存储了该合同已部署到的网络,以及已部署合同的地址,如下面的屏幕截图所示:
恭喜你!如果您已到达此处,则您已成功创建并部署了您的第一份合同。您已经看到了我们指定owner
变量和构建constructor
函数时使用的一些语法。在下一节中,我们将讨论 Solidity 编程语言使用的一些语法和风格指南。
理解基本语法
如果不涉及开发 Solidity 代码的一些基本语法指南,我们就无法开始我们的学习之旅。编写代码时一致性的目标不是确定什么是对的什么是错的,而是提供指导方针来帮助确保它总是相同的。这使得代码更加清晰易读。
这是要记住的重要一点——这不是正确的方法或最好的方法,只是一致的方法。这并不意味着样式指南适用于每个实例。如果有疑问,您应该看看其他脚本示例,然后使用您的最佳判断。
代码布局
对于代码布局,我们应该总是在每个缩进层次使用四个空格。空格优先于制表符,但是即使你使用制表符,也要避免在同一个文件中混用制表符和空格。让我们看看下面代码块中的一个例子:
pragma solidity 0.5.0;
contract Gaming {
function determineWinner() public(){
if (guess == true){
return true;
}
else if (guess == false){
return false;
}
}
}
我们可以先看到指定的contract
,然后第一个声明的函数缩进四个空格。if
块本身从那里缩进四个空格。
空白行
建议用两行空行包围顶级声明,用一行空行包围函数级声明。这将从本质上帮助您更快地找到 bug,而不是滚动一个随机排序的文件并希望找到所需的行。让我们用下面的例子来看看我们应该如何使用空行:
contract A {
function foo() public{
//...
}
function bar() public{
//...
}
}
contract B{
function foo() public{
//...
}
}
在前面的代码块中,我们声明了两个契约,在这两个契约之间有两个空行,这样就有一个很大的空白空间可以很容易地将它们区分开来。在我们的函数声明中,每个函数由一个空行分隔。这应该有助于容易地区分代码片段的每个元素。
线路长度
对于行长度,建议最大长度为 79 个字符。79 个字符的建议可以追溯到人们使用最大宽度为 80 个字符的 TTY 终端的时候。实际上,现在看到多达 99 个字符的线宽变得非常普遍。让我们参考下面的例子:
myReallyLongAndDescriptiveFunctionN
ame(
reallyLongVariableOne,
reallyLongVariableTwo,
reallyLongVariableThree,
reallyLongVariableFour
);
正如这里所看到的,第一个参数没有附加到声明函数的那一行,它只由一个缩进来分隔。此外,每行只有一个参数,最后,终止元素单独占一行。
功能布局
你的职能应该按照特定的顺序来安排。这是正确的顺序:
- 构造器
- 后备功能
- 外部功能
- 公共职能
- 内部功能
- 私人功能
我们还没有谈到函数,所以这些函数的含义对你来说可能还有点模糊,但是我们将在下一节详细讨论这个问题,所以请耐心等待。
为了构造函数,我们将在声明的同一行打开括号,并在开始声明的同一缩进层次关闭该行。另外,左括号前面应该有一个空格。让我们来看看下面的代码块:
pragma solidity 0.5.0;
contract Gaming {
function determineWinner() public() {
if (guess == true){
return true;
}
else if (guess == false){
return false;
}
}
}
正如所见,当我们声明Gaming
契约时,我们有一个中间有空格的大括号。类似地,对于我们的determineWinner()
函数,它在同一行上有一个左花括号,然后我们函数的右花括号就在它的下面,这里是函数的第一个字符开始的地方。
在声明变量时,双引号比单引号更适合字符串。我们在操作符的两边都留出一个空格,因为这有助于它们在代码中脱颖而出,这样你就可以更容易地识别它们。以下代码片段说明了这一点:
string str = "foo"; //This
string str='foo'; //Not this
例外情况是,当您有更高优先级的操作符时,您可以省略周围的空格以表示其优先级。
命名规格
命名变量时,应该避免使用单字母的变量名,如 int L、bool O 等。原因是它们看起来非常相似,这只是给你的代码增加了不必要的复杂性。
当您命名契约、库、结构和事件时,您希望使用 CapWords(或 CamelCase ),其中您将变量名中每个单词的第一个字母大写。下面的代码片段显示了这样一个例子:
contracts SimpleGame {
//...
}
contracts MyPlayer {
//...
}
在命名函数、函数参数、变量和修饰符时,应该使用 mixedCase,它与 CapWords 非常相似,只是没有将第一个字母大写,如下面的代码块所示:
contracts SimpleGame {
function simpleGame () {
//...
}
}
在命名常量时,您希望全部用大写字母命名。
您可以使用下划线( _ )符号来避免函数、变量和代码中其他对象之间的命名冲突。下面的代码块显示了一个这样的示例:
function mysteryNumber() returns (uint) {
uint randomnumber = blockhash%10 + 1;
return randomnumber;
}
uint mysteryNumber_ = mysteryNumber();
在前面的代码块中,我有一个名为mysteryNumber()
的函数,在后面的代码中,当使用这个函数时,调用我的变量mysteryNumber
确实有意义,因为它是一个神秘的数字,但是我不能在不引起名称冲突的情况下重用这个名称。所以,当我实际得到这个神秘数字变量时,我在它的末尾加了一个下划线,这样mysteryNumber_
就变成了我从mysteryNumber()
函数中得到的变量。这使得区分这两者变得很容易,但也很清楚我是从哪里得到这个神秘的数字的。
到目前为止,您可能已经看到了实现关于我们如何编写代码的一致指导方针是如何使代码更容易阅读和维护的。虽然它确实不能使代码运行得更快或保证它是正确的,但它确实使编码中的人的因素更令人愉快,这反过来可能使合作和讨论代码的功能而不是格式变得更容易。在下一节中,我们将看看如何为我们的代码编写测试,以及为什么我们想要这样做。
编写您的第一个测试
在上一节中,我们学习了如何高效地编写代码。虽然这使得它更容易阅读,但这并不意味着我们的代码可以正常工作,毕竟,破碎的代码看起来也应该很漂亮,对吗?
让我们花一分钟的时间来谈谈你目前的测试策略。编写完代码后,您是否通过使用它并检查输出来手动测试它,然后在发布最新版本之前重复相同的过程?好吧,让我问你这个问题,你有没有忘记一个步骤,结果你在你的代码中发布了一个 bug,如果你记得做那个步骤的话,这个 bug 可能会被发现。在做出任何改变之前运行测试是一个好主意。我们都花了相当多的时间试图弄清楚我们的代码是如何破坏测试的,结果却发现在我们开始做任何改变之前测试就已经被破坏了。
在这本书里,让我们编写能自动为我们做到这一点的测试,我们将利用在测试上节省下来的所有时间来编写更多优秀的代码。我们的测试可以用 Solidity 编写,也可以用 JavaScript 编写。它们可以自动验证代码是否按照预期执行,并且应该在每次代码更改前后运行。
因此,为了测试Gaming
契约,我们将把我们的测试文件命名为TestGaming.sol
,测试本身进入项目的test
目录。它们也是一个实际的可靠性合同,这使得它们相对容易编写,因为你使用了与编写任何其他可靠性合同相同的技术。
让我们来看看来自应用程序的一个实际的示例测试契约,以及实现它们的一些最佳实践。您可以通过在文本编辑器中打开TestGaming.sol
文件来访问合同。
现在,让我们把这份合同分成几部分。合同开始如下:
pragma solidity 0.5.0;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Gaming.sol"
因此,我们有了pragma solidity
语句和我们的可靠性版本。然后,我们从 Truffle 导入Assert
库和DeployedAddresses
库。与此同时,我们将导入我们正在测试的实际契约,即Gaming.sol
。下一步是定义我们的合同:
contract TestGaming {
uint public initialBalance = 10 ether;
Gaming gaming;
就像任何其他可靠性合同一样,我们定义我们的合同,并给它命名为TestGaming
。我们给它一些初始的ether
来保持我们可以在测试中使用,然后我们创建我们的gaming
变量,它是Gaming
契约的一个实例。
在我们的任何测试运行之前,我们继续进行并获取我们部署的契约,因为在测试期间,每个测试运行都向测试网络部署一个新的契约实例。下面几行代码显示了这是如何实现的:
function beforeAll() public {
gaming = Gaming(DeployedAddresses.Gaming());
}
然后,我们为我们想要测试的每个测试场景创建函数,如下面的代码块所示:
function testPlayerWonGuessHigher() public {
bool expected = true;
bool result = gaming.determineWinner(5, 4, true);
Assert.equal(expected, result, "The player should have won by guessing the mystery number was higher than their number");
}
function testPlayerLostGuessLower() public {
bool expected = false;
bool result = gaming.determineWinner(5, 4, false);
Assert.equal(expected, result, "The player should have lost by guessing the mystery number was lower than their number");
}
testPlayerWonGuessHigher()
函数测试玩家是否猜测数字应该更高,如果数字确实更高,那么他们应该已经赢了。
testPlayerLostGuessLower()
函数测试当数字实际上更高时猜中更低的玩家是否应该输。
我们在测试中设置这些场景,定义我们期望发生的事情,然后使用这些断言来验证实际发生的事情。因此,我们所要做的就是在任何需要的时候或者在部署之前运行这个测试,我们可以自信地说,我们决定谁赢谁输的函数正在准确地工作。
分配
在这本书的每一章中,我会给你一个家庭作业,在你开始第二天或下一章之前完成。今天的家庭作业是关于建立你的开发环境。这不仅会帮助你完成本书的剩余部分,而且我将要展示给你的工具会帮助你完成你承担的每一个区块链项目。你需要做的是:
- 安装 Visual Studio 代码。这是唯一可选的步骤,而且只有当你已经有了一个你熟悉并喜欢的代码编辑器时才是可选的。
- 从nodejs.org安装 Node.js。
- 从 Truffle framework 网站安装 Ganache。
- 使用
npm
模块安装 Truffle 框架本身。
如果你以前安装过 Truffle,请确保你至少安装了 Truffle 4.1 版本,如果没有,请更新。你可以随时使用truffle version
命令检查你的松露版本。
- 最后,使用
truffle unbox
命令和本书的 GitHub repo 的 URL 安装课程代码。 - 完成后,启动终端或命令提示符,根据您的操作系统,切换到您下载代码的目录,并键入
truffle test
。
如果一切顺利,Truffle 将返回一条成功消息,如下图所示:
摘要
这本书的第一天到此结束!在本章中,我们学习了使用区块链和智能合约的所有基础知识。我们学习了如何设置创建区块链应用程序的环境。我们还学习了基本的代码语法、命名约定、风格和结构,以获得最佳效率。我们学习了如何创建智能合同,以及如何测试它以确保它按预期工作。
在下一章,也就是第二天,我们将看看 Solidity 变量和数据类型,看看我们如何使用它们为我们的应用程序构建一些业务逻辑和数据。欢迎来到区块链的世界!