实战-动手开发第一个区块链投票DAPP

第一个区块链投票DAPP

前言

  • 我是一个菜鸟,所以在代码质量上可能不太好,欢迎指点。
  • 阅读本文可能需要一定的基础,有疑问欢迎留言。

本篇文章,将带读者用Truffle框架在ganache环境上


ganache

搭建一个属于自己的投票DAPP雏形,你可以在这基础上进行扩展。这里如果你对ganache不熟悉的可以使用testrpc环境也是一样的。

开发包对应版本

  • web3.js v0.20.5
  • Truffle v4.1.3
  • Solidity v0.4.2
  • Ganache 1.1.0

初始化工程

这里我们需要提前安装好 nodeTruffle,如果不会的,可自行翻阅文档,网上很多资料。

  • 新建一个文件夹
  • 在新建的文件下执行下面的命令
truffle unbox pet-shop

这个命令是通过 truffleunbox 工具初始化一个宠物DAPP,我们这里偷个懒,在官方的 pet-shop 项目上进行修改。

编写合约

contracts文件夹下面新建一个 Election.sol 的合约文件,在这里我们进行合约编写。

合约内容如下:

pragma solidity ^0.4.2;

contract Election {
    
    //结构体
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    //事件
    event votedEvent(
        uint indexed _candidateId
    );

    //存储结构体
    mapping (uint => Candidate) public candidates;
    //是否已经投票了
    mapping (address=>bool) public voters;

    //总数量
    uint public candidateCount;

    //构造函数
    function Election () public {
        addCandidate("张三");
        addCandidate("李四");
    }

    //添加候选人
    function addCandidate(string _name) private {
        candidateCount ++;
        candidates[candidateCount] = Candidate(candidateCount, _name, 0);
    }

    //投票
    function vote(uint _candidateId) public {

        //过滤
        require(!voters[msg.sender]);
        require(_candidateId > 0 && _candidateId <= candidateCount);

        //记录用户已经投票了
        voters[msg.sender] = true;
        candidates[_candidateId].voteCount ++;

        votedEvent(_candidateId);
    }

}

合约部分不赘述,如果有疑问可以留言。

部署合约

Truffle框架部署合约很简单,可以通过他的 migration 功能直接部署。

  • migrations 文件夹下面,新建一个名为2_deploy_contract.js 的文件。
  • 在里面我们把我们的合约给加到 migration 里面,代码如下:
var Election = artifacts.require("./Election.sol");

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

这里说明下,如果你想偷懒,也可以直接在他下面的1_initial_migration.js文件里面导入Election.sol文件再加到 migration 里面。

  • 在一切准备就绪,只需要在终端上执行下面的命令
truffle migrate --reset

就能部署好了。为啥要加 --reset 如果你的合约第一次部署,可以不加,如果是迭代覆盖部署,就的加这个参数,好从新给你分配一个地址,否者会出现莫名其妙的问题,具体详细介绍,请查阅官方文档。

如果你这里使用的tesrpc环境,服务地址的端口是 8545 你需要去 修改下项目里面的 truffle.js文件里面的配置

编写html页面

由于我们是在 pet-shop 项目上进行修改的,所以我们只需要去修改 src/index.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">
    <title>区块链投票</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">

  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-sm-8 col-sm-push-2">

          <h1 class="text-center">区块链投票</h1>
          <hr/>
          <br/>

          <div id="loader">
            <p class="test-center">Loading...</p>
          </div>

          <div id="content" style="display: none;">
            <table>
                <thead>
                  <tr>
                      <th scope="col">#</th>
                      <th scope="col">Name</th>
                      <th scope="col">Votes</th>
                  </tr>
                </thead>
                <tbody id="candidatesResults">
                </tbody>
            </table>

            <hr/>
           
            <!-- 投票 -->
            <form onsubmit="App.castVote();return false;">
              <div class="form-group">
                <label for="cadidatesSelect">选择你要投的名字:</label>
                <select class="form-control" id="cadidatesSelect">
                </select>
              </div>
              <button type="submit" class="btn btn-primary">投票</button>
            </form>

          </div>
       
          <hr/>
          <p id="accountAddress" class="test-center"></p>

        </div>
      </div>

    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/web3.min.js"></script>
    <script src="js/truffle-contract.js"></script>
    <script src="js/app.js"></script>
  </body>
</html>

<style>
  .test-center{
    text-align: center;
  }
  table{
    width: 100%;
  }
  table tr{
    border-bottom: 2px solid #efefef;
    height: 40px;
  }
</style>

代码很简单,不用解释应该都能看懂哈。

编写js文件

