首页 > 分享 > 开发和部署以太坊DApp——投票系统

开发和部署以太坊DApp——投票系统

开发和部署以太坊DApp——投票系统

良__IP属地: 广东

0.3912018.05.30 22:58:02字数 1,283阅读 4,908

1、项目简介

本篇文章将介绍如何基于以太坊平台开发简单的投票系统DApp,将学习到以下内容:

搭建开发环境(使用到Truffle框架) 编写和部署智能合约到区块链 Web3和智能合约的交互 MetaMask的使用

该项目比较简单,初始化一组候选人,任何人可以投票给候选人,并显示每个候选人得到的总票数。麻雀虽小,五脏俱全。通过编写此项目,可以学习到编译、部署和交互的全过程。

注意:以下操作均在MacOS上面操作

2、搭建开发环境

进行该项目前,需要安装Node.js和Truffle框架。

安装Node.js

官网:https://nodejs.org/en/

安装很简单,只需要下载安装包直接安装即可,可以先通过终端检查安装情况再安装,没有对应结果显示再安装:

wenzildeiMac:~ wenzil$ npm -v 3.10.10 wenzildeiMac:~ wenzil$ node -v v6.9.5

安装Truffle:

Truffle是目前比较流行的Solidity智能合约开发框架,功能十分强大,可以帮助开发者快速地开发一个DApp。

安装Ganache CLI:

Ganache CLI是以太坊节点仿真器软件ganache的命令行版本,可以方便开发者快速进行以太坊DApp的开发与测试,Ganache CLI已经取代了TestRPC。

npm install -g ganache-cli 3、通过Truffle创建项目

建一个项目的目录,然后进入到该目录,如下:

wenzildeiMac:study wenzil$ pwd /Users/wenzil/Desktop/study wenzildeiMac:study wenzil$ mkdir VotingSystem wenzildeiMac:study wenzil$ cd VotingSystem/

然后通过Truffle创建项目,如下:

wenzildeiMac:VotingSystem wenzil$ truffle unbox react-box Downloading... Unpacking... Setting up... Unbox successful. Sweet! Commands: Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test Test dapp: npm test Run dev server: npm run start Build for production: npm run build

创建项目过程可能会有点慢,因为会安装项目中"package.json"里面的第三方依赖包,打开"node_modules"目录查看占用了182.1MB。


node_modules文件大小

4、查看项目结构

查看项目结构

简单说明:

contrcts/:存放智能合约Solidity代码的文件夹 migrations/:存放部署智能合约脚本的文件夹 src/:存放前端Web代码的文件夹,这里集成了React tests/:存放用于智能合约测试用例的文件夹 package.json:定义了项目所需要的各个模块,项目的配置信息(比如名称、版本、许可证等数据) truffle.js:Truffle默认的配置文件 5、编写智能合约

在contracts目录下,创建一个名为Voting.sol的合约文件,代码如下:

