如何建立区块链应用程序 – 2019年以太坊待办事项列表
今天我将向您展示如何构建您的第一个区块链应用程序让我们创建一个由以太坊智能合约提供支持的待办事项列表。首先,我们将使用Solidity编程语言创建智能合约。然后,我们将针对智能合约编写测试,并将其部署到区块链。最后,我们将为todo列表创建一个客户端应用程序。
您可以在上面的90分钟视频中观看我构建完整的区块链应用程序。我还将指导您完成本教程中的分步说明。在我们开始构建dApp之前,让我先解释一下区块链应用程序是如何工作的。
区块链应用程序如何工作?
我为本教程选择了一个todo列表应用程序,因为它是学习任何新编程语言的最常用方法之一。它将教会我们如何从区块链中读取和写入数据,以及执行管理我们的待办事项列表应用程序行为的业务逻辑。它将教你关于区块链如何工作以及如何编写以太坊智能合约的基础知识。
为了理解区块链应用程序的工作原理,让我们首先看一下待办事项列表如何作为Web应用程序工作。要访问待办事项列表,您将使用可通过Internet与Web服务器通信的Web浏览器。服务器包含待办事项列表的所有代码和数据。
以下列出了您在服务器上可以找到的内容:
- HTML,CSS和JavaScript中的客户端文件
- 后端代码负责应用程序的业务逻辑
- 将任务存储在待办事项列表中的数据库
该服务器是一个中心化实体,可以完全控制应用程序的各个方面。任何对服务器具有完全访问权限的人都可以随时更改代码或数据的任何部分。区块链应用程序的工作方式完全不同。todo列表中的所有代码和数据都不在于中心化服务器上。相反,它分布在区块链中。所有代码和数据都在区块链上共享和不可更改。
为了说明这一点,让我们来看看我们基于区块链的待办事项列表是如何工作的。
要访问区块链待办事项列表,我们将使用Web浏览器与客户端应用程序进行通信,该应用程序将使用HTML,CSS和JavaScript编写。客户端应用程序将直接与区块链通信,而不是与后端Web服务器通信。
什么是区块链?
区块链是彼此交谈的计算机或节点的对等网络。它是一个分布式网络,所有参与者都共同负责运行网络。每个网络参与者都在区块链上维护代码和数据的副本。所有这些数据都包含在称为“块”的记录包中,这些记录被“链接在一起”以组成区块链。网络上的所有节点都确保这些数据是安全且不可更改的,这与中心化应用程序不同,后者可以随时更改代码和数据。这就是区块链如此强大的原因由于区块链负责存储数据,因此它基本上是一个数据库。而且因为它是一个彼此交谈的计算机网络,所以它是一个网络。
我还应该强调传统Web应用程序和区块链应用程序之间的另一个基本区别:您不是应用程序本身的用户,而是块状网络的用户。应用程序不管理任何用户数据。这是区块链的责任
什么是智能合约
区块链上的所有代码都包含在智能合约中,这些是在区块链上运行的程序。它们是区块链应用程序的构建块。我们将在本教程中编写一份智能合约,以便为我们的待办事项清单提供支持。它将负责从区块链中获取我们的待办事项列表中的所有任务,添加新任务和完成任务。
智能合约是用一种名为Solidity的编程语言编写的,它看起来很像JavaScript。智能合约中的所有代码都是不可变的或不可更改的。一旦我们将智能合约部署到区块链,我们将无法更改或更新任何代码。这是一项设计功能,可确保代码无信任且安全。我经常将智能合约与网络上的微服务进行比较。它们充当从区块链读取和写入数据的接口,以及执行业务逻辑。它们是公共可访问的,这意味着任何有权访问blockchian的人都可以访问他们的界面。
区块链Todo List如何运作
让我们回顾一下,我们将在本教程中构建应用程序的工作原理。我们将为todo列表创建一个客户端应用程序,它将直接与区块链对话。我们将在本教程中使用以太坊区块链,我们可以通过将客户端应用程序连接到单个以太坊节点来访问它。我们将在Solidity中写一个智能合约,为todo列表提供支持,我们将把它部署到以太坊区块链。我们还将使用以太坊钱包与我们的个人帐户连接到区块链网络,以便与待办事项列表应用程序进行交互。
应用预览
这是我们将在本教程中构建的待办事项列表应用程序的预览。我们将能够列出待办事项列表中的所有任务,创建新任务并完成它们。
安装依赖项
现在让我们安装构建项目所需的所有依赖项。首先,我们将设置一个人区块链来在本地开发应用程序。
Ganache个人区块链
依赖关系是个人区块链,它是一个本地开发区块链,可用于模仿公共区块链的行为。我建议使用Ganache作为您在以太坊开发中的个人区块链。它将允许您部署智能合约,开发应用程序和运行测试。它可以在Windows,Mac和Linux上使用,如桌面应用程序和命令行工具
我将引导您完成本教程中的桌面应用程序设置。您可以在此处找到适用于您的操作系统的最新版本。下载归档软件包后,解压缩安装程序并运行设置步骤。安装完成后,无论何时打开它都会看到此屏幕:
好极了?现在您正在运行个人区块链网络您可以看到有关运行Ganache的服务器的一些详细信息,以及连接到网络的帐户列表。每个帐户已被记入100以太。这节省了大量时间如果您是从头开始创建自己的个人区块链网络,或者在测试网络上开发应用程序,则必须手动创建所有10个帐户并使用以太坊为每个帐户贷记。值得庆幸的是,Ganache已经为我们做了这件事,所以我们不必担心。
Node.js的
现在您正在运行私有区块链,您需要配置环境以开发智能合约。您需要的第一个依赖项是节点包管理器,或NPM,它随Node.js一起提供。您可以通过转到终端并键入以下内容来查看是否已安装节点:
$node -v
如果您还没有安装节点,可以访问Node.js网站下载它。
Truffle框架
现在让我们安装Truffle Framework,它提供了一套工具,用于使用Solidity编程语言开发以太坊智能联系人。
以下是我们通过Truffle Framework获得的所有功能的概述:
- 智能合约管理 – 使用Solidity编程语言编写智能合约,并将其编译为在以太坊Virtal Machine(EVM)上运行的字节码。
- 自动化测试 – 针对您的智能合约编写测试,以确保它们按照您希望的方式运行。这些测试可以用JavaScript或Solidity编写,并且可以针对Truffle配置的任何网络运行,包括公共区块链网络。
- 部署和迁移 – 编写脚本以将智能合约迁移和部署到任何公共以太坊区块链网络。
- 网络管理 – 连接到任何公共以太坊区块链网络,以及您可能用于开发目的的任何个人区块链网络。
- 开发控制台 – 使用Truffle控制台与JavaScript运行时环境中的智能合约进行交互。您可以连接到在网络配置中指定的任何区块链网络来执行此操作。
- Script Runner – 编写可以使用JavaScript针对公共区块链网络运行的自定义脚本。您可以在此文件中编写任意代码并在项目中运行它。
- 客户端开发 – 将您的Truffle项目配置为托管与部署到区块链的智能合约对话的客户端应用程序。
您可以在命令行中使用NPM安装Truffle,如下所示。注意:使用下面指定的Truffle的确切版本非常重要,以便按照本教程进行操作。
$npm install -g truffle@5.0.2
Metamask以太坊钱包
现在是时候将您的Web浏览器变成区块链浏览器了。大多数主要的Web浏览器目前都没有连接到区块链网络,所以我们必须安装允许他们这样做的浏览器扩展。
我将使用Google Chrome的Metamask扩展程序。要安装Metamask,请访问此链接或在Google Chrome网上应用店中搜索Metamask Chrome插件。安装完成后,请确保在扩展列表中选中它。安装后,您会在Chrome浏览器的右上角看到狐狸图标。如果卡住了,请参考视频演示
Metamask还允许我们在连接区块链时管理我们的个人账户,以及管理我们需要为交易支付的以太基金。
项目设置
本教程这部分的随附视频片段从9:26开始。
现在让我们创建项目我将首先创建一个项目目录,然后像这样输入:
$mkdir eth-todo-list
$cd eth-todo-list
现在我们初始化一个新的Truffle项目来开发我们的项目,如下所示:
$ truffle init
大您的终端输出应显示项目已成功创建。您可以打开文本编辑器,并在运行该命令后看到创建了一些新文件和目录。现在让我们创建一个package.json
文件来安装项目所需的一些开发依赖项。您可以从命令行执行此操作,如下所示:
$touch package.json
只需将下面的代码复制并粘贴到您的package.json
文件中,即可为项目引导所有依赖项:
{
"name": "eth-todo-list",
"version": "1.0.0",
"description": "区块链Todo List Powered By Ethereum",
"main": "truffle-config.js",
"directories": {
"test": "test"
},
"scripts": {
"dev": "lite-server",
"test": "echo \"Error: no test specified\" && sexit 1"
},
"author": "gregory@dappuniversity.com",
"license": "ISC",
"devDependencies": {
"bootstrap": "4.1.3",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chai-bignumber": "^2.0.2",
"lite-server": "^2.3.0",
"nodemon": "^1.17.3",
"truffle": "5.0.2",
"truffle-contract": "3.0.6"
}
}
现在,您可以从命令行安装依赖项,如下所示:
$npm install
既然已经安装了依赖项,那么让我们检查一下我们刚刚创建的项目目录结构:
- 合约目录:这是所有智能联系人所在的地方。我们已经有一个迁移合约来处理我们到区块链的迁移。
- 迁移目录:这是所有迁移文件所在的位置。这些迁移类似于需要迁移来更改数据库状态的其他Web开发框架。每当我们将智能合约部署到区块链时,我们都会更新区块链的状态,因此需要迁移。
- node_modules目录:这是我们刚安装的所有Node依赖项的主页。
- 测试目录:这是我们为智能合约编写测试的地方。
- truffle-config.js文件:这是我们Truffle项目的主要配置文件,我们将处理网络配置等事务。
现在让我们开始开发管理我们的待办事项列表的智能合约。我们可以通过在contracts
目录中创建一个新文件来完成此操作,如下所示:
$touch ./contracts/TodoList.sol
在这里,让我们开发我们的todo list智能合约。首先,我们首先指定这样的版本:
pragma solidity ^0.5.0;
现在我们可以像这样声明智能合约:
pragma solidity ^0.5.0;
contract TodoList {
// Code goes here...
}
我们创建了一个智能合约,TodoList
后面跟着花括号。我们将在其中添加智能合约的所有代码。我们要做的就是跟踪待办事项列表中的任务数量。这将允许我们编写一些简单的代码,以帮助我们确保项目正确设置,并且我们的代码正在处理区块链。我们只需创建一个状态变量taskCount
来跟踪这样的任务数量:
pragma solidity ^0.5.0;
contract TodoList {
uint taskCount;
}
这taskCount
是一种称为“状态变量”的特殊变量。我们存储在此状态变量中的任何数据都将写入区块链中的存储。它改变了智能合约的状态,并且在整个智能合约中具有范围,而不是只在函数内部具有范围的局部变量。我们可以0
为此状态变量设置默认值,如下所示:
pragma solidity ^0.5.0;
contract TodoList {
uint taskCount = 0;
}
现在,我们可以创建一种在合约之外访问此状态变量值的方法。我们可以使用public
Solidity中调用的特殊修饰符关键字来完成此操作。当我们这样做时,Solidity将神奇地创建一个taskCount()
函数,以便我们可以在智能合约之外访问此变量的值。当我们在控制台,客户端应用程序和测试文件内部与智能合约进行交互时,这将非常有用。
现在让我们编译智能合约并确保没有错误:
$truffle compile
好极了?你刚刚写了第一份以太坊智能合约。您应该注意到,只要您在以下路径中编译智能合约,就会生成一个新文件:`./build/contracts/TodoList.json`
。该文件是智能合约ABI文件,代表“抽象二进制接口”。这个文件有很多责任,但我将在这里强调两个:
- 它包含可以在以太坊虚拟机(EVM)上运行的Solidity智能合约代码的编译字节码版本,即以太坊节点。
- 它包含可以向客户端JavaScript应用程序等外部客户端公开的智能合约函数的JSON表示。
我们的下一个目标是访问Truffle控制台内的智能合约。但是,我们无法运行Truffle控制台,因为我们的应用程序尚未连接到我们在依赖项部分中设置的Ganache个人区块链网络。要谈谈Truffle控制台内个人区块链网络上的智能合约,我们必须做一些事情:
- 更新我们项目的配置文件以指定我们要连接的个人区块链网络(Ganache)。
- 创建一个迁移脚本,告诉Truffle如何将智能合约部署到个人区块链网络。
- 运行新创建的迁移脚本,将智能合约部署到个人区块链网络。
首先,我们将更新项目配置文件,以指定我们想要在第一部分中设置的个人区块链网络。找到该文件truffle-config.js
并粘贴以下代码:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*" // Match any network id
}
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
}
注意:这些应与Ganache个人区块链网络提供的默认设置相匹配。如果您更改了Ganache设置页面中的任何设置,例如端口,则应在此处反映这些设置。
接下来,我们将在迁移目录中创建迁移脚本,以将智能合约部署到个人区块链网络。从项目根目录,从命令行创建一个新文件,如下所示:
$touch migrations/2_deploy_contracts.js
让我解释一下这个文件的作用。每当我们创建新的智能合约时,我们都会更新区块链的状态。记住,我说区块链从根本上说是一个数据库。因此,每当我们永久地改变它时,我们必须将它从一个状态迁移到另一个状态。这与您可能在其他Web应用程序开发框架中执行的数据库迁移非常相似。
请注意,我们使用数字对迁移目录中的所有文件进行编号,以便Truffle知道执行它们的顺序。在这个新创建的迁移文件中,您可以使用此代码来部署智能合约:
var TodoList = artifacts.require("./TodoList.sol");
module.exports = function(deployer) {
deployer.deploy(TodoList);
};
首先,我们需要我们创建的契约,并将其分配给名为“TodoList”的变量。接下来,我们将其添加到已部署合约的清单中,以确保在我们运行迁移时部署它。现在让我们从命令行运行这个迁移脚本,如下所示:
$truffle migrate
现在我们已成功将智能合约迁移到个人区块链网络,让我们打开控制台与智能合约进行交互。您可以从命令行打开Truffle控制台,如下所示:
$truffle console
现在我们进入控制台,让我们得到我们部署的智能合约的实例,看看我们是否可以taskCount
从合约中读取。从控制台,运行以下代码:
todoList = await TodoList.deployed()
这TodoList
是我们在迁移文件中创建的变量的名称。我们使用该deployed()
函数检索了部署的合约实例,并将其分配给了todoList
。另外,请注意await
关键字的使用。我们必须以异步方式与区块链交互。因此,JavaScript是与区块链智能合约进行客户端交互的绝佳选择。有几种策略可以在JavaScript中处理异步操作。最受欢迎的方式之一是async/await
我在这里选择的模式。Truffle最近在Truffle控制台内发布了对此的支持。您可以在此处阅读有关async / await模式的更多信息。
首先,我们可以获得部署到区块链的智能合约的地址,如下所示:
todoList.address
// => '0xABC123...'
现在我们可以taskCount
从存储中读取这样的值:
takCount = await app.taskCount()
// => 0
好极了?您已成功完成此tutorail的第一部分。您已完成以下所有操作:
- 设置机器进行区块链开发
- 创建了一个新的Truffle项目
- 创建了你的第一份智能合约
- 与您在区块链上新创建的智能合约互动
如果您遇到任何步骤,请随意从github克隆此部分的项目代码。您也可以从此处开始参考本节的视频教程。
列出任务
本教程这一部分的随附视频片段从22:47开始。
现在让我们开始列出待办事项列表中的任务。以下是我们将在本节中完成的所有步骤:
- 编写代码以列出智能合约中的任务
- 列出Truffle控制台内智能合约的任务
- 列出客户端应用程序中的任务
- 为列出任务编写测试
为了列出智能合约中的任务,我们需要一种方法来模拟任务的可靠性。Solidity允许您使用结构定义自己的数据类型。我们可以使用这个强大的功能对任意数据建模。我们将使用结构为我们的待办事项列表建模任务,如下所示:
pragma solidity ^0.5.0;
contract TodoList {
uint public taskCount = 0;
struct Task {
uint id;
string content;
bool completed;
}
}
首先,我们使用struct
关键字后跟新结构的名称对任务建模Task
。注意,这不代表a的实例Task
,而只是Task
结构的定义。花括号中包含的行定义了Task
struct 的属性:
uint id
– 这是结构的唯一标识符。它id
就像传统的数据库记录一样。注意,我们将此标识符的数据类型声明为auint
,表示“无符号整数”。这只是意味着它是一个非负整数。它前面没有“标志”,即一个-
或+
标志,暗示它总是积极的。string content
– 这是字符串中包含的待办事项列表中的任务文本。bool completed
– 这是待办事项列表的复选框状态,即true/false
。如果是true
,则任务将“完成”或从待办事项列表中检出。
现在我们已经建模了一个任务,我们需要一个地方将所有任务放在待办事项列表中我们希望将它们存储在区块链中,以便智能合约的状态将是持久的。我们可以使用状态变量访问区块链的存储,就像我们一样taskCount
。我们将创建一个tasks
状态变量。它将使用一种特殊的Solidity数据结构,称为映射,如下所示:
pragma solidity ^0.5.0;
contract TodoList {
uint public taskCount = 0;
struct Task {
uint id;
string content;
bool completed;
}
mapping(uint => Task) public tasks;
}
Solidity中的映射很像关联数组或其他编程语言中的哈希。它创建了存储在区块链中的键值对。我们将使用唯一ID作为密钥。价值将是它自己的任务。这将允许我们查找任何任务id
现在让我们创建一个创建任务的功能。这将允许我们默认将新任务添加到待办事项列表,以便我们可以在控制台中列出它们。
pragma solidity ^0.5.0;
contract TodoList {
uint public taskCount = 0;
struct Task {
uint id;
string content;
bool completed;
}
mapping(uint => Task) public tasks;
function createTask(string memory _content) public {
taskCount ++;
tasks[taskCount] = Task(taskCount, _content, false);
}
}
我将解释这个功能:
- 首先,我们使用
function
关键字创建函数,并为其命名createTask()
- 我们允许函数接受一个被调用的参数
_content
,该参数将是任务的文本。我们指定此参数将是string
数据类型,并且它将保持不变memory
- 我们将函数可见性设置为
public
可以在智能合约之外调用,例如在控制台中,或者从客户端调用,例如 - 在函数内部,我们
id
为新任务创建一个。我们只需取现有值taskCount
并将其递增1。 - 现在我们通过调用
Task(taskCount, _content, false);
和传入新任务的值来创建一个新的任务结构。 - 接下来,我们存储在blockchain新任务通过将其加入到
tasks
这样的映射:task[taskCount] = ...
。
现在,只要将智能合约部署到区块链,我们就希望将一个任务添加到待办事项列表中,这样它就会有一个我们可以在控制台中检查的默认任务。我们可以通过调用createTask()
智能合约的构造函数内部的函数来做到这一点:
contract TodoList {
// ....
constructor() public {
createTask("Check out dappuniversity.com");
}
// ....
}
我们使用constructor
关键字创建构造函数,如上所示。无论何时初始化合约(即部署到区块链),此函数都只运行一次。在此函数内部,我们创建了一个新的默认任务,其字符串内容为“Check out dappuniversity.com”。
现在让我们将这个智能合约部署到区块链。为此,我们必须部署代码的新副本。请记住,智能合约代码是不可变的它无法改变。因此,我们必须在进行代码更改时创建新的智能合约。幸运的是,Truffle提供了一个快捷方式来帮助解决这个问题。我们可以重新运行这样的迁移:
$ truffle migrate --reset
中提琴现在我们在区块链上有一份新的智能合约副本。现在让我们在控制台中列出任务。
$ truffle console
在控制台内部,让我们获得新智能合约的部署副本。
todoList = await TodoList.deployed()
现在我们可以通过调用tasks()
函数从todo列表中获取任务。这将允许我们从tasks
映射中访问值id
。id
当我们调用这个函数时,我们将简单地传入列表中的第一个任务:
task = await todoList.tasks(1)
好极了?如何在控制台中检查此任务的值。?
现在我们已经将这个智能合约迁移到区块链,让我们创建客户端代码以与todo list智能合约进行交互。您需要为项目创建以下文件:
bs-config.json
src/index.html
src/app.js
我们将逐个填写所有这些文件的代码。我们使用lite-server为客户端提供所有项目文件。我们需要告诉lite-server所有这些文件所在的位置。我们可以通过更新bs-config.json
文件中lite-server的browsersync配置来实现。将此配置粘贴到项目文件中:
{
"server": {
"baseDir": [
"./src",
"./build/contracts"
],
"routes": {
"/vendor": "./node_modules"
}
}
}
此配置告诉lite-server将src
和build/contracts
目录中的所有文件公开给Web服务器的根目录。它还为node_modules
目录中显示在vendor
路径中的任何文件添加别名。这将允许我们将任何项目依赖项(如bootstrap)引入客户端并使用该vendor
路由,我们将立即看到。
现在让我们为我们的待办事项列表填写HTML代码。本教程主要关注区块链技术,因此我不想在HTML和CSS部分上花费太多时间。我只需在此处粘贴HTML代码:
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dapp University | Todo Listtitle>
<link href="vendor/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
main {
margin-top: 60px;
}
#content {
display: none;
}
form {
width: 350px;
margin-bottom: 10px;
}
ul {
margin-bottom: 0px;
}
#completedTaskList .content {
color: grey;
text-decoration: line-through;
}
style>
head>
<body>
<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://www.dappuniversity.com/free-download" target="_blank">Dapp University | Todo Lista>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap d-none d-sm-none d-sm-block">
<small><a class="nav-link" href="#"><span id="account">span>a>small>
li>
ul>
nav>
<div class="container-fluid">
<div class="row">
<main role="main" class="col-lg-12 d-flex justify-content-center">
<div id="loader" class="text-center">
<p class="text-center">Loading...p>
div>
<div id="content">
<ul id="taskList" class="list-unstyled">
<div class="taskTemplate" class="checkbox" style="display: none">
<label>
<input type="checkbox" />
<span class="content">Task content goes here...span>
label>
div>
ul>
<ul id="completedTaskList" class="list-unstyled">
ul>
div>
main>
div>
div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js">script>
<script src="vendor/bootstrap/dist/js/bootstrap.min.js">script>
<script src="vendor/truffle-contract/dist/truffle-contract.js">script>
<script src="app.js">script>
body>
html>
这个文件支持我们项目所需的所有HTML。我已经注释掉了我们将在后面部分中启用的表单代码。该文件引入了项目的所有依赖项,如引导模板框架,它将允许我们创建漂亮的UI元素,而无需编写太多的CSS。它还使用Truffle Conract库,它允许我们使用JavaScript与todo list智能合约进行交互。
现在让我们填写本节的JavaScript代码。我们将代码添加到新创建的app.js
文件中,如下所示:
App = {
loading: false,
contracts: {},
load: async () => {
await App.loadWeb3()
await App.loadAccount()
await App.loadContract()
await App.render()
},
// https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
loadWeb3: async () => {
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider
web3 = new Web3(web3.currentProvider)
} else {
window.alert("Please connect to Metamask.")
}
// Modern dapp browsers...
if (window.ethereum) {
window.web3 = new Web3(ethereum)
try {
// Request account access if needed
await ethereum.enable()
// Acccounts now exposed
web3.eth.sendTransaction({/* ... */})
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = web3.currentProvider
window.web3 = new Web3(web3.currentProvider)
// Acccounts always exposed
web3.eth.sendTransaction({/* ... */})
}
// Non-dapp browsers...
else {
console.log('Non-Ethereum browser detected. You should consider trying MetaMask!')
}
},
loadAccount: async () => {
// Set the current blockchain account
App.account = web3.eth.accounts[0]
},
loadContract: async () => {
// Create a JavaScript version of the smart contract
const todoList = await $.getJSON('TodoList.json')
App.contracts.TodoList = TruffleContract(todoList)
App.contracts.TodoList.setProvider(App.web3Provider)
// Hydrate the smart contract with values from the blockchain
App.todoList = await App.contracts.TodoList.deployed()
},
render: async () => {
// Prevent double render
if (App.loading) {
return
}
// Update app loading state
App.setLoading(true)
// Render Account
$('#account').html(App.account)
// Render Tasks
await App.renderTasks()
// Update loading state
App.setLoading(false)
},
renderTasks: async () => {
// Load the total task count from the blockchain
const taskCount = await App.todoList.taskCount()
const $taskTemplate = $('.taskTemplate')
// Render out each task with a new task template
for (var i = 1; i <= taskCount; i++) {
// Fetch the task data from the blockchain
const task = await App.todoList.tasks(i)
const taskId = task[0].toNumber()
const taskContent = task[1]
const taskCompleted = task[2]
// Create the html for the task
const $newTaskTemplate = $taskTemplate.clone()
$newTaskTemplate.find('.content').html(taskContent)
$newTaskTemplate.find('input')
.prop('name', taskId)
.prop('checked', taskCompleted)
// .on('click', App.toggleCompleted)
// Put the task in the correct list
if (taskCompleted) {
$('#completedTaskList').append($newTaskTemplate)
} else {
$('#taskList').append($newTaskTemplate)
}
// Show the task
$newTaskTemplate.show()
}
},
setLoading: (boolean) => {
App.loading = boolean
const loader = $('#loader')
const content = $('#content')
if (boolean) {
loader.show()
content.hide()
} else {
loader.hide()
content.show()
}
}
}
$(() => {
$(window).load(() => {
App.load()
})
})
让我解释一下这段代码。我们创建了一个新App
对象,其中包含运行JavaScript应用程序所需的所有函数。我将在这里解释重要的功能。有关完整说明,请观看我在40:25解释视频中的JavaScript代码。另请注意,我已经注释掉了几行代码,我们将在后面的部分中启用它们。
loadWeb3()
web3.js是一个JavaScript库,允许我们的客户端应用程序与区块链交谈。我们在这里配置web3。这是Metamask指定的默认Web3配置。如果您不完全了解这里发生的事情,请不要担心。这是Metamask建议的复制和粘贴实现。loadContract()
这是我们从区块链加载智能合约数据的地方。我们使用Truffle Contract库创建智能结构的JavaScript表示。然后我们用web3加载智能合约数据。这将允许我们列出待办事项列表中的任务。renderTasks()
这是我们实际列出待办事项列表中的任务的地方。请注意,我们创建了一个for循环来单独访问每个任务。那是因为我们无法从智能合约中获取整个任务映射。我们必须首先确定taskCount
并逐个获取每个任务。
现在让我们启动Web服务器并确保项目将在浏览器中加载。
$ npm run dev
好极了?您已成功加载客户端应用程序。?请注意,您的应用程序显示“正在加载…”。那是因为我们还没有登录到区块链为了连接到区块链,我们需要将其中一个帐户从Ganache导入Metamask。您可以在43:55观看我在视频中设置Metamask。
与Metamask连接后,您应该会看到所有合约和帐户数据都已加载。
?轰有你的待办事项列表?
测试
现在让我们编写一个基本测试,以确保todo list smart conract正常工作。首先,让我解释为什么在开发智能合约时测试非常重要。我们希望确保合约没有错误,原因如下:
- 以太坊区块链上的所有代码都是不可变的; 它无法改变。如果合约包含任何错误,我们必须禁用它并部署新副本。此新副本与旧合约的状态不同,它将具有不同的地址。
- 部署合约会产生气体,因为它会创建一个事务并将数据写入区块链。这会花费以太,我们希望尽量减少我们必须支付的以太坊数量。
- 如果我们写入区块链的任何合约函数都包含错误,那么调用此函数的帐户可能会浪费以太,并且它可能不会按照预期的方式运行。
让我们创建一个这样的测试文件:
$ test/TodoList.test.js
我们将使用Mocha测试框架和Chai断言库在此文件中的Javascript中编写所有测试。这些与Truffle框架捆绑在一起。我们将在Javascript中编写所有这些测试,以模拟与智能合约的客户端交互,就像我们在控制台中所做的那样。以下是测试的所有代码:
const TodoList = artifacts.require('./TodoList.sol')
contract('TodoList', (accounts) => {
before(async () => {
this.todoList = await TodoList.deployed()
})
it('deploys successfully', async () => {
const address = await this.todoList.address
assert.notEqual(address, 0x0)
assert.notEqual(address, '')
assert.notEqual(address, null)
assert.notEqual(address, undefined)
})
it('lists tasks', async () => {
const taskCount = await this.todoList.taskCount()
const task = await this.todoList.tasks(taskCount)
assert.equal(task.id.toNumber(), taskCount.toNumber())
assert.equal(task.content, 'Check out dappuniversity.com')
assert.equal(task.completed, false)
assert.equal(taskCount.toNumber(), 1)
})
})
让我解释一下这段代码。首先,我们要求合约并将其分配给变量,就像我们在迁移文件中所做的那样。接下来,我们调用“契约”函数,并在回调函数中编写所有测试。此回调函数提供了一个“帐户”变量,表示我们的区块链上的所有帐户,由Ganache提供。
第一个测试通过检查其地址来检查合约是否已正确部署到区块链。
下一个测试通过检查我们在初始化函数中创建的默认任务来检查智能合约是否正确地列出了任务。
现在让我们从命令行运行测试,如下所示:
$truffle test
是的,他们通过了?如果您遇到困难,我可以跟随我,因为我在视频中编写这些测试以获得进一步的解释。
创建任务
本部分教程的随附视频片段于1:05:07开始。
我们已经创建了一个用于创建任务的函数,但它还没有完成。那是因为我想在创建新任务的任何时候触发事件。Solidity允许我们触发外部消费者可以订阅的任意事件。它将允许我们在客户端应用程序内部监听这些事件等…让我们创建一个TaskCreated()
事件并在createTask()
函数中创建新任务时触发它,如下所示:
pragma solidity ^0.5.0;
contract TodoList {
// ...
event TaskCreated(
uint id,
string content,
bool completed
);
// ...
function createTask(string memory _content) public {
taskCount ++;
tasks[taskCount] = Task(taskCount, _content, false);
emit TaskCreated(taskCount, _content, false);
}
}
现在让我们创建一个测试,以确保在创建新任务时触发此事件。我们将在创建新任务时检查交易收据。这将包含将包含事件数据的所有日志信息。我们可以像这样在我们的测试中检查这些数据,以确保事件被正确触发:
it('creates tasks', async () => {
const result = await this.todoList.createTask('A new task')
const taskCount = await this.todoList.taskCount()
assert.equal(taskCount, 2)
const event = result.logs[0].args
assert.equal(event.id.toNumber(), 2)
assert.equal(event.content, 'A new task')
assert.equal(event.completed, false)
})
现在让我们进行测试:
$ truffle test
是的,他们通过了?现在让我们将代码更改后的智能合约的新副本部署到区块链:
$ truffle migrate --reset
现在让我们更新客户端代码。我们首先取消注释index.html
文件中的表单代码:
<form onSubmit="App.createTask(); return false;">
<input id="newTask" type="text" class="form-control" placeholder="Add task..." required>
<input type="submit" hidden="">
form>
现在我们将createTask()
在app.js
文件中添加一个函数,如下所示:
createTask: async () => {
App.setLoading(true)
const content = $('#newTask').val()
await App.todoList.createTask(content)
window.location.reload()
},
现在您应该能够从客户端应用程序添加新任务请注意,表单上没有“提交”按钮。我把它留下来简化用户界面。您必须按键盘上的“enter”键才能提交任务。完成后,您会看到弹出Metamask确认。您必须签署此事务才能创建任务。
完成任务
本部分教程的附带视频片段从1:16:40开始。
现在我们在本教程中要做的最后一件事是“检查”待办事项列表中的任务。一旦我们这样做,他们就会出现在“已完成”的列表中,尽管如此。首先,我们将更新智能合约。我们将添加一个TaskComplted()
事件,并在一个新toggleCompleted()
函数中触发它,如下所示:
pragma solidity ^0.5.0;
contract TodoList {
// ...
event TaskCompleted(
uint id,
bool completed
);
// ...
function toggleCompleted(uint _id) public {
Task memory _task = tasks[_id];
_task.completed = !_task.completed;
tasks[_id] = _task;
emit TaskCompleted(_id, _task.completed);
}
}
现在我们将编写一个这样的测试:
it('toggles task completion', async () => {
const result = await this.todoList.toggleCompleted(1)
const task = await this.todoList.tasks(1)
assert.equal(task.completed, true)
const event = result.logs[0].args
assert.equal(event.id.toNumber(), 1)
assert.equal(event.completed, true)
})
现在让我们进行测试:
$ truffle test
是的,它过去了?现在让我们将代码更改后的智能合约的新副本部署到区块链:
$ truffle migrate --reset
现在让我们更新客户端代码。首先,我们将取消注释renderTasks()
函数内的事件监听器:
$newTaskTemplate.find('input')
.prop('name', taskId)
.prop('checked', taskCompleted)
.on('click', App.toggleCompleted)
现在我们将toggleCompleted()
在app.js
文件中添加一个函数,如下所示:
toggleCompleted: async (e) => {
App.setLoading(true)
const taskId = e.target.name
await App.todoList.toggleCompleted(taskId)
window.location.reload()
},
现在,在客户端应用程序中找到一个任务,然后单击复选框。签署此交易后,它将从待办事项列表中检查任务
恭喜?您已成功构建了由以太坊智能合约提供支持的完整堆栈区块链应用程序您可以将完整的源代码下载到本教程这里,并观看完整的视频在这里。
原文:https://www.dappuniversity.com/articles/blockchain-app-tutorial