js部分是DAPP比较麻烦的地方,也是最初学者迷惑的地方,我这里先把最终代码粘贴过来再解释:

App = {
  web3Provider: null,
  contracts: {},
  account: '0x0',

  init: function() {
    return App.initWeb3();
  },

  initWeb3: function() {
    if (typeof web3 !== 'undefined') {
      App.web3Provider = web3.currentProvider;
      console.warn("Meata");
    }else{
      App.web3Provider = new Web3.providers.HttpProvider('https://localhost:7545');
    }
    web3 = new Web3(App.web3Provider);

    return App.initContract();
  },

  initContract: function() {
    
    $.getJSON("Election.json",function(election){
      
      App.contracts.Election = TruffleContract(election);
      App.contracts.Election.setProvider(App.web3Provider);

      App.listenForEvents();

      return App.reander();
    })
    
  },

  reander: function(){

    var electionInstance;
    var $loader = $("#loader");
    var $content = $("#content");

    $loader.show();
    $content.hide();

    //获得账号信息
    web3.eth.getCoinbase(function(err,account){
      if(err === null){
        App.account = account;
        $("#accountAddress").html("您当前的账号: " + account);
      }
    });

    //加载数据
    App.contracts.Election.deployed().then(function(instance){
      electionInstance = instance;
      return electionInstance.candidateCount();
    }).then(function(candidatesCount){
      var $candidatesResults = $("#candidatesResults");
      $candidatesResults.empty();

      var $cadidatesSelect = $("#cadidatesSelect");
      $cadidatesSelect.empty();

      for (var i=1;i<=candidatesCount;i++){
        electionInstance.candidates(i).then(function(candidate){
          var id = candidate[0];
          var name = candidate[1];
          var voteCount = candidate[2];

          var candidateTemplate = "<tr><th>"+id+"</th><td>"+name+"</td><td>"+voteCount+"</td></tr>";
          $candidatesResults.append(candidateTemplate);

          //投票
          var cadidateOption = "<option value='"+id+"'>"+name+"</option>";
          $cadidatesSelect.append(cadidateOption);

        });
      }

      return electionInstance.voters(App.account);

    }).then(function(hasVoted){

      if(hasVoted){
        $('form').hide();
      }
      $loader.hide();
      $content.show();

    }).catch(function(err){
      console.warn(err);
    });

  },

  //投票
  castVote: function(){

    var $loader = $("#loader");
    var $content = $("#content");

    var candidateId = $('#cadidatesSelect').val();

    App.contracts.Election.deployed().then(function(instance){
      return instance.vote(candidateId,{from: App.account});
    }).then(function(result){
      $content.hide();
      $loader.show();
    }).catch(function(err){
      console.warn(err);
    });

  },

  //监听事件
  listenForEvents: function(){
    App.contracts.Election.deployed().then(function(instance){
      instance.votedEvent({},{
       formBlock:0,
       toBlock: 'latest'
      }).watch(function(error,event){
        console.log("event triggered",event);
        App.reander();
      });
    })
  }

};

$(function() {
  $(window).load(function() {
    App.init();
  });
});

  • initWeb3方法里面主要是对web3.js进行初始化,应该都能看懂。
  • initContract方法中
    • getJSON 方法是从本地读取json文件,在json文件读取成功后,再调用 TruffleTruffleContract 方法进行合约初始化。
    • 初始化合约后,通过 setProvider 方法我这里理解是设置代理。

其他的都是调取的web3.js提供的api,除了api之外我觉得最有必要解释的是 App.contracts.Election.deployed().then(function(instance)... 这一串代码,这是实例化Election合约后会调取后面then 里面的方法同时,把实例化的变量通过 instance 带入到方法的参数里面。

同时在then里面有返回了一个方法 return instance.vote(candidateId,{from: App.account}); 这个方法又会执行,执行完后,又把执行的结果待会给下一个 then ,依次类推,这貌似是es6的链式语法。

如果我解释得不太明白,可以留言。

运行起来

上面的代码啥的一切准备就绪,现在只需要执行

npm run dev

项目就启动了,由于需要和testrpc或者Ganache交互,所有我们需要用到 MetaMask 插件,所以得要用谷歌浏览器,打开我们的项目,同时需要MetaMask 插件连接到我们的测试环境。


就能看到出来的效果了。

如果对MetaMask不了解的,可以在网上查阅相关资料。这里我的理解是,在DAPP中MetaMask充当的是一个桥梁作用,当我我们需要用到签名时,他会出现一个签名的界面让你确认,如下图:

其实就是一个轻钱包。

[获取授权]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容