區塊鏈: 從零構建和部署去中心化投票App

You may as well be bold to love someone, to climb a mountain,to chase your dream.
你不妨大膽一些,愛一個人,攀一座山,追一個夢。


安裝所需工具

首先開發機上必須裝好Node.js,再使用以下命令安裝所需的工具

$ npm install -g ethereumjs-testrpc truffle
macdeiMac:~ mac$ npm install -g ethereumjs-testrpc truffle
npm WARN deprecated ethereumjs-testrpc@6.0.3: ethereumjs-testrpc has been renamed to ganache-cli, please use this package from now on.
/usr/local/bin/testrpc -> /usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js
/usr/local/bin/truffle -> /usr/local/lib/node_modules/truffle/build/cli.bundled.js
+ ethereumjs-testrpc@6.0.3
+ truffle@4.1.14
updated 3 packages in 134.279s
macdeiMac:~ mac$ npm ganache-cli install

Usage: npm <command>

where <command> is one of:
    access, adduser, bin, bugs, c, cache, completion, config,
    ddp, dedupe, deprecate, dist-tag, docs, doctor, edit,
    explore, get, help, help-search, i, init, install,
    install-test, it, link, list, ln, login, logout, ls,
    outdated, owner, pack, ping, prefix, profile, prune,
    publish, rb, rebuild, repo, restart, root, run, run-script,
    s, se, search, set, shrinkwrap, star, stars, start, stop, t,
    team, test, token, tst, un, uninstall, unpublish, unstar,
    up, update, v, version, view, whoami

npm <command> -h     quick help on <command>
npm -l           display full usage info
npm help <term>  search for help on <term>
npm help npm     involved overview

Specify configs in the ini-formatted file:
    /Users/mac/.npmrc
or on the command line via: npm <command> --key value
Config info can be viewed via: npm help config

npm@5.6.0 /usr/local/lib/node_modules/npm


   ╭─────────────────────────────────────╮
   │                                     │
   │   Update available 5.6.06.4.1    │
   │     Run npm i -g npm to update      │
   │                                     │
   ╰─────────────────────────────────────╯

創建項目

macdeiMac:~ mac$ cd /Users/mac/Desktop/GitHub/Solidity/learn/Voting 
macdeiMac:Voting mac$ ls
macdeiMac:Voting mac$ pwd
/Users/mac/Desktop/GitHub/Solidity/learn/Voting
macdeiMac:Voting mac$ truffle unbox react-box
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:              truffle compile
  Migrate:              truffle migrate
  Test contracts:       truffle test
  Test dapp:            cd client && npm test
  Run dev server:       cd client && npm run start
  Build for production: cd client && npm run build
macdeiMac:Voting mac$ 

編寫投票Dapp智能合約

contracts文件夾下創建Voting.sol文件,將下面的代碼拷貝到文件中。

pragma solidity ^0.4.24;

contract Voting {


//   ["wt","mayun","liuyifei","zhaoliying","mahuateng"]
//   [1,2,3,4,5]


  // 存儲候選人ID的數組
//   bytes32[] candidateNames =new bytes32[](5);
//   bytes32[] candidateNames = ["wt","mayun","liuyifei","zhaoliying","mahuateng"];
  uint[] candidateIds;
  mapping (uint => uint) public votesReceived;
  // 構造函數 初始化候選人名單
  constructor(uint[] _candidateIds) public {

      candidateIds = _candidateIds;
  }

  // 查詢某個候選人的總票數
  function totalVotesFor(uint candidate) public constant returns (uint) {
    require(validCandidate(candidate) == true);
    // 或者
    // assert(validCandidate(candidate) == true);
    return votesReceived[candidate];
  }

  // 爲某個候選人投票
  function voteForCandidate(uint candidate)public {
    assert(validCandidate(candidate) == true);
    votesReceived[candidate] += 1;
  }

  // 檢索投票的ID是不是候選人的ID
  function validCandidate(uint candidate) public constant returns (bool) {
    for(uint i = 0; i < candidateIds.length; i++) {
      if (candidateIds[i] == candidate) {
        return true;
      }
    }
    return false;
  }
}

修改migrations/2_deploy_contracts.js

var Voting = artifacts.require("./Voting.sol");

module.exports = function(deployer) {
  deployer.deploy(Voting);
};

通過remix + metamask部署合約到Kovan Test Net

  • Google瀏覽器MetaMask插件安裝前面的文章中有

這裏寫圖片描述

這裏寫圖片描述

  • 確保MetaMask賬號處於等於狀態,並且有一定的以太幣支付給礦工。
  • 確保EnvironmentInjected Web3,如果切換不過來,關掉瀏覽器重新啓動
  • Deploy函數中輸入一個數組,數組裏面的內容爲候選人Id名單
  • 點擊Deploy按鈕,會彈出MetaMask界面讓你確認,確認提交,過一會兒,合約就部署成功
  • 可以測試給某個候選人投票,查詢某個候選人的票數

拷貝合約地址

這裏寫圖片描述

0xc36c23c1e6eb12f8df63c787ef06aecd1f38b778

