简单投票DApp

2022.2.24

主要内容

ganache,简单投票DApp

教程搬运

ganache

需求描述

接下来我们要开始真正做一个 DApp,尽管它这是很简单的一个投票应用,但会包含完整的工作流程和交互页面。构建这个应用的主要步骤如下:

我们与区块链进行通信的方式是通过 RPC(Remote Procedure Call)。 web3js 是一个 JavaScript 库,它抽象出了所有的 RPC 调用,以便于你可以通过 JavaScript 与区块链进行交互。另一个好处是,web3js 能够让你使用你最喜欢的 JavaScript 框架构建非常棒的 web 应用。

环境准备

Solidity合约

我们会写一个叫做 Voting 的合约,这个合约有以下内容:

当你把合约部署到区块链的时候,就会调用构造函数,并只调用一次。与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新合约并再次部署,旧的合约仍然会在区块链上存在,并且数据仍在。新的部署将会创建合约的一个新的实例

Line 1. 我们必须指定代码将会哪个版本的编译器进行编译

Line 3. mapping 相当于一个关联数组或者是字典,是一个键值对。mapping votesReceived 的键是候选者的名字,类型为 bytes32。mapping 的值是一个 未赋值的整型,存储的是投票数。

Line 4. 在很多编程语言中(例如 java、python 中的字典<HashTable 继承 自字典>),仅仅通过 votesReceived.keys 就可以获取所有的候选者姓名。但 是,但是在 solidity 中没有这样的方法,所以我们必须单独管理一个候选者数组 candidateList。

Line 14. 注意到 votesReceived[key] 有一个默认值 0,所以你不需要将其 初始化为 0,直接加 1 即可。

你也会注意到每个函数有个可见性说明符(visibility specifier)(比如本例 中的 public)。这意味着,函数可以从合约外调用。如果你不想要其他任何人 调用这个函数,你可以把它设置为私有(private)函数。如果你不指定可见性,

编译器会抛出一个警告。最近 solidity 编译器进行了一些改进,如果用户忘记了 对私有函数进行标记导致了外部可以调用私有函数,编译器会捕获这个问题。

你也会在一些函数上看到一个修饰符 view。它通常用来告诉编译器函数是 只读的(也就是说,调用该函数,区块链状态并不会更新)。

编译代码

In the node console

首先,在终端中运行 node 进入 node 控制台,初始化 web3 对象,并向区块链查询获取所有的账户。

确保与此同时 ganache 已经在另一个窗口中运行为了编译合约,先从 Voting.sol 中加载代码并绑定到一个 string 类型的变 量,然后像右边这样对合约进行编译。

当你成功地编译好合约,打印 compiledCode 对象(直接在 node 控制台 输入 compiledCode 就可以看到内容),你会注意到有两个重要的字段,它们

很重要,你必须要理解:

  1. compiledCode.contracts[':Voting'].bytecode: 这就是 Voting.sol 编译好后的字节码。也是要部署到区块链上的代码。
  2. compiledCode.contracts[':Voting'].interface: 这是一个合约的接口或 者说模板(叫做 abi 定义),它告诉了用户在这个合约里有哪些方法。在未来无论 何时你想要跟任意一个合约进行交互,你都会需要这个 abi 定义。你可以在这里看到 ABI 的更多内容。

在以后的项目中,我们将会使用 truffle 框架来管理编译和与区块链的交互。但是,在使用任何框架之前,深入了解它的工作方式还是大有裨益的,因为框架会将这些内容抽象出去。

部署合约

让我们继续课程,现在将合约部署到区块链上。为此,你必须先通过传入 abi 定义来创建一个合约对象 VotingContract。然后用这个对象在链上部署并初始化合约。

Execute this in your node console:

