在整個(gè)加密貨幣市場(chǎng)的市值超過(guò)7000億美元之后泊愧,加密貨幣市場(chǎng)在過(guò)去幾個(gè)月太瘋狂了,但這只是一個(gè)開(kāi)始吐辙。隨著區(qū)塊鏈系統(tǒng)的不斷發(fā)展和擴(kuò)展丧肴,進(jìn)入這一新領(lǐng)域并利用這項(xiàng)技術(shù)的一個(gè)好方法是使用去中心化應(yīng)用程序,也稱為dApps巾腕。
CryptoKitties以其使以太坊區(qū)塊鏈擁擠而聞名面睛,是dApp的一個(gè)很好的例子絮蒿,它將可養(yǎng)殖和可收藏的概念與區(qū)塊鏈相結(jié)合尊搬。這個(gè)聳人聽(tīng)聞的游戲只是一個(gè)創(chuàng)造性的例子,幾乎有無(wú)限的機(jī)會(huì)土涝。
雖然看似非常復(fù)雜佛寿,但已經(jīng)開(kāi)發(fā)出某些框架和工具來(lái)抽象你與區(qū)塊鏈和智能合約的交互。在這篇博文中但壮,我將通過(guò)一種方式在以太坊上創(chuàng)建一個(gè)去中心化的投票應(yīng)用程序冀泻。我將簡(jiǎn)要介紹以太坊,但你可能應(yīng)該對(duì)它有所了解蜡饵,以便充分利用本指南弹渔。另外,我希望你知道Javascript溯祸。
為什么要開(kāi)發(fā)去中心化投票應(yīng)用肢专?
從本質(zhì)上講,利用區(qū)塊鏈技術(shù)的去中心化應(yīng)用程序允許你在沒(méi)有可信賴的第三方的情況下執(zhí)行與今天相同的操作(如轉(zhuǎn)移資金)焦辅。最好的dApp具有特定的真實(shí)世界的用例博杖,以便利用區(qū)塊鏈的獨(dú)特特征。
- 從本質(zhì)上講筷登,區(qū)塊鏈?zhǔn)且粋€(gè)共享的剃根,可編程的,加密安全的前方,可信賴的分類賬本狈醉,沒(méi)有任何一個(gè)用戶可以控制,任何人都可以查詢惠险。- Klaus Schwab
即使投票應(yīng)用對(duì)大家來(lái)說(shuō)可能不是一個(gè)偉大的應(yīng)用程序苗傅,但是我選擇使用它作為本指南,這是因?yàn)閰^(qū)塊鏈解決的主要問(wèn)題:透明度莺匠,安全性金吗,可訪問(wèn)性,可信任,是困擾當(dāng)前民主選舉的主要問(wèn)題摇庙。
由于區(qū)塊鏈?zhǔn)侨ブ行幕慕灰祝ㄍ镀保┑挠谰糜涗浐滴铮虼嗣看瓮镀倍伎梢詿o(wú)可辯駁地追溯到它發(fā)生的時(shí)間和地點(diǎn),而不會(huì)泄露選民的身份卫袒。此外宵呛,過(guò)去的投票也不能被改變,而現(xiàn)在也不能被黑客攻擊夕凝,因?yàn)槊總€(gè)交易都是由網(wǎng)絡(luò)中的每個(gè)節(jié)點(diǎn)驗(yàn)證的宝穗。任何外部或內(nèi)部攻擊者必須控制51%的節(jié)點(diǎn)才能改變記錄。
即使攻擊者能夠在偽造用戶輸入真實(shí)身份證投票時(shí)也能實(shí)現(xiàn)這一點(diǎn)码秉,但端到端投票系統(tǒng)可以讓選民驗(yàn)證他們的投票是否在系統(tǒng)中正確輸入逮矛,這使得系統(tǒng)極其安全。
以太坊的核心組成部分
我希望你讀本指南的其余部分前转砖,了解了區(qū)塊鏈和以太坊须鼎。這里有一個(gè)很棒的指南,寫(xiě)了我想讓你知道的核心組件的簡(jiǎn)要概述府蔗。
- 智能合約充當(dāng)后端邏輯和存儲(chǔ)晋控。合約是用Solidity一種智能合約語(yǔ)言編寫(xiě)的,是一個(gè)代碼和數(shù)據(jù)的集合姓赤,駐留在以太坊區(qū)塊鏈的特定地址赡译。它與面向?qū)ο缶幊讨械念惙浅O嗨疲瘮?shù)和狀態(tài)變量不铆。智能合約以及區(qū)塊鏈?zhǔn)撬袡?quán)力下放應(yīng)用程序的基礎(chǔ)蝌焚。像Blockchain一樣,它們是不可變的和分布式的狂男,這意味著如果它們已經(jīng)在以太坊網(wǎng)絡(luò)上综看,升級(jí)它們將是一種痛苦。幸運(yùn)的是岖食,這里有一些方法可以做到這一點(diǎn)红碑。
- 以太坊虛擬機(jī)(EVM)處理整個(gè)以太坊網(wǎng)絡(luò)的內(nèi)部狀態(tài)和計(jì)算。將EVM視為這種大規(guī)模去中心化計(jì)算機(jī)泡垃,其中包含能夠執(zhí)行代碼析珊,更改數(shù)據(jù)和相互交互的
addresses
。 - Web3.js是一個(gè)Javascript API蔑穴,允許你與區(qū)塊鏈進(jìn)行交互忠寻,包括進(jìn)行交易和調(diào)用智能合約。此API抽象了與以太坊客戶端的通信存和,允許開(kāi)發(fā)人員專注于他們的應(yīng)用程序的內(nèi)容奕剃。你必須在瀏覽器中嵌入一個(gè)web3實(shí)例才能執(zhí)行此操作衷旅。
我們將使用的其他工具
- Truffle是以太坊的流行測(cè)試開(kāi)發(fā)框架。它包括開(kāi)發(fā)區(qū)塊鏈纵朋,編譯和遷移腳本柿顶,用于將合約部署到區(qū)塊鏈,合約測(cè)試等操软。它使開(kāi)發(fā)更容易嘁锯!
- Truffle Contracts是Web3 Javascript API之上的抽象,允許你輕松連接智能合約并與之互動(dòng)聂薪。
- Metamask將以太坊帶入你的瀏覽器家乘。它是一個(gè)瀏覽器擴(kuò)展,提供鏈接到你的以太坊地址的安全web3實(shí)例藏澳,允許你使用去中心化應(yīng)用程序仁锯。我們不會(huì)在本教程中使用Metamask,但它是人們?cè)谏a(chǎn)中與DApp交互的一種方式笆载。相反扑馁,我們將在開(kāi)發(fā)期間注入我們自己的web3實(shí)例。有關(guān)更多信息凉驻,請(qǐng)查看此鏈接。
開(kāi)始吧复罐!
為簡(jiǎn)單起見(jiàn)涝登,我們實(shí)際上不會(huì)構(gòu)建我之前描述的完整投票系統(tǒng)。為了便于說(shuō)明效诅,它只是一個(gè)單頁(yè)應(yīng)用程序胀滚,用戶可以輸入他們的ID并為候選人投票。還將有一個(gè)按鈕乱投,計(jì)算并顯示每個(gè)候選人的投票數(shù)咽笼。
這樣,我們將能夠?qū)W⒂谠趹?yīng)用程序中創(chuàng)建智能合約并與之交互的過(guò)程戚炫。整個(gè)應(yīng)用程序的源代碼將在此存儲(chǔ)庫(kù)中剑刑,你需要安裝Node.js和npm。
1.首先双肤,讓我們?cè)谌址秶鷥?nèi)安裝Truffle施掏。
npm install -g truffle
要使用Truffle命令,必須在現(xiàn)有項(xiàng)目中運(yùn)行它們茅糜。
git clone https://github.com/tko22/truffle-webpack-boilerplate
cd truffle-webpack-boilerplate
npm install
這個(gè)存儲(chǔ)庫(kù)只是一個(gè)Truffle Box的框架七芭,它是可以在一個(gè)命令中獲得的樣板或示例應(yīng)用程序 - truffle unbox [box name]
。但是蔑赘,帶有webpack的Truffle box未使用最新版本進(jìn)行更新狸驳,并包含一個(gè)示例應(yīng)用程序预明。因此,我創(chuàng)建了這個(gè)repo耙箍。
2.目錄結(jié)構(gòu)
你的目錄結(jié)構(gòu)應(yīng)包括以下內(nèi)容:
-
contracts/
:包括所有合約的文件夾贮庞。不要?jiǎng)h除Migrations.sol
。 -
migrations/
:包含Migration files
的文件夾究西,可幫助你將智能合約部署到區(qū)塊鏈中窗慎。 -
src/
:保存應(yīng)用程序的HTML/CSS和Javascript文件。 -
truffle.js
:truffle配置文件卤材。 -
build/
:在編譯合約之前遮斥,你不會(huì)看到此文件夾。此文件夾包含構(gòu)建文件扇丛,因此不要修改任何這些文件术吗!構(gòu)建文件描述了合約的功能和體系結(jié)構(gòu),并提供了有關(guān)如何與區(qū)塊鏈中的智能合約進(jìn)行交互的Truffle Contracts和web3信息帆精。
1.寫(xiě)下你的智能合約
設(shè)置和介紹完较屿,讓我們開(kāi)始寫(xiě)代碼吧!首先卓练,我們將編寫(xiě)我們的智能合約隘蝎,這是用Solidity編寫(xiě)的(其他語(yǔ)言不那么受歡迎)。這可能看起來(lái)很不爽襟企,但事實(shí)并非如此嘱么。
對(duì)于任何應(yīng)用程序,你希望智能合約盡可能簡(jiǎn)單顽悼,甚至是非常簡(jiǎn)單曼振。請(qǐng)記住,你必須為你所做的每筆計(jì)算/交易付費(fèi)蔚龙,而你的智能合約將永遠(yuǎn)存在于區(qū)塊鏈中冰评。所以,你真的希望它能夠完美地運(yùn)作——也就是說(shuō)木羹,它越復(fù)雜甲雅,就越容易犯錯(cuò)誤。
我們的合約將包括:
- 狀態(tài)變量:包含永久存儲(chǔ)在區(qū)塊鏈中的值的變量汇跨。我們將使用狀態(tài)變量來(lái)保存選民和候選人的名單和數(shù)量务荆。
- 函數(shù):函數(shù)是智能合約的可執(zhí)行文件。它們是我們要求與區(qū)塊鏈進(jìn)行交互的內(nèi)容穷遂,具有不同級(jí)別的內(nèi)部和外部可見(jiàn)性函匕。請(qǐng)記住,無(wú)論何時(shí)你想要更改變量的值/狀態(tài)蚪黑,都必須進(jìn)行交易——這要耗費(fèi)以太幣盅惜。你也可以
calls
區(qū)塊鏈中剩,這不會(huì)花費(fèi)任何以太,因?yàn)槟闼龅母膶⒈讳N(xiāo)毀(當(dāng)我們實(shí)際進(jìn)行transactions
和call
時(shí)抒寂,在下面會(huì)有更多內(nèi)容)结啼。 - 事件:每當(dāng)調(diào)用事件時(shí),傳遞給事件的值都將記錄在交易日志中屈芜。這允許Javascript回調(diào)函數(shù)或已解析的promises查看你想要在交易之后傳回的特定值郊愧。這是因?yàn)槊看芜M(jìn)行交易時(shí),都會(huì)返回交易日志井佑。我們將使用一個(gè)事件來(lái)記錄新創(chuàng)建的候選者的ID属铁,我們將顯示該ID。
- 結(jié)構(gòu)類型 - 這與C結(jié)構(gòu)非常相似躬翁。Structs允許你保存多個(gè)變量焦蘑,并且對(duì)于具有多個(gè)屬性的事物非常棒。
Candidates
只會(huì)有他們的名字和黨派盒发,但你絕對(duì)可以為他們添加更多屬性例嘱。 - 映射 - 將它們視為hash映射或字典,它具有鍵值對(duì)宁舰。我們將使用兩個(gè)映射拼卵。
這里沒(méi)有列出更多類型,但有些類型稍微復(fù)雜一些明吩。這五個(gè)包含了智能合約通常使用的大部分結(jié)構(gòu)间学。這里將更深入地解釋這些類型。
作為參考印荔,這是智能合約的代碼。請(qǐng)注意详羡,此文件應(yīng)該被稱為Voting.sol
但我希望Github gist
具有style
仍律,所以我給它一個(gè).js擴(kuò)展名。與本指南的其余部分一樣实柠,我將在代碼中提供注釋解釋它正在做什么水泉,然后我將在指出某些警告和邏輯的同時(shí)解釋大體思路。
pragma solidity ^0.4.18;
// written for Solidity version 0.4.18 and above that doesnt break functionality
contract Voting {
// an event that is called whenever a Candidate is added so the frontend could
// appropriately display the candidate with the right element id (it is used
// to vote for the candidate, since it is one of arguments for the function "vote")
event AddedCandidate(uint candidateID);
// describes a Voter, which has an id and the ID of the candidate they voted for
struct Voter {
bytes32 uid; // bytes32 type are basically strings
uint candidateIDVote;
}
// describes a Candidate
struct Candidate {
bytes32 name;
bytes32 party;
// "bool doesExist" is to check if this Struct exists
// This is so we can keep track of the candidates
bool doesExist;
}
// These state variables are used keep track of the number of Candidates/Voters
// and used to as a way to index them
uint numCandidates; // declares a state variable - number Of Candidates
uint numVoters;
// Think of these as a hash table, with the key as a uint and value of
// the struct Candidate/Voter. These mappings will be used in the majority
// of our transactions/calls
// These mappings will hold all the candidates and Voters respectively
mapping (uint => Candidate) candidates;
mapping (uint => Voter) voters;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* These functions perform transactions, editing the mappings *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function addCandidate(bytes32 name, bytes32 party) public {
// candidateID is the return variable
uint candidateID = numCandidates++;
// Create new Candidate Struct with name and saves it to storage.
candidates[candidateID] = Candidate(name,party,true);
AddedCandidate(candidateID);
}
function vote(bytes32 uid, uint candidateID) public {
// checks if the struct exists for that candidate
if (candidates[candidateID].doesExist == true) {
uint voterID = numVoters++; //voterID is the return variable
voters[voterID] = Voter(uid,candidateID);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * *
* Getter Functions, marked by the key word "view" *
* * * * * * * * * * * * * * * * * * * * * * * * * */
// finds the total amount of votes for a specific candidate by looping
// through voters
function totalVotes(uint candidateID) view public returns (uint) {
uint numOfVotes = 0; // we will return this
for (uint i = 0; i < numVoters; i++) {
// if the voter votes for this specific candidate, we increment the number
if (voters[i].candidateIDVote == candidateID) {
numOfVotes++;
}
}
return numOfVotes;
}
function getNumOfCandidates() public view returns(uint) {
return numCandidates;
}
function getNumOfVoters() public view returns(uint) {
return numVoters;
}
// returns candidate information, including its ID, name, and party
function getCandidate(uint candidateID) public view returns (uint,bytes32, bytes32) {
return (candidateID,candidates[candidateID].name,candidates[candidateID].party);
}
}
基本上窒盐,我們有兩個(gè)Structs(包含多個(gè)變量的類型)草则,用于描述選民和候選人。使用Structs蟹漓,我們可以為它們分配多個(gè)屬性炕横,例如電子郵件,地址等葡粒。
為了跟蹤選民和候選人份殿,我們將它們放入單獨(dú)的映射中膜钓,它們是整數(shù)索引的。候選人或選民的索引/密鑰——讓我們稱之為ID——是函數(shù)訪問(wèn)它們的唯一方式卿嘲。
我們還會(huì)跟蹤選民和候選人的數(shù)量颂斜,這將有助于我們?yōu)樗麄兙幹扑饕4送馐霸妫灰浀?行中的事件沃疮,該事件將在添加時(shí)記錄候選人的ID。我們的界面將使用此事件梅肤,因?yàn)槲覀冃枰櫤蜻x人的ID以便為候選人投票司蔬。
- 1.我知道,與我之前所說(shuō)的關(guān)于使合約變得非常簡(jiǎn)單的說(shuō)法相反凭语,我認(rèn)為這個(gè)合約與這個(gè)應(yīng)用實(shí)際上做的相比有點(diǎn)復(fù)雜葱她。但是,我這樣做是為了讓你們更容易進(jìn)行編輯并在之后為此應(yīng)用程序添加功能(最后更多內(nèi)容)似扔。如果你想制作一個(gè)更簡(jiǎn)單的投票應(yīng)用程序吨些,智能合約可以在不到15行代碼。
- 2.請(qǐng)注意炒辉,狀態(tài)變量
numCandidates
和numVoters
未聲明為public豪墅。默認(rèn)情況下,這些變量具有internal
可見(jiàn)性黔寇,這意味著它們只能由當(dāng)前合約或派生合約直接訪問(wèn)(不用擔(dān)心偶器,我們不會(huì)使用它)。 - 3.我們使用
32bytes
用于字符串而不是使用string
類型缝裤。我們的EVM具有32字節(jié)的字大小屏轰,因此它被optimized
以處理32字節(jié)的塊中的數(shù)據(jù)。(當(dāng)數(shù)據(jù)不是32字節(jié)的塊時(shí)憋飞,編譯器霎苗,例如Solidity,必須做更多的工作并生成更多的字節(jié)碼榛做,這實(shí)際上會(huì)導(dǎo)致更高的天然氣成本唁盏。) - 4.當(dāng)用戶投票時(shí),會(huì)創(chuàng)建一個(gè)新的Voter結(jié)構(gòu)并將其添加到映射中检眯。為了計(jì)算某個(gè)候選人的投票數(shù)厘擂,你必須遍歷所有選民并計(jì)算投票數(shù)。候選人的行為相同锰瘸。因此刽严,這些映射將保留所有候選人和選民的歷史。
2.實(shí)例化web3和合約
完成我們的智能合約后获茬,我們現(xiàn)在需要運(yùn)行我們的測(cè)試區(qū)塊鏈并將此合約部署到區(qū)塊鏈上港庄。我們還需要一種方法來(lái)與它交互倔既,這將通過(guò)web3.js完成。
在我們開(kāi)始測(cè)試區(qū)塊鏈之前鹏氧,我們必須在/contracts
文件夾中創(chuàng)建一個(gè)名為2_deploy_contracts.js
的文件渤涌,告訴它在遷移時(shí)包含你的投票智能合約。
var Voting = artifacts.require("Voting")
module.exports = function(deployer) {
deployer.deploy(Voting)
}
要開(kāi)始開(kāi)發(fā)以太坊區(qū)塊鏈把还,請(qǐng)轉(zhuǎn)到命令行并運(yùn)行:
truffle develop
由于Solidity是一種編譯語(yǔ)言实蓬,我們必須首先將其編譯為字節(jié)碼,以便EVM執(zhí)行吊履。
compile
你現(xiàn)在應(yīng)該在目錄中看到一個(gè)文件夾build/
安皱。此文件夾包含構(gòu)建文件,這對(duì)Truffle的內(nèi)部工作至關(guān)重要艇炎,因此請(qǐng)勿修改它們酌伊!
接下來(lái),我們必須遷移合約缀踪。migrations是一個(gè)truffle腳本居砖,可幫助你在開(kāi)發(fā)時(shí)更改應(yīng)用程序合約的狀態(tài)。請(qǐng)記住驴娃,你的合約已部署到區(qū)塊鏈上的某個(gè)地址奏候,因此無(wú)論何時(shí)進(jìn)行更改,你的合約都將位于不同的地址唇敞。 遷移可幫助你執(zhí)行此操作蔗草,還可幫助你移動(dòng)數(shù)據(jù)。
migrate
恭喜疆柔!你的智能合約現(xiàn)在永遠(yuǎn)在區(qū)塊鏈上咒精。好吧,還不是真的...... 因?yàn)閠ruffle develop會(huì)在每次停止時(shí)刷新旷档。
如果你想擁有一個(gè)持久的區(qū)塊鏈狠轻,可以考慮一下由Truffle開(kāi)發(fā)的Ganache。如果你使用的是Ganache彬犯,則無(wú)需調(diào)用truffle develop。相反查吊,你將運(yùn)行truffle compile
和truffle migrate
谐区。要了解在沒(méi)有Truffle的情況下部署合約需要什么,請(qǐng)查看此博客文章逻卖。
一旦我們將智能合約部署到區(qū)塊鏈宋列,我們將不得不在應(yīng)用程序啟動(dòng)時(shí)在瀏覽器上使用Javascript設(shè)置web3.0實(shí)例。因此评也,下一段代碼將放在js/app.js
的底部炼杖。請(qǐng)注意灭返,我們使用的是web3.0版本0.20.1。
// When the page loads, we create a web3 instance and set a provider. We then set up the app
window.addEventListener("load", function() {
// Is there an injected web3 instance?
if (typeof web3 !== "undefined") {
console.warn("Using web3 detected from external source like Metamask")
// If there is a web3 instance(in Mist/Metamask), then we use its provider to create our web3object
window.web3 = new Web3(web3.currentProvider)
} else {
console.warn("No web3 detected. Falling back to http://localhost:9545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask")
// fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545"))
}
// initializing the App
window.App.start()
})
如果你不理解這段代碼坤邪,你真的不必太擔(dān)心熙含。只要知道這將在應(yīng)用程序啟動(dòng)時(shí)運(yùn)行,并將檢查瀏覽器中是否已存在web3實(shí)例(Metamask)艇纺。如果沒(méi)有怎静,我們將創(chuàng)建一個(gè)與localhost:9545
交互Truffle開(kāi)發(fā)區(qū)塊鏈。
如果你正在使用Ganache黔衡,你必須將端口更改為7545
.一旦創(chuàng)建了一個(gè)實(shí)例蚓聘,我們將調(diào)用start
函數(shù)。
3.添加功能
我們需要做的最后一件事是為應(yīng)用程序編寫(xiě)接口盟劫。這涉及任何Web應(yīng)用程序的基本要素——HTML夜牡,CSS和Javascript(我們已經(jīng)編寫(xiě)了一些用于創(chuàng)建web3實(shí)例的Javascript)。首先侣签,讓我們創(chuàng)建我們的HTML文件塘装。
<!DOCTYPE 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">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Ethereum Voting Dapp</title>
<!-- Bootstrap -->
<link rel="stylesheet" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div>
<h1 class="text-center">Ethereum Voting Dapp</h1>
<hr/>
<br/>
</div>
</div>
<div class="row">
<div class="col-md-4">
<p>Add ID and click candidate to vote</p>
<div class="input-group mb-3">
<input type="text" class="form-control" id="id-input" placeholder="Enter ID">
</div>
<div class="candidate-box"></div>
<button class="btn btn-primary" onclick="App.vote()">Vote</button>
<div class="msg"></div>
</div>
<div class="col-md-6">
<button class="btn btn-primary" onclick="App.findNumOfVotes()">Count Votes</button>
<div id="vote-box"></div>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4" crossorigin="anonymous"></script>
<!-- Custom Scripts -->
<script src="app.js"></script>
</body>
</html>
這是一個(gè)非常簡(jiǎn)單的頁(yè)面,帶有用戶ID的輸入表單硝岗,以及用于投票和計(jì)票的按鈕氢哮。點(diǎn)擊這些按鈕后,他們將調(diào)用投票的特定功能型檀,并找到候選人的投票數(shù)播急。
但是有三個(gè)重要的div元素,其中有id:candidate-box
朦前,msg
和vote-box
唬格,它們分別包含每個(gè)候選者的復(fù)選框,一條消息和一個(gè)投票數(shù)仓坞。我們還導(dǎo)入了JQuery背零,Bootstrap和app.js
。
現(xiàn)在无埃,我們只需要與合約互動(dòng)并實(shí)施投票和計(jì)算每個(gè)候選人的投票數(shù)量的功能徙瓶。JQuery將控制DOM,當(dāng)我們進(jìn)行交易或調(diào)用Blockchain時(shí)嫉称,我們將使用Promises侦镇。以下是app.js
的代碼。
// import CSS. Webpack with deal with it
import "../css/style.css"
// Import libraries we need.
import { default as Web3} from "web3"
import { default as contract } from "truffle-contract"
// get build artifacts from compiled smart contract and create the truffle contract
import votingArtifacts from "../../build/contracts/Voting.json"
var VotingContract = contract(votingArtifacts)
/*
* This holds all the functions for the app
*/
window.App = {
// called when web3 is set up
start: function() {
// setting up contract providers and transaction defaults for ALL contract instances
VotingContract.setProvider(window.web3.currentProvider)
VotingContract.defaults({from: window.web3.eth.accounts[0],gas:6721975})
// creates an VotingContract instance that represents default address managed by VotingContract
VotingContract.deployed().then(function(instance){
// calls getNumOfCandidates() function in Smart Contract,
// this is not a transaction though, since the function is marked with "view" and
// truffle contract automatically knows this
instance.getNumOfCandidates().then(function(numOfCandidates){
// adds candidates to Contract if there aren't any
if (numOfCandidates == 0){
// calls addCandidate() function in Smart Contract and adds candidate with name "Candidate1"
// the return value "result" is just the transaction, which holds the logs,
// which is an array of trigger events (1 item in this case - "addedCandidate" event)
// We use this to get the candidateID
instance.addCandidate("Candidate1","Democratic").then(function(result){
$("#candidate-box").append(`<div class='form-check'><input class='form-check-input' type='checkbox' value='' id=${result.logs[0].args.candidateID}><label class='form-check-label' for=0>Candidate1</label></div>`)
})
instance.addCandidate("Candidate2","Republican").then(function(result){
$("#candidate-box").append(`<div class='form-check'><input class='form-check-input' type='checkbox' value='' id=${result.logs[0].args.candidateID}><label class='form-check-label' for=1>Candidate1</label></div>`)
})
// the global variable will take the value of this variable
numOfCandidates = 2
}
else { // if candidates were already added to the contract we loop through them and display them
for (var i = 0; i < numOfCandidates; i++ ){
// gets candidates and displays them
instance.getCandidate(i).then(function(data){
$("#candidate-box").append(`<div class="form-check"><input class="form-check-input" type="checkbox" value="" id=${data[0]}><label class="form-check-label" for=${data[0]}>${window.web3.toAscii(data[1])}</label></div>`)
})
}
}
// sets global variable for number of Candidates
// displaying and counting the number of Votes depends on this
window.numOfCandidates = numOfCandidates
})
}).catch(function(err){
console.error("ERROR! " + err.message)
})
},
// Function that is called when user clicks the "vote" button
vote: function() {
var uid = $("#id-input").val() //getting user inputted id
// Application Logic
if (uid == ""){
$("#msg").html("<p>Please enter id.</p>")
return
}
// Checks whether a candidate is chosen or not.
// if it is, we get the Candidate's ID, which we will use
// when we call the vote function in Smart Contracts
if ($("#candidate-box :checkbox:checked").length > 0){
// just takes the first checked box and gets its id
var candidateID = $("#candidate-box :checkbox:checked")[0].id
}
else {
// print message if user didn't vote for candidate
$("#msg").html("<p>Please vote for a candidate.</p>")
return
}
// Actually voting for the Candidate using the Contract and displaying "Voted"
VotingContract.deployed().then(function(instance){
instance.vote(uid,parseInt(candidateID)).then(function(result){
$("#msg").html("<p>Voted</p>")
})
}).catch(function(err){
console.error("ERROR! " + err.message)
})
},
// function called when the "Count Votes" button is clicked
findNumOfVotes: function() {
VotingContract.deployed().then(function(instance){
// this is where we will add the candidate vote Info before replacing whatever is in #vote-box
var box = $("<section></section>")
// loop through the number of candidates and display their votes
for (var i = 0; i < window.numOfCandidates; i++){
// calls two smart contract functions
var candidatePromise = instance.getCandidate(i)
var votesPromise = instance.totalVotes(i)
// resolves Promises by adding them to the variable box
Promise.all([candidatePromise,votesPromise]).then(function(data){
box.append(`<p>${window.web3.toAscii(data[0][1])}: ${data[1]}</p>`)
}).catch(function(err){
console.error("ERROR! " + err.message)
})
}
$("#vote-box").html(box) // displays the "box" and replaces everything that was in it before
})
}
}
// When the page loads, we create a web3 instance and set a provider. We then set up the app
window.addEventListener("load", function() {
// Is there an injected web3 instance?
if (typeof web3 !== "undefined") {
console.warn("Using web3 detected from external source like Metamask")
// If there is a web3 instance(in Mist/Metamask), then we use its provider to create our web3object
window.web3 = new Web3(web3.currentProvider)
} else {
console.warn("No web3 detected. Falling back to http://localhost:9545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for deployment. More info here: http://truffleframework.com/tutorials/truffle-and-metamask")
// fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545"))
}
// initializing the App
window.App.start()
})
請(qǐng)注意织阅,我在上一步中用于創(chuàng)建web3實(shí)例的代碼也在這里壳繁。首先,我們導(dǎo)入必要的庫(kù)和webpack內(nèi)容,包括web3和Truffle Contracts闹炉。我們將使用Truffle Contracts蒿赢,它建立在web3之上,與Blockchain進(jìn)行交互渣触。
要使用它羡棵,我們將獲取在編譯投票智能合約時(shí)自動(dòng)構(gòu)建的構(gòu)建文件,并使用它們來(lái)創(chuàng)建Truffle Contracts
昵观。最后晾腔,我們?cè)谌肿兞?code>windows中設(shè)置函數(shù),用于啟動(dòng)應(yīng)用程序啊犬,投票給候選人灼擂,以及查找投票數(shù)。
要實(shí)際與區(qū)塊鏈交互觉至,我們必須使用deployed
的功能創(chuàng)建松露合約的實(shí)例剔应。反過(guò)來(lái),這將返回一個(gè)承諾语御,該實(shí)例作為你將用于從智能合約調(diào)用函數(shù)的返回值峻贮。
有兩種方法可以與這些功能進(jìn)行交互:交易和調(diào)用。交易是一種寫(xiě)操作应闯,它將被廣播到整個(gè)網(wǎng)絡(luò)并由礦工處理(因此纤控,成本為Ether)。如果要更改狀態(tài)變量碉纺,則必須執(zhí)行交易船万,因?yàn)樗鼘⒏膮^(qū)塊鏈的狀態(tài)。
call是一種讀操作骨田,模擬交易但丟棄狀態(tài)變化耿导。因此,它不會(huì)花費(fèi)以太态贤。這非常適合調(diào)用getter函數(shù)(查看我們之前在智能合約中編寫(xiě)的四個(gè)getter函數(shù))舱呻。
要使用Truffle Contracts進(jìn)行交易,你可以編寫(xiě)instance.functionName(param1,param2)
悠汽,將instance
作為deployed
函數(shù)返回的實(shí)例(例如箱吕,檢查第36行)。此事務(wù)將返回一個(gè)以交易數(shù)據(jù)作為返回值的promise柿冲。因此殖氏,如果在智能合約函數(shù)中返回一個(gè)值,但是使用相同的函數(shù)執(zhí)行交易姻采,則不會(huì)返回該值。
這就是為什么我們有一個(gè)事件會(huì)記錄你想要寫(xiě)入要返回的交易數(shù)據(jù)的任何內(nèi)容。在第36-37行慨亲,我們進(jìn)行交易以添加一個(gè)候選人即Candidate婚瓜。當(dāng)我們確定promise時(shí),我們?cè)诮Y(jié)果中有交易數(shù)據(jù)刑棵。
要獲取我們使用事件AddedCandidate()
記錄的候選ID(檢查智能合約以查看它0)巴刻,我們必須檢查日志并檢索它:result.logs[0].args.candidateID
。
要真正了解正在發(fā)生的事情蛉签,請(qǐng)使用Chrome開(kāi)發(fā)人員工具打印result
并查看其result
結(jié)構(gòu)胡陪。
要進(jìn)行調(diào)用,你將編寫(xiě)instance.functionName.call(param1,param2)碍舍。但是柠座,如果某個(gè)函數(shù)具有關(guān)鍵字
view,那么Truffle Contracts將自動(dòng)創(chuàng)建一個(gè)調(diào)用片橡,因此你無(wú)需添加
.call`妈经。
這就是我們的getter函數(shù)具有view關(guān)鍵字的原因。與進(jìn)行交易不同捧书,返回的調(diào)用promise將具有智能合約函數(shù)返回的任何返回值吹泡。
我現(xiàn)在將簡(jiǎn)要解釋這三個(gè)函數(shù),但如果你構(gòu)建了從數(shù)據(jù)存儲(chǔ)中檢索/更改數(shù)據(jù)并相應(yīng)地操作DOM的應(yīng)用程序经瓷,那么這應(yīng)該非常熟悉爆哑。將Blockchain視為你的數(shù)據(jù)庫(kù),將Truffle Contracts視為從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)的API舆吮。
App.start()
創(chuàng)建web3實(shí)例后立即調(diào)用此函數(shù)揭朝。要使Truffle Contracts正常工作,我們必須將接口設(shè)置為創(chuàng)建的web3實(shí)例并設(shè)置默認(rèn)值(例如你正在使用的帳戶以及你要為交易支付的gas量)歪泳。
由于我們處于開(kāi)發(fā)模式萝勤,我們可以使用任何數(shù)量的gas和任何帳戶。在生產(chǎn)過(guò)程中呐伞,我們將采用MetaMask提供的帳戶敌卓,并嘗試找出你可以使用的最少量的gas,因?yàn)樗鼘?shí)際上是真錢(qián)伶氢。
設(shè)置好所有內(nèi)容后趟径,我們現(xiàn)在將顯示每個(gè)候選人的復(fù)選框,供用戶投票癣防。為此蜗巧,我們必須創(chuàng)建合約實(shí)例并獲取候選人的信息。如果沒(méi)有候選人蕾盯,我們將創(chuàng)建他們幕屹。為了讓用戶投票給候選人,我們必須提供該特定候選人的ID。因此望拖,我們使每個(gè)checkbox元素具有候選ID的id
(HTML元素屬性)渺尘。另外,我們將把候選數(shù)量添加到全局變量numOfCandidates
中说敏,我們將在App.findNumOfVotes()
中使用它鸥跟。JQuery用于將每個(gè)復(fù)選框及其候選名稱附加到.candidate-box
。
App.vote()
此功能將根據(jù)單擊的復(fù)選框及其id
屬性為某個(gè)候選人投票盔沫。
- 1.我們將檢查用戶是否輸入了他們的userID医咨,這是他們的身份。如果他們沒(méi)有架诞,我們會(huì)顯示一條消息告訴他們需要這樣做拟淮。
- 2.我們將檢查用戶是否正在為候選人投票,檢查是否至少有一個(gè)被點(diǎn)擊的復(fù)選框侈贷。如果沒(méi)有點(diǎn)擊任何復(fù)選框惩歉,我們也會(huì)顯示一條消息,告訴他們請(qǐng)投票給候選人俏蛮。如果單擊其中一個(gè)復(fù)選框撑蚌,我們將獲取該復(fù)選框的id屬性,該屬性也是鏈接候選人的ID搏屑,并使用該屬性為候選人投票争涌。
交易完成后,我們將解決退回的承諾并顯示Voted
已經(jīng)完成投票的消息辣恋。
App.findNumOfVotes()
最后一個(gè)函數(shù)將找到每個(gè)候選人的投票數(shù)并顯示它們亮垫。我們將通過(guò)候選人并調(diào)用兩個(gè)智能合約函數(shù),getCandidate
和totalVotes
伟骨。我們將解決這些承諾并為該特定候選人創(chuàng)建HTML元素饮潦。
現(xiàn)在,啟動(dòng)應(yīng)用程序携狭,你將在`http://localhost:8080/上看到它继蜡!
npm run dev
資源
我知道,這很多......當(dāng)你慢慢開(kāi)發(fā)這個(gè)應(yīng)用程序并真正了解正在發(fā)生的事情時(shí)逛腿,你可能會(huì)暫時(shí)打開(kāi)這篇文章稀并。但那是在學(xué)習(xí)!請(qǐng)使用以太網(wǎng)单默,truffle以及我在下面提供的所有文檔補(bǔ)充本指南碘举。我試圖點(diǎn)擊本文中的許多關(guān)鍵點(diǎn),但這只是一個(gè)簡(jiǎn)短的概述搁廓,這些資源將有很大幫助引颈。
- 關(guān)于Solidity和Smart Contracts的一切:我的意思是一切耕皮。
- 關(guān)于truffle一切
- truffle contract 文檔
- Web3 Javascript API:這將是很好的知識(shí)和參考,但松露合約抽象了很多部分线欲。
- 有用的DApp模式
- 以太坊文件:看目錄明场,有很多東西。
- CryptoKitties代碼說(shuō)明:作者介紹了CryptoKitties的智能合約的重要部分李丰。
- 智能合約最佳實(shí)踐:必讀。
總結(jié)
在以太坊上構(gòu)建應(yīng)用程序非常類似于調(diào)用后端服務(wù)的常規(guī)應(yīng)用程序逼泣。最難的部分是編寫(xiě)一份強(qiáng)大而完整的智能合約趴泌。我希望本指南可以幫助你了解去中心化應(yīng)用程序和以太坊的核心知識(shí),并幫助你啟動(dòng)你對(duì)開(kāi)發(fā)它們的興趣拉庶。
如果你想創(chuàng)建我們已經(jīng)建立的東西嗜憔,這里有一些想法。我實(shí)際上已經(jīng)用這樣的方式編寫(xiě)了智能合約氏仗,它可以很容易地實(shí)現(xiàn)我在本指南中提到的一切吉捶。
- 顯示每個(gè)候選人的一方。當(dāng)我們運(yùn)行g(shù)etCandidate(id)時(shí)皆尔,我們已經(jīng)獲得了候選人的聚會(huì)呐舔。
- 檢查用戶輸入的ID是否唯一。
- 詢問(wèn)并存儲(chǔ)有關(guān)用戶的更多信息慷蠕,例如他們的出生日期和家庭住址珊拼。
- 添加選項(xiàng)以查看具有特定ID的人是否已投票。你將創(chuàng)建一個(gè)新表單以輸入ID流炕,然后你可以在區(qū)塊鏈中搜索該特定用戶澎现。
- 寫(xiě)一個(gè)新的智能合約功能,立即計(jì)算兩個(gè)候選人的選票每辟。目前剑辫,我們必須為兩個(gè)候選人分別進(jìn)行兩次調(diào)用,要求合約循環(huán)遍歷所有用戶兩次渠欺。
- 允許添加新候選人妹蔽。這意味著添加一個(gè)新表單來(lái)添加候選人,但也會(huì)更改我們?nèi)绾卧谇岸孙@示和投票候選人峻堰。
- 要求用戶擁有以太坊地址進(jìn)行投票讹开。我不包括用戶地址的邏輯是因?yàn)椴幌Mx民讓以太坊參與這個(gè)投票過(guò)程。但是捐名,許多DApps將要求用戶擁有以太坊地址旦万。
此外,這里有一些提示镶蹋,可以防止一些錯(cuò)誤發(fā)生:
- 當(dāng)發(fā)生奇怪的事情時(shí)成艘,請(qǐng)多檢查一下你的智能合約函數(shù)赏半。我在一個(gè)bug上花了幾個(gè)小時(shí)才發(fā)現(xiàn)我在我的一個(gè)函數(shù)中返回了錯(cuò)誤的值。
- 連接到開(kāi)發(fā)區(qū)塊鏈時(shí)淆两,請(qǐng)檢查你的URL和端口是否正確断箫。記住:
7545
用于truffle開(kāi)發(fā)秋冰,9545
用于Ganache仲义。這些是默認(rèn)值,因此如果你無(wú)法連接到區(qū)塊鏈剑勾,你可能已經(jīng)更改了它們埃撵。 - 我沒(méi)有仔細(xì)閱讀,因?yàn)檫@個(gè)指南已經(jīng)太久了虽另,我可能會(huì)在這個(gè)問(wèn)題上再發(fā)一篇文章 - 但是你應(yīng)該測(cè)試你的合約暂刘!它會(huì)有很大幫助。
- 如果你不熟悉promises捂刺,請(qǐng)了解它們的工作原理以及如何使用它們谣拣。Truffle Contracts使用promises,而web3也將支持promises族展。如果你做錯(cuò)了森缠,他們可能會(huì)搞砸你正在檢索的大量數(shù)據(jù)。
歡呼致力于去中心化和安全的互聯(lián)網(wǎng) - Web 3.0苛谷!
**另外我們還提供一些加快學(xué)習(xí)過(guò)程和提供問(wèn)答服務(wù)的以太坊教程如下: **
- web3j教程辅鲸,主要是針對(duì)java和android程序員進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的web3j詳解。
腹殿,主要是介紹使用node.js独悴、mongodb、區(qū)塊鏈锣尉、ipfs實(shí)現(xiàn)去中心化電商DApp實(shí)戰(zhàn)刻炒,適合進(jìn)階。- php以太坊自沧,主要是介紹使用php進(jìn)行智能合約開(kāi)發(fā)交互坟奥,進(jìn)行賬號(hào)創(chuàng)建、交易拇厢、轉(zhuǎn)賬爱谁、代幣開(kāi)發(fā)以及過(guò)濾器和事件等內(nèi)容。
- 以太坊教程孝偎,主要介紹智能合約與dapp應(yīng)用開(kāi)發(fā)访敌,適合入門(mén)。
- 以太坊開(kāi)發(fā)
- python以太坊教程衣盾,主要是針對(duì)python工程師使用web3.py進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的詳解寺旺。
- C#以太坊爷抓,主要講解如何使用C#開(kāi)發(fā)基于.Net的以太坊應(yīng)用,包括賬戶管理阻塑、狀態(tài)與交易蓝撇、智能合約開(kāi)發(fā)與交互、過(guò)濾器和事件等陈莽。
- php比特幣開(kāi)發(fā)教程渤昌,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念走搁,例如區(qū)塊鏈存儲(chǔ)耘沼、去中心化共識(shí)機(jī)制、密鑰與腳本朱盐、交易與UTXO等,同時(shí)也詳細(xì)講解如何在Php代碼中集成比特幣支持功能菠隆,例如創(chuàng)建地址兵琳、管理錢(qián)包、構(gòu)造裸交易等骇径,是Php工程師不可多得的比特幣開(kāi)發(fā)學(xué)習(xí)課程躯肌。
- EOS入門(mén)教程,本課程幫助你快速入門(mén)EOS區(qū)塊鏈去中心化應(yīng)用的開(kāi)發(fā)破衔,內(nèi)容涵蓋EOS工具鏈清女、賬戶與錢(qián)包、發(fā)行代幣晰筛、智能合約開(kāi)發(fā)與部署嫡丙、使用代碼與智能合約交互等核心知識(shí)點(diǎn),最后綜合運(yùn)用各知識(shí)點(diǎn)完成一個(gè)便簽DApp的開(kāi)發(fā)读第。
匯智網(wǎng)原創(chuàng)翻譯曙博,轉(zhuǎn)載請(qǐng)標(biāo)明出處。這里是原文