這是一個非常簡單的應用程序,只需要初始化一組參賽者,任何人都可以給候選人投票,並顯示每位候選人收到的總票數。
我們的目標不是只編寫一個應用程序,而是學習編譯,部署和與之交互的過程。
我故意避免使用任何dapp框架來構建這個應用程序,因為框架抽象掉了很多細節,不利於理解系統的內部結構。不過當你使用框架時,你將更加感激框架為您所做的所有繁重工作!
這個練習的目標是:
- 建立開發環境
- 了解編寫合同,編譯並將其部署到開發環境中的過程。
- 通過nodejs控制台與區塊鏈上的合約進行交互。
- 通過一個簡單的網頁與合約進行交互。
系統示意圖如下:
1.搭建開發環境
我們將使用內存區塊鏈(將其視為區塊鏈模擬器)——ganache,而不是真的區塊鏈開發應用程序。在Part2,我們將使用真的區塊鏈。
以下是在Linux操作系統上安裝ganache,web3js和啟動測試區塊鏈的步驟。
czk@czk-vm:~$ npm -v 6.1.0 czk@czk-vm:~$ node -v v8.11.2 czk@czk-vm:~$ sudo apt-get install build-essential python czk@czk-vm:~$ cd work/ czk@czk-vm:~/work$ mkdir hello_world_voting czk@czk-vm:~/work$ cd hello_world_voting czk@czk-vm:~/work/hello_world_voting$ mkdir node_modules czk@czk-vm:~/work/hello_world_voting$ npm install ganache-cli czk@czk-vm:~/work/hello_world_voting$ npm install web3@0.20.6
czk@czk-vm:~/work/hello_world_voting$ node_modules/.bin/ganache-cli
ganache-cli會創建10個測試帳戶,這些帳戶預村了100個(假的)Eth。
(以上為局部安裝,關於npm 與node_modules, 見https://www.runoob.com/nodejs/nodejs-npm.html)
2. 簡單的投票合約
我們使用Solidity語言來編寫智能合約。我們將編寫2種方法,一種是返回候選人收到的總票數,另一種方法是增加候選人的投票數。
注意:當您將合約部署到區塊鏈時,構造函數只會被調用一次。與在Web中,新部署代碼會覆蓋舊代碼不同,區塊鏈中部署的代碼是不可修改的。
如果您更新合約並再次進行部署,舊合約仍會保留在區塊鏈中,並保留其中的所有數據,新的部署將創建一個新的合約實例。
以下是投票合約代碼:
1 pragma solidity^0.4.18; 2 // We have to specify what version of compiler this code will compile with 3 4 contract Voting { 5 mapping(bytes32 => uint8) public votesReceived; 6 7 /* Solidity doesn't let you pass in an array of strings in the constructor (yet). 8 We will use an array of bytes32 instead to store the list of candidates 9 */ 10 bytes32[] public candidateList; 11 12 /* This is the constructor which will be called once when you 13 deploy the contract to the blockchain. When we deploy the contract, 14 we will pass an array of candidates who will be contesting in the election 15 */ 16 constructor (bytes32[] candidateNames) public { 17 candidateList = candidateNames; 18 } 19 20 // This function returns the total votes a candidate has received so far 21 function candidateVotes(bytes32 candidate) public view returns(uint8) { 22 require(_candidateValidation(candidate), "Invalid candidate."); 23 return votesReceived[candidate]; 24 } 25 26 // This function increments the vote count for the specified candidate. This 27 // is equivalent to casting a vote 28 function VoteToCandidate(bytes32 candidate) public { 29 require(_candidateValidation(candidate), "Invalid candidate."); 30 votesReceived[candidate]++; 31 } 32 33 // This function check the validity of input candidate. 34 function _candidateValidation(bytes32 candidate) private view returns(bool) { 35 for(uint i = 0; i < candidateList.length; ++i) { 36 if(candidateList[i] == candidate) 37 return true; 38 } 39 return false; 40 } 41 }
將以上代碼復制到Voting.sol文件中,並將該文件置於hello_world_voting目錄下。現在讓我們編譯代碼並將其部署到ganache區塊鏈。
為了編譯Solidity代碼,需要安裝npm模塊solc:
czk@czk-vm:~/work/hello_world_voting$ npm install solc + solc@0.4.24
我們將在節點控制台內使用這個庫來編譯我們的合同。
web3js是一個庫,可讓您通過RPC與區塊鏈進行交互。我們將使用該庫來部署我們的應用程序並與其進行交互。
首先,在終端中運行'node'命令以進入節點控制台並初始化solc和web3對象。下面的所有代碼片段都需要在節點控制台中輸入。
czk@czk-vm:~/work/hello_world_voting$ node > Web3 = require('web3') > web3 = new Web3(new Web3.providers.HttpProvider("http://localhost::8545"));
為了確保web3對象被初始化並且可以與區塊鏈進行通信,我們來查詢區塊鏈中的所有帳戶。你應該看到如下結果:
將Voting.sol中的代碼加載到字符串變量中並進行編譯。
> code = fs.readFileSync('Voting.sol').toString() > solc = require('solc') > compiledCode = solc.compile(code)
當成功編譯代碼並打印'contract'對象(只需在node控制台中鍵入compiledCode)時,其中有兩個重要的字段:
1. compiledCode.contracts[‘:Voting’].bytecode: 這是在編譯Voting.sol中的源代碼時獲得的字節碼。這是會被部署到區塊鏈的代碼。
2. compiledCode.contracts[‘:Voting’].interface: 這是合約的接口或模板(稱為abi, Application Binary Interface),它告訴合約用戶合約中有哪些方法可用。無論何時需要與合同進行交互,都需要此abi。
現在我們來部署合約。您首先創建一個合約對象(下面的VotingContract),用於在區塊鏈中部署和啟動合約。
> abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface) > VotingContract = web3.eth.contract(abiDefinition) > byteCode = compiledCode.contracts[':Voting'].bytecode > deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000}) > deployedContract.address > contractInstance = VotingContract.at(deployedContract.address)
上面的VotingContract.new將合約部署到區塊鏈。我們現在已經部署了合約,並且有一個合約實例(上面的可變contractInstance),我們可以使用它來與合約進行交互。在區塊鏈上部署了數十萬個合約。那么,你如何在區塊鏈中識別你的合約呢?答案:deployedContract.address。
當與的合約進行交互時,需要合約部署地址和合約的abi。
3. 在nodejs控制台中與合約進行交互
4. 通過網頁與區塊鏈上的合約交互
現在大部分工作都已完成,現在我們所要做的就是創建一個帶候選人名字的簡單html文件,並在js文件中調用投票命令(我們已經在nodejs控制台中測試過)。 以下是index.html文件和index.js代碼,將它們都放在hello_world_voting目錄中,然后在瀏覽器中打開index.html。
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Hello World DApp</title>
5 <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
6 <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
7 </head>
8 <body class="container">
9 <h1>A Simple Hello World Voting Application</h1>
10 <div class="table-responsive">
11 <table class="table table-bordered">
12 <thead>
13 <tr>
14 <th>Candidate</th>
15 <th>Votes</th>
16 </tr>
17 </thead>
18 <tbody>
19 <tr>
20 <td>Rama</td>
21 <td id="candidate-1"></td>
22 </tr>
23 <tr>
24 <td>Nick</td>
25 <td id="candidate-2"></td>
26 </tr>
27 <tr>
28 <td>Jose</td>
29 <td id="candidate-3"></td>
30 </tr>
31 </tbody>
32 </table>
33 </div>
34 <input type="text" id="candidate" />
35 <a href="#" onclick="VoteToCandidate()" class="btn btn-primary">Vote</a>
36 </body>
37 <script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
38 <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
39 <script src="./index.js"></script>
40 </html>
注:以上代碼運行時,可能會出現無法連接到網站錯誤。在虛擬機中無法打開這個網頁,但是在windows中瀏覽器中可以打開。。。。將該網頁中的js代碼保存為web3.js文件,放入工作index.html同一目錄,將37行改為 如下代碼即可。
<script src="./web3.js"></script>
index.js:
1 web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 2 abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"candidateVotes","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"VoteToCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]') 3 VotingContract = web3.eth.contract(abi); 4 // In your nodejs console, execute contractInstance.address to get the address at which the contract is deployed and change the line below to use your deployed address
5 contractInstance = VotingContract.at('0x5367391364f427209e5f08acd260f21abb15ba30'); 6 candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"} 7
8 function VoteToCandidate() { 9 candidateName = $("#candidate").val(); 10 contractInstance.VoteToCandidate(candidateName, {from: web3.eth.accounts[0]}, function() { 11 let div_id = candidates[candidateName]; 12 $("#" + div_id).html(contractInstance.candidateVotes.call(candidateName).toString()); 13 }); 14 } 15
16 $(document).ready(function() { 17 candidateNames = Object.keys(candidates); 18 for (var i = 0; i < candidateNames.length; i++) { 19 let name = candidateNames[i]; 20 let val = contractInstance.candidateVotes.call(name).toString() 21 $("#" + candidates[name]).html(val); 22 } 23 });
用瀏覽器打開index.html文件,你將看到一下內容。
如果您可以在文本框中輸入候選人姓名並進行投票,可以看到查看投票計數增加,那么您已經成功創建了第一個應用程序!恭喜!
總結一下,本文設置了開發環境,編寫簡單的合同,在區塊鏈上編譯和部署合同,並通過nodejs控制台和網頁與其交互。
在Part2部分,我們將把這個合同部署到公共測試網絡,以便全世界都可以看到它並投票給候選人。並使用Truffle框架進行開發(而不必使用nodejs控制台來管理整個過程)。