VotingContract.new 将合约部署到区块链。第一个参数是一个候选者数组,候选者们会竞争选举,这很容易理解。让我 们来看一下第二个参数里面都是些什么:

  1. data: 这是我们编译后部署到区块链上的字节码。

  2. from: 区块链必须跟踪是谁部署了这个合约。在这种情况下,我们仅仅是

    从调用 web3.eth.accounts 返回的第一个账户,作为部署这个合约的账 户。记住,web3.eth.accounts 返回一个 ganache 所创建 10 个测试账 号的数组。在交易之前,你必须拥有这个账户,并对其解锁。创建一个账 户时,你会被要求输入一个密码,这就是你用来证明你对账户所有权的东 西。在下一节,我们将会进行详细介绍。为了方便起见,ganache 默认 会解锁 10 个账户。

  3. gas: 与区块链进行交互需要花费金钱。这笔钱用来付给矿工,因为他们帮你把代码包含了在区块链里面。你必须指定你愿意花费多少钱让你的代 码包含在区块链中,也就是设定 “gas” 的值。你的 “from” 账户里面的 ETH 余额将会被用来购买 gas。gas 的价格由网络设定。

我们已经部署了合约,并有了一个合约实例(变量 contractInstance),我们可以用这个实例与合约进行交互。

在区块链上有上千个合约。那么,如何识别你的合约已经上链了呢?

答案是找到已部署合约的地址:deployedContract.address. 当你需要跟合约进行交互时,就需要这个部署地址和我们之前谈到的 abi 定义。

控制台交互

In your node console:

为候选者投票并查看投票数

继续课程,在你的 node 控制台里调用 voteForCandidate 和 totalVotesFor 方法并查看结果。

每为一位候选者投一次票,你就会得到一个交易 id,比如: ‘0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53’。这个交易 id 就是交易发生的凭据,你可以在将来的任何时候引用这 笔交易。这笔交易是不可改变的。

对于以太坊这样的区块链,不可改变是其主要特性之一。在接下来的章节, 我们将会利用这一特性构建应用。

网页交互

至此,大部分的工作都已完成,我们还需要做的事情就是创建一个简单的 html,里面有候选者姓名并调用投票命令(我们已经在 nodejs 控制台里试过)。 你可以在右侧找到 html 代码和 js 代码。将它们放到 chapter1 目录,并在浏 览器中打开 index.html。

index.html

Tips:

  1. <head>中用 link 形式引入 bootstrap 的 css 类型库,以下 container、table-responsive 等 class 均来自 bootstrap
  2. <th>表头单元格,<td>表单元格,候选人名字后的单元格为得票数,用 id 区分以方便写入,之后 js 中写死了对应关系
  3. <input>一个输入框,定义 id 方便在 js 中取值
  4. <a>超链接形式的按键 btn,href=“#”为跳转至本页,即不跳转;onclick指向js 中方法 为了简化项目,我们已经硬编码了候选者姓名。如果你喜欢的话,可以调整代码使其动态选择候选者。

index.js

在第 4 行,用你自己的合约地址替换代码中的合约地址。合约地址是之前 的 deployedContract.address

如果一切顺利的话,你应该能够在文本框中输入候选者姓名,然后投票数应该加 1 。

注意:由于网络原因,web3.js 可能无法获取,可自行下载到本地导入。

如果你可以看到页面,为候选者投票,然后看到投票数增加,那就已经成功 创建了第一个合约,恭喜!所有投票都会保存到区块链上,并且是不可改变的。 任何人都可以独立验证每个候选者获得了多少投票。当然,我们所有的事情都是 在一个模拟的区块链上(ganache)完成,在接下来的课程中,我们会将这个合约部署到真正的公链上。在 Part 2,我们会把合约部署到叫做 Ropsten testnet 的公链,同时也会学习如何使用 truffle 框架构建合约,管理 dapp。

总结一下,下面是你到目前为止已经完成的事情:

  1. 通过安装 node, npm 和 ganache,你已经配置好了开发环境。
  2. 你编码了一个简单的投票合约,编译并部署到区块链上。
  3. 你通过 nodejs 控制台与网页与合约进行了交互。

自己的Dapp

下面展示的是三个主要文件。详细代码已上传

github:https://github.com/CharlesShan-hub/SimpleVoteDapp

视频介绍:https://www.bilibili.com/video/bv1LS4y1k7jv

如果采用geth命令台主要要允许跨域访问

 

Voting.sol

main.js

index.html