以太坊区块链开发以太坊区块链研习社

以太坊Dapp终极教程——如何构建一个完整的全栈去中心化应用(二

2019-05-31  本文已影响2人  编程狂魔

以太坊Dapp终极教程——如何构建一个完整的全栈去中心化应用(一)中,我们已经完成了一切所需的设置,让我们通过列出将在选举中运行的候选人来继续构建智能联系。我们需要一种方法来存储多个候选者,并存储关于每个候选者的多个属性。我们希望跟踪候选人的身份,姓名和投票计数。以下是我们如何为候选人建模:

contract Election {
    // Model a Candidate
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    // ...
}

我们使用Solidity Struct为候选人建模。Solidity允许我们创建自己的结构类型,就像我们在这里为候选人所做的那样。我们指定此结构具有无符号整数类型的id,字符串类型的名称和无符号整数类型的voteCount。简单地声明这个结构实际上不会给我们一个候选人。我们需要实例化它并将其分配给变量,然后才能将其写入存储。

接下来我们需要的是存放候选人的地方。我们需要一个地方来存储我们刚刚创建的结构类型之一。我们可以使用Solidity mapping来完成此操作。Solidity中的映射类似于关联数组或散列,它将键值对关联起来。我们可以像这样创建这个映射:

contract Election {
    // Model a Candidate
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    // Read/write Candidates
    mapping(uint => Candidate) public candidates;

    // ...
}

在这种情况下,映射的关键是无符号整数,值是我们刚刚定义的候选结构类型。这基本上为我们提供了基于身份的每个候选人的查找。由于此映射已分配给状态变量,因此只要我们为其分配新的键值对,我们就会将数据写入区块链。接下来,我们将此映射的可见性设置为public以获取getter函数,就像我们在冒烟测试中使用候选名称一样。

接下来,我们使用计数器缓存状态变量跟踪选举中存在多少候选者,如下所示:

contract Election {
    // Model a Candidate
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    // Read/write Candidates
    mapping(uint => Candidate) public candidates;

    // Store Candidates Count
    uint public candidatesCount;

    // ...
}

在Solidity中,无法确定映射的大小,也无法迭代它。这是因为尚未赋值的映射中的任何键都将返回默认值(在这种情况下为空候选)。例如,如果我们在这次选举中只有2名候选人,并且我们尝试查找候选人#99,那么映射将返回空的候选人结构。此行为使得无法知道存在多少候选项,因此我们必须使用计数器缓存。

接下来,让我们创建一个函数来将候选添加到我们创建的映射中,如下所示:

contract Election {
    // ...

    function addCandidate (string _name) private {
        candidatesCount ++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }
}

我们已经声明了函数addCandidate,它接受一个表示候选者名称的字符串类型参数。在函数内部,我们递增候选计数器缓存以表示已添加新候选者。然后我们使用当前候选计数作为关键字,用新的Candidate结构更新映射。使用当前候选计数中的候选ID,函数参数中的名称以及初始投票计数来初始化此Candidate结构。请注意,此函数的可见性是私有的,因为我们只想在合约中调用它。

现在我们可以通过在构造函数中调用两次addCandidate函数来添加两个候选者,如下所示:

contract Election {
    // ...

    function Election () public {
        addCandidate("Candidate 1");
        addCandidate("Candidate 2");
    }

    // ...
}

当我们将合约部署到区块链时,将执行此迁移,并使用两个候选人填充我们的选举。此时,你的完整合约代码应如下所示:

pragma solidity ^0.4.2;

contract Election {
    // Model a Candidate
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    // Read/write candidates
    mapping(uint => Candidate) public candidates;

    // Store Candidates Count
    uint public candidatesCount;

    function Election () public {
        addCandidate("Candidate 1");
        addCandidate("Candidate 2");
    }

    function addCandidate (string _name) private {
        candidatesCount ++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }

}

现在让我们像这样迁移我们的合约:

$ truffle migrate --reset

现在尝试与控制台内的候选人进行交互。

现在让我们编写一些测试来确保我们的智能合约正确初始化。首先,让我解释为什么在开发智能合约时测试非常重要。我们希望确保合约没有错误,原因如下:

测试

现在让我们写一些测试。确保你先运行Ganache。然后,从项目的根目录在命令行中创建一个新的测试文件,如下所示:

$ touch test/election.js

我们将使用Mocha测试框架和Chai断言库在此文件中的Javascript中编写所有测试。这些与Truffle框架捆绑在一起。我们将在Javascript中编写所有这些测试,以模拟与智能合约的客户端交互,就像我们在控制台中所做的那样。以下是测试的所有代码:

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

contract("Election", function(accounts) {
  var electionInstance;

  it("initializes with two candidates", function() {
    return Election.deployed().then(function(instance) {
      return instance.candidatesCount();
    }).then(function(count) {
      assert.equal(count, 2);
    });
  });

  it("it initializes the candidates with the correct values", function() {
    return Election.deployed().then(function(instance) {
      electionInstance = instance;
      return electionInstance.candidates(1);
    }).then(function(candidate) {
      assert.equal(candidate[0], 1, "contains the correct id");
      assert.equal(candidate[1], "Candidate 1", "contains the correct name");
      assert.equal(candidate[2], 0, "contains the correct votes count");
      return electionInstance.candidates(2);
    }).then(function(candidate) {
      assert.equal(candidate[0], 2, "contains the correct id");
      assert.equal(candidate[1], "Candidate 2", "contains the correct name");
      assert.equal(candidate[2], 0, "contains the correct votes count");
    });
  });
});

让我解释一下这段代码。首先,我们要求合约并将其分配给变量,就像我们在迁移文件中所做的那样。接下来,我们调用合约函数contract,并在回调函数中编写所有测试。此回调函数提供了一个帐户Accounts变量,表示我们的区块链上的所有帐户,由Ganache提供。

第一次测试通过检查候选人数等于2来检查合约是否已使用正确数量的候选人进行初始化。

下一个测试检查每个候选人在选举中的价值,确保每个候选人都有正确的身份证,姓名和投票计数。

现在让我们从命令行运行测试,如下所示:

$ truffle test

是的,他们通过了!

客户端应用程序

现在让我们开始构建将与智能合约对话的客户端应用程序。我们将通过修改我们在上一节中安装的Truffle Pet Shop框附带的HTML和Javascript文件来完成此操作。我们将使用此现有代码开始。我们还要注意Truffle Pet Shop盒子附带的一些其他东西,比如Bootstrap框架,它将使我们不必在本教程中编写任何CSS。我们还有lite-server,它将为我们的资产提供服务以用于开发目的。

你不必是前端专家就可以按照本教程的这一部分进行操作。我故意保持HTML和Javascript代码非常简单,我们不会花太多时间专注于它。我想继续专注于开发dApp的智能合约部分!

继续使用以下代码替换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>Election Results</title>

    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container" style="width: 650px;">
      <div class="row">
        <div class="col-lg-12">
          <h1 class="text-center">Election Results</h1>
          <hr/>
          <br/>
          <div id="loader">
            <p class="text-center">Loading...</p>
          </div>
          <div id="content" style="display: none;">
            <table class="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/>
            <p id="accountAddress" class="text-center"></p>
          </div>
        </div>
      </div>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <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>

接下来,使用以下代码替换app.js文件的所有内容:

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

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

  initWeb3: function() {
    if (typeof web3 !== 'undefined') {
      // If a web3 instance is already provided by Meta Mask.
      App.web3Provider = web3.currentProvider;
      web3 = new Web3(web3.currentProvider);
    } else {
      // Specify default instance if no web3 instance provided
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
      web3 = new Web3(App.web3Provider);
    }
    return App.initContract();
  },

  initContract: function() {
    $.getJSON("Election.json", function(election) {
      // Instantiate a new truffle contract from the artifact
      App.contracts.Election = TruffleContract(election);
      // Connect provider to interact with contract
      App.contracts.Election.setProvider(App.web3Provider);

      return App.render();
    });
  },

  render: function() {
    var electionInstance;
    var loader = $("#loader");
    var content = $("#content");

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

    // Load account data
    web3.eth.getCoinbase(function(err, account) {
      if (err === null) {
        App.account = account;
        $("#accountAddress").html("Your Account: " + account);
      }
    });

    // Load contract data
    App.contracts.Election.deployed().then(function(instance) {
      electionInstance = instance;
      return electionInstance.candidatesCount();
    }).then(function(candidatesCount) {
      var candidatesResults = $("#candidatesResults");
      candidatesResults.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];

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

      loader.hide();
      content.show();
    }).catch(function(error) {
      console.warn(error);
    });
  }
};

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