編譯合約

使用truffle develop

macdeiMac:Voting mac$ truffle develop
Truffle Develop started at http://127.0.0.1:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Private Keys:
(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
(1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
(2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
(3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
(4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
(5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
(6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
(7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
(8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
(9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

⚠️  Important ⚠️  : This mnemonic was created for you by Truffle. It is not secure.
Ensure you do not use it on production blockchains, or else you risk losing funds.

使用truffle compile

macdeiMac:Voting mac$ truffle compile
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/SimpleStorage.sol...
Compiling ./contracts/Voting.sol...
Writing artifacts to ./build/contracts

編譯完合約以後在build/contracts文件夾下面會有一個Voting.jsonabi文件。

這個文件是編譯後的abi文件,待會兒需要將這個文件的json導入到App.json中。

查看client/src/utils/getWeb3.js

import Web3 from 'web3'

let getWeb3 = new Promise(function(resolve, reject) {
  // Wait for loading completion to avoid race conditions with web3 injection timing.
  window.addEventListener('load', function() {
    var results
    var web3 = window.web3

    // Checking if Web3 has been injected by the browser (Mist/MetaMask)
    if (typeof web3 !== 'undefined') {
      // Use Mist/MetaMask's provider.
      web3 = new Web3(web3.currentProvider)

      results = {
        web3: web3
      }

      console.log('Injected web3 detected.');

      resolve(results)
    } else {
      // Fallback to localhost if no web3 injection.
      var provider = new Web3.providers.HttpProvider('http://localhost:8545')

      web3 = new Web3(provider)

      results = {
        web3: web3
      }

      console.log('No web3 instance injected, using Local web3.');

      resolve(results)
    }
  })
})

export default getWeb3

這個文件主要是封裝了一個getWeb3promiss供我們直接使用,可以從getWeb3直接獲取到web3對象供App.js文件中使用。
(後期版本不同,可能導致使用方式不同)

修改app.js前端代碼和合約進

import React, { Component } from "react";
import VotingContract from "./contracts/Voting.json";
import getWeb3 from "./utils/getWeb3";
// import truffleContract from "truffle-contract";

import "./App.css";

//合約地址0x81ae2ffc4119090f2d320f19ae9a3726c8a80cae
const contractAddress = "0x81ae2ffc4119090f2d320f19ae9a3726c8a80cae";
//合約實例
var votingContractInstance;
var account;

var _modifyVotingCount = (candidates,i,votingCount) => {

    console.log("---------");
    console.log(candidates);
    console.log(i);
    console.log(votingCount);

    let obj = candidates[i];
    obj.votingCount = votingCount;
    return candidates;
}

class App extends Component {
  state = { storageValue: 0, web3: null, accounts: null, contract: null };

  constructor(props) {
      super(props)
       // ["1","2","3","4","5"]
      this.state = {
        candidates: [
                      {
                        "name": "Rama",
                        "id": 1,
                        "votingCount": 0
                      },
                      {
                        "name": "Nick",
                        "id": 2,
                        "votingCount": 0
                      },
                      {
                        "name": "Jose",
                        "id": 3,
                        "votingCount": 0
                      },
                      {
                        "name": "haha",
                        "id": 4,
                        "votingCount": 0
                      },
                      {
                        "name": "hehe",
                        "id": 5,
                        "votingCount": 0
                      }
                    ],
        candidatesVoteCount: ["0","0","0","0","0"],
        web3: null
      }
    }

    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() {

      const contract = require('truffle-contract')
      const votingContract = contract(VotingContract)
      votingContract.setProvider(this.state.web3.currentProvider)


      // Get accounts.
      this.state.web3.eth.getAccounts((error, accounts) => {
        console.log(accounts);
        votingContract.at(contractAddress).then((instance) => {
          account = accounts[0];
          votingContractInstance = instance; //部署完的合約實例
          console.log(votingContractInstance);//合約實例對象

          for (var i = 0; i < this.state.candidates.length; i++) {
              console.log(this.state.candidates[i]);//合約實例對象
            votingContractInstance.totalVotesFor(this.state.candidates[i].id,{from:accounts[0]})
            .then((result) =>{
              console.log(result);
            })
          }
          return;
        })
      })
    }





    //   componentDidMount = async () => {
    //     try {
    //       // Get network provider and web3 instance.
    //       //獲取網絡提供商和web3實例。
    //       const web3 = await getWeb3();
    //       this.setState({
    //           web3: web3
    //       })
    //       // Use web3 to get the user's accounts.
    //       //使用web3獲取用戶的帳戶。
    //       const accounts = await web3.eth.getAccounts();
    //
    //       // Get the contract instance.
    //       //獲取合同實例。
    //       const Contract = truffleContract(VotingContract);
    //       Contract.setProvider(web3.currentProvider);
    //       const instance = await Contract.deployed();
    //
    //       // Set web3, accounts, and contract to the state, and then proceed with an
    //       // example of interacting with the contract's methods.
    //       // 將web3,帳戶和合同設置爲州,然後繼續
    //       //與合同方法交互的例子。
    //       this.setState({
    //         web3,
    //         accounts,
    //         contract: instance
    //       }, this.runExample);
    //       this.runExample();
    //       console.log(this.state);
    //     } catch (error) {
    //       // Catch any errors for any of the above operations.
    //       alert(
    //         `Failed to load web3, accounts, or contract. Check console for details.`
    //       );
    //       console.log(error);
    //     }
    //   };
    // runExample = async () => {
    //   const { accounts, contract } = this.state;
    //
    //   //存儲給定值,默認爲5//   await contract.set(5, { from: accounts[0] });
    //
    //   // Get the value from the contract to prove it worked.
    //   //從合同中獲取價值以證明其有效。
    //   const response = await contract.get();
    //
    //   // Update state with the result.
    //   //使用結果更新狀態。
    //   this.setState({ storageValue: response.toNumber() });
    // };

  render() {
    return (
      <div className="App">
         <ul>
         {
           this.state.candidates.map((person) =>{
              return (
                <li key={person.id}>候選人:{person.name} 獲得{person.votingCount}票</li>
              )
           })
         }
         </ul>
      </div>
    );
  }


}

export default App;

使用命令cd client && npm run start

macdeiMac:client mac$ npm run start

> [email protected] start /Users/mac/Desktop/GitHub/Solidity/learn/Voting/client
> react-scripts start
> 

這裏寫圖片描述

通過Remix對Id爲2的人進行投票

這裏寫圖片描述

submit
這裏寫圖片描述

消息提醒
這裏寫圖片描述

查詢Id爲2的票數

這裏寫圖片描述

網頁查看

這裏寫圖片描述

全部代碼

import React, { Component } from "react";
import VotingContract from "./contracts/Voting.json";
import getWeb3 from "./utils/getWeb3";
// import truffleContract from "truffle-contract";

import "./App.css";

//合約地址0x81ae2ffc4119090f2d320f19ae9a3726c8a80cae
const contractAddress = "0x81ae2ffc4119090f2d320f19ae9a3726c8a80cae";
//合約實例
var votingContractInstance;
var account;

var _modifyVotingCount = (candidates, i, votingCount) => {

    let obj = candidates[i];
    obj.votingCount = votingCount;
    return candidates;
}

class App extends Component {
  state = { storageValue: 0, web3: null, accounts: null, contract: null };

  constructor(props) {
      super(props)

      this.state = {
        candidates: [
                      {
                        "name": "Rama",
                        "id": 1,
                        "votingCount": 0
                      },
                      {
                        "name": "Nick",
                        "id": 2,
                        "votingCount": 0
                      },
                      {
                        "name": "Jose",
                        "id": 3,
                        "votingCount": 0
                      },
                      {
                        "name": "haha",
                        "id": 4,
                        "votingCount": 0
                      },
                      {
                        "name": "hehe",
                        "id": 5,
                        "votingCount": 0
                      }
                    ],
        candidatesVoteCount: ["0","0","0","0","0"],
        web3: null
      }
    }

    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() {

      const contract = require('truffle-contract')
      const votingContract = contract(VotingContract)
      votingContract.setProvider(this.state.web3.currentProvider)


      // Get accounts.
      this.state.web3.eth.getAccounts((error, accounts) => {
        console.log(accounts);
        votingContract.at(contractAddress).then((instance) => {
          account = accounts[0];
          votingContractInstance = instance; //部署完的合約實例
          console.log(votingContractInstance);//合約實例對象

          for (let i = 0; i < this.state.candidates.length; i++) {
              console.log(this.state.candidates[i]);//合約實例對象
            votingContractInstance.totalVotesFor(this.state.candidates[i].id,{from:accounts[0]})
            .then((result) =>{
              console.log(i);
              this.setState({
                candidates:_modifyVotingCount(this.state.candidates,i,result.words[0])
              })
            })
          }
          return;
        })
      })
    }

  render() {
    return (
      <div className="App">
         <ul>
         {
           this.state.candidates.map((person) =>{
              return (
                <li key={person.id}>候選人:{person.name} 候選人Id:{person.id} 獲得{person.votingCount}票</li>
              )
           })
         }
         </ul>

         <input
           ref="votingInput" placeholder="請輸入候選人Id..."
         style={{width:200,height:40,borderWidth:2,marginLeft:30}}></input>
         <button onClick={() =>{
           let candidateId = this.refs.votingInput.value;
           console.log(this.state.web3.eth.accounts[0]);
           votingContractInstance.voteForCandidate(candidateId,{from:account})
           .then((result => {
             console.log(result);
             for (let i = 0; i < this.state.candidates.length; i++) {
                 console.log(this.state.candidates[i]);//合約實例對象
               votingContractInstance.totalVotesFor(this.state.candidates[i].id,{from:account})
               .then((result) =>{
                 console.log(i);
                 this.setState({
                   candidates:_modifyVotingCount(this.state.candidates,i,result.words[0])
                 })
               })
             }
           }))
         }}>Voting</button>
      </div>
    );
  }


}

export default App;

這裏寫圖片描述

gitHub參考鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章