pragma solidity ^0.4.18; contract Voting { /* mapping:称为映射或者字典,一种键值对的映射关系存储结构 mapping的key:存储类型为bytes32,存储的是候选人名字 mapping的value:存储类型为uint8的无符号整型, bytes32类型:能存储32个字节,即32*8=256位的二进制内容 uint8类型:能存储8个字节,即8*8=64位的二进制内容 */ mapping (bytes32 => uint8) public votesReceived; /* Solidity目前不允许字符串数组,这里使用bytes32类型的数组来存储候选人名字 */ bytes32[] public candidateList; /* 构造函数,传入bytes32类型的数组,初始化所有候选人名字 */ constructor(bytes32[] candidateNames) public { candidateList = candidateNames; } /* 查询指定候选人的总票数 */ function totalVotesFor(bytes32 candidate) constant public returns (uint8) { /* require像其他语言中的断言(assert),用于条件检查。 条件满足时继续执行,条件不满足则抛出异常。 */ require(validCandidate(candidate)); return votesReceived[candidate]; } /* 对指定候选人进行投票 */ function voteForCandidate(bytes32 candidate) public { // 投票前判断是否为候选人名字 require(validCandidate(candidate)); votesReceived[candidate] += 1; } /* 检查投票名字的有效性,即判断投票名字是否在候选人名字里面 */ function validCandidate(bytes32 candidate) constant public returns (bool) { // 循环遍历候选人列表 for(uint i = 0; i < candidateList.length; i++) { // 判断投票名字是否为候选人 if (candidateList[i] == candidate) { return true; } } return false; } }

然后,修改Migrations.sol文件的内容

function Migrations() public { owner = msg.sender; } 修改为 constructor() public { owner = msg.sender; } 6、编写智能合约部署脚本

在"migratioins"目录中新建一个文件,名字为"3_deploy_voting.js",内容如下:

var Voting = artifacts.require("./Voting.sol"); module.exports = function(deployer) { deployer.deploy(Voting, ['Jack Chen', 'Andy Lau', 'Stephen Chow', 'Wenzil'], { gas: 6700000 }); }; 7、修改truffle.js

修改全局网络配置信息,可以设置默认gas(为了部署合约的时候忘记设置gas)

module.exports = { networks: { development: { host: 'localhost', port: 8545, network_id: '*', gas: 470000 } } }; 8、编译和部署智能合约 8.1 启动Ganache CLI

输入"ganache-cli"命令启动:

wenzildeiMac:~ wenzil$ ganache-cli Ganache CLI v6.1.0 (ganache-core: 2.1.0) Available Accounts ================== (0) 0x137b0be0e36b991277193a243a5c02203df54055 (1) 0xaf96555135bf00727c9efc36d42823fd5a7a9364 (2) 0xb28720c0f3263b72bb7c742418b9b7a4e4fa707c (3) 0xb9aa64aef84476493ed9db31549141846c66e59e (4) 0xfe1825f4039531e88990e1a6e4a102f0ed930fa2 (5) 0x10c26ac6076fb665541dd60e50da2f759c4e5801 (6) 0x93a546b3acc2d34f0a37825ed27fc1ed6618157f (7) 0xc0c633249d3c241b17f9ed9282c9754397b676c8 (8) 0x565262091159e0baf4eb5b78477187f3e9f4bd6e (9) 0x6f8653f1174f09edc7a38b66a7567f02408938c4 Private Keys ================== (0) b5ee4dd415b7616b0fd1d4af585579d70970b5b71e4033b02bd7bdb9b5e8ccad (1) 0f91e7d5f7a4b366a0d95dbae9c4917d22ae95809b1acb4117feadd1a60da5de (2) 4a61d911ccc12f02ae426c5771fcfffcd2c718dc1fc183e0ce30ed61f0bee8cc (3) 928c4151d6c8e12f6e6aa72fe01c9baa362032eb7b5af9bd0928bf30cdc39375 (4) 98a9d8938a2375f598b0f0f19b1bfe64509f186333dbf75fefd9e44f4d5189d6 (5) b2e43d00fb5f0b1d7360bdba4f7e408b0a0d45618121d566b56c41fe28c5d4eb (6) e4f9df75683ccce2a05865d017f756e3091cfd66c858e856b9a82273c5accbb1 (7) 627add4d489b0a8d44a6c87d684ac1119d715bbf44e87fe35fb00a2a309f3df6 (8) 6b37dd934ea80056290f7edad3c294b592b8d1548259685e50c93ca90cb94f03 (9) f4ccb4df8bf5fa6caba9a0daf3112eea09a1bde8d2056b9e129dac939bd93403 HD Wallet ================== Mnemonic: arch innocent borrow fog forward pepper legal bulk deposit orchard decline actor Base HD Path: m/44'/60'/0'/0/{account_index} Listening on localhost:8545

看到启动后自动建立了10个帐号(Accounts),每个帐号中都有100个测试用的以太币(Ether),还有每个帐号对应的私钥(Private Key)。可以用私钥来导入账号进行测试(如在MetaMask设置对应的IP和端口号,然后导入私钥)

8.2 编译智能合约

然后打开新的终端,确认在当前项目的根目录,输入"truffle compile"命令进行编译:

wenzildeiMac:VotingSystem wenzil$ pwd /Users/wenzil/Desktop/study/VotingSystem wenzildeiMac:VotingSystem wenzil$ ls box-img-lg.png node_modules test box-img-sm.png package.json truffle-config.js config public truffle.js contracts scripts migrations src wenzildeiMac:VotingSystem wenzil$ truffle compile Compiling ./contracts/Migrations.sol... Compiling ./contracts/SimpleStorage.sol... Compiling ./contracts/Voting.sol... Writing artifacts to ./build/contracts 8.3 部署智能合约

最后,执行"truffle migrate"命令部署智能合约:

wenzildeiMac:VotingSystem wenzil$ truffle migrate Using network 'development'. Running migration: 1_initial_migration.js Deploying Migrations... ... 0xcea6c6aabbf29a213f4e9bd35d4b2f47fe247777e120532e3269467442a99b41 Migrations: 0x712e510be1dea2093c9d8e5b49bf34096410f4de Saving successful migration to network... ... 0x1e6cbd04ba0883ceace67973bf2aa2f773e6fd06575691db5c2888a2343f088d Saving artifacts... Running migration: 2_deploy_contracts.js Deploying SimpleStorage... ... 0x55908414c1c4c8c6f0799fa9dce3841aa1015e3cf30ee5436618092c67739303 SimpleStorage: 0x3d29036cf74ca5045c9c445638db3c2afd46d502 Saving successful migration to network... ... 0x19ad61a3711f497ec95818ee7a4218ab095a3d044ee90bd905f89bc14eb1ac1d Saving artifacts... Running migration: 3_deploy_voting.js Deploying Voting... ... 0x80327895bb6e741d620bb5a2034c8cf9d2c6738c044aba1b03eaac4d9947ab75 Voting: 0x66c4af2ff3ca1856b57eaa89df38f7435d9ca4cb Saving successful migration to network... ... 0xe7754eedfb46de0a0c740d19c4f669edf3c6912a1822bac65c651fcdfd28ac82 Saving artifacts... 9、与投票合约交互

如果智能合约部署成功的话,可以通过truffle控制台进行交互,比如投票和投票统计操作,如下:

truffle(development)> var contract; truffle(development)> Voting.deployed().then(instance => contract = instance) TruffleContract { constructor: { [Function: TruffleContract] _static_methods: { setProvider: [Function: setProvider], new: [Function: new], at: [Function: at], deployed: [Function: deployed], defaults: [Function: defaults], hasNetwork: [Function: hasNetwork], isDeployed: [Function: isDeployed], detectNetwork: [Function: detectNetwork], setNetwork: [Function: setNetwork], resetAddress: [Function: resetAddress], link: [Function: link], clone: [Function: clone], addProp: [Function: addProp], toJSON: [Function: toJSON] }, _properties: { contract_name: [Object], #############此处省略N行打印输出############# truffle(development)> contract.voteForCandidate('Wenzil').then( (result) => { console.log(result) }) { tx: '0x92f72a7f4ad535a0e4bdf20cc90ee875747345f08ce065b3b538a5cfab7cfe1e', receipt: { transactionHash: '0x92f72a7f4ad535a0e4bdf20cc90ee875747345f08ce065b3b538a5cfab7cfe1e', transactionIndex: 0, blockHash: '0x184ef792a90a5fedf2939d8d2effbdb28bb0ca4c08455237b7d546a011dc3302', blockNumber: 7, gasUsed: 45921, cumulativeGasUsed: 45921, contractAddress: null, logs: [], status: '0x01', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' }, logs: [] } truffle(development)> contract.totalVotesFor('Wenzil').then( (result) => { console.log(result) }) { [String: '1'] s: 1, e: 0, c: [ 1 ] }

这说明已成功投了"Wenzil"一票,并且可以总计其票数。

10、修改前端,运行体验

复制Voting.sol到Remix Solidity IDE部署合约,部署成功后复制合约地址

部署投票智能合约

部署成功后,可以进行测试。

复制合约地址,为:
"0x7829abb7d424f118f1243ec13207a02f5bffc114"

然后,修改"src"->"utils"目录下的"getWeb3.js",修改IP和端口,修改后的内容如下:

var provider = new Web3.providers.HttpProvider('http://localhost:8545')

在工程中"src"目录中新建"mani.css"文件,用于App.js的样式修改,代码如下:

.pageTitle { margin: 10px; } .dataContent { border: 1px solid #ccc; display:flex; width: 50%; margin: 0 0 -1px 0; } .dataItem { flex: 1; align-items: center; justify-content: center; display: flex; padding: 5px; } .inputName { width: 180px; height: 30px; margin: 10px 10px 10px 0; } .voteButton { height: 38px; margin-left: 10px; }

修改App.js文件,文件内容:

import React, { Component } from 'react' import VotingContract from '../build/contracts/Voting.json' import getWeb3 from './utils/getWeb3' import './css/oswald.css' import './css/open-sans.css' import './css/pure-min.css' import './App.css' import './main.css' // 合约的实例 var votingContractInstance; // 投票合约地址 const contractAddr = "0x7829abb7d424f118f1243ec13207a02f5bffc114" class App extends Component { constructor(props) { super(props) this.state = { storageValue: 0, web3: null, candidateList: [{ "candidateId": 1, "candidateName": "Jack Chen", "voteNumber": 0 }, { "candidateId": 2, "candidateName": "Andy Lau", "voteNumber": 0 }, { "candidateId": 3, "candidateName": "Stephen Chow", "voteNumber": 0 }, { "candidateId": 4, "candidateName": "Wenzil", "voteNumber": 0 }] } } componentWillMount() { // Get network provider and web3 instance. // See utils/getWeb3 for more info. getWeb3 .then(results => { this.setState({ web3: results.web3 }) // Instantiate contract once web3 provided. this.instantiateContract() }) .catch(() => { console.log('Error finding web3.') }) } instantiateContract() { /* * SMART CONTRACT EXAMPLE * * Normally these functions would be called in the context of a * state management library, but for convenience I've placed them here. */ const contract = require('truffle-contract') const votingContract = contract(VotingContract) votingContract.setProvider(this.state.web3.currentProvider) // Declaring this for later so we can chain functions on SimpleStorage. // Get accounts. this.state.web3.eth.getAccounts((error, accounts) => { votingContract.at(contractAddr).then((instance) => { votingContractInstance = instance // 读取合约成功后,遍历所有候选人的票数,并更新到前端 var candidateListTemp = this.state.candidateList; for (let i = 0; i < this.state.candidateList.length; i++) { let object = candidateListTemp[i]; votingContractInstance.totalVotesFor(object.candidateName).then(result => { object.voteNumber = result.c[0] this.setState({ candidateList: candidateListTemp }) }); } }) }) } render() { return ( <div className="App" style={{margin:20}}> <div className="pageTitle">以太坊DApp投票系统</div> <div className="dataContent"> <div className="dataItem">候选人</div> <div className="dataItem">票数</div> </div> {/* 读取当前页面的候选人列表,并显示 */} { this.state.candidateList.map((item) => { return ( <div className="dataContent" key={item.candidateId}> <div className="dataItem">{item.candidateName}</div> <div className="dataItem">{item.voteNumber}</div> </div> ) }) } <input className="inputName" placeholder="请输入候选人名字" ref="inputName" /> {/* 获取输入框的内容 */} <button className="voteButton" onClick={() => { let candidateName = this.refs.inputName.value; let account = this.state.web3.eth.accounts[0]; {/* 为输入的候选人投票,写入到区块链中 */} votingContractInstance.voteForCandidate(candidateName,{from: account}).then((result) => { var currentCandidateIndex = 0; {/* 获取输入候选人的数组下标 */} for(let i = 0; i < this.state.candidateList.length; i++) { let currentCandidate = this.state.candidateList[i]; if (currentCandidate.candidateName === candidateName) { currentCandidateIndex = i; break; } } {/* 根据合约读取区块中的候选人列表数据,并刷新到前端页面 */} votingContractInstance.totalVotesFor(candidateName).then(result => { var candidateListTemp = this.state.candidateList; {/* 根据输入候选人的数组下标来更新列表数据 */} for (let i = 0; i < candidateListTemp.length; i++) { if (candidateName === candidateListTemp[currentCandidateIndex].candidateName) { candidateListTemp[currentCandidateIndex].voteNumber = result.c[0] } } this.setState({ candidateList: candidateListTemp }) }) }) }}>投票</button> </div> ); } } export default App

输入"npm run start"命令启动服务器,会自动打开网页。


启动服务器打开网页

刚开始"Wenzil"的投票是2,因为在Remix Solidity IDE部署合约的时候投了2票,然后手动投票了好几次,所以为6,没有重新实验了。

演示下为"Jack Chen"投票,会弹出MetaMask确认交易


投票演示

等几秒之后,页面自动更新票数。


投票成功

如果输入非候选人名字的话,MetaMask会显示错误信息。


投票失败

继续提交,打开Etherscan查看区块链信息,显示如图:


Etherscan查看区块链信息

搞定,收工。。。

PS:刚入坑的小白,很多不懂,还请各位大佬多赐教,谢谢!

最后编辑于

:2018.06.06 17:51:00

更多精彩内容,就在简书APP

"小礼物走一走,来简书关注我"

还没有人赞赏,支持一下

总资产77共写了4.8W字获得171个赞共150个粉丝

序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...

沈念sama阅读 200,392评论 5赞 470

序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...

文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...

文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...

正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...

茶点故事阅读 62,930评论 5赞 360

文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...

那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...

文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...

序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...

正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...

茶点故事阅读 35,382评论 2赞 317

正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...

茶点故事阅读 37,432评论 1赞 329

序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...

正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...

茶点故事阅读 38,704评论 3赞 303

文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...

文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...

我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...

正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...

茶点故事阅读 42,057评论 2赞 341

被以下专题收入,发现更多相似内容

推荐阅读更多精彩内容

1. 文章摘要 【本文目标】 通过逐步的指导和截图举证,一步步带领一个技术小白完成一个宠物商店DAPP应用的开发和...

笔名辉哥阅读 15,547评论 15赞 54

学习目标 了解智能合约 简单环境搭建 能够利用solidity编写Hello World合约 合约部署 和合约互动...

1、背景介绍以及相关问题 以太猫游戏介绍CryptoKitties(以太猫)是2017年11月上线的一款以太坊区块...

良__阅读 4,728评论 7赞 5

吃完饭坐在餐桌前玩玩手机,玩了一会无聊于是忽悠人陪我溜腿,找准目标后成功把在万宝那边等娃的姚香香忽悠出来陪我公园溜...

雪球球阅读 488评论 2赞 3

实话生活 体悟人生 今天是2016年11月18日 天气晴天 温度20-29度 今天早上四点起床 起来了以后 先按柔...

木风恒阅读 116评论 2赞 2

相关知识

开发第一个基于以太坊的dapp
Truffle & Web3.js 教程:教你开发、部署第一个去中心化应用(Dapp)
以太坊宠物店搭建教程
Web3优秀案例收集整理(附带源码):80+ 项目、创意和案例等待你的探索
第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)
第七课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)
以太坊开发
【精品】边黎安大神陪你一起学dapp以太坊 宠物狗领养 免费大神指导
开发一个去中心化应用Dapp
第十二课 从宠物商店案例看DAPP架构和WEB3.JS交互接口

网址: 开发和部署以太坊DApp——投票系统 https://m.mcbbbk.com/newsview461211.html

所属分类:萌宠日常
上一篇: 赛事投票系统如何与线下活动结合?
下一篇: 克而瑞参与的“线上投票赋能基层系