让我们注意一下这段代码所做的一些事情:

现在让我们在浏览器中查看客户端应用程序。首先,确保你已按照以下方式迁移合约:

$ truffle migrate --reset

接下来,从命令行启动你的开发服务器,如下所示:

$ npm run dev

这应该会自动使用客户端应用程序打开一个新的浏览器窗口。

[图片上传失败...(image-81a2d0-1559275088329)]

请注意你的应用程序显示正在加载loading...。那是因为我们还没有登录到区块链!为了连接到区块链,我们需要将其中一个帐户从Ganache导入Metamask。

与Metamask连接后,你应该会看到所有合约和帐户数据都已加载。

[图片上传失败...(image-fed964-1559275088329)]

======================================================================

分享一些比特币、以太坊、EOS、Fabric等区块链相关的交互式在线编程实战教程:

  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • c#比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在C#代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是C#工程师不可多得的比特币开发学习课程。
  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • ERC721以太坊通证实战,课程以一个数字艺术品创作与分享DApp的实战开发为主线,深入讲解以太坊非同质化通证的概念、标准与开发方案。内容包含ERC-721标准的自主实现,讲解OpenZeppelin合约代码库二次开发,实战项目采用Truffle,IPFS,实现了通证以及去中心化的通证交易所。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • 深入浅出玩转EOS钱包开发,本课程以手机EOS钱包的完整开发过程为主线,深入学习EOS区块链应用开发,课程内容即涵盖账户、计算资源、智能合约、动作与交易等EOS区块链的核心概念,同时也讲解如何使用eosjs和eosjs-ecc开发包访问EOS区块链,以及如何在React前端应用中集成对EOS区块链的支持。课程内容深入浅出,非常适合前端工程师深入学习EOS区块链应用开发。
  • Hyperledger Fabric 区块链开发详解,本课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、nodejs链码与应用开发的操作实践,是Nodejs工程师学习Fabric区块链开发的最佳选择。
  • Hyperledger Fabric java 区块链开发详解,课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、java链码与应用开发的操作实践,是java工程师学习Fabric区块链开发的最佳选择。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是以太坊Dapp终极教程——如何构建一个完整的全栈去中心化应用

上一篇下一篇

猜你喜欢

热点阅读