精华内容
下载资源
问答
  • 以太坊DAPP开发实例: 全栈投票系统

    千次阅读 2018-04-11 11:17:47
    原文地址: Full Stack Hello World Voting Ethereum Dapp Tutorial — Part 1[教程最近更新于2018年1月]在我以前的文章中,我解释了以太坊平台和web 应用的差别。作为一个开发者,学习任何新技术的最好方法是潜入...

    原文地址: Full Stack Hello World Voting Ethereum Dapp Tutorial — Part 1

    [教程最近更新于2018年1月]


    在我以前的文章中,我解释了以太坊平台和web 应用的差别。作为一个开发者,学习任何新技术的最好方法是潜入和尝试构建应用。在这篇文章中,让我们构建一个简单的"Hello World!" 应用程序, 这是一个投票应用程序。

    该应用程序非常简单,它所做的只是初始化一组候选人,让任何人投票给候选人,并显示每个候选人收到的总票数。我们的目标不是简单地编写应用程序,而是学习编译、部署和交互的过程。

    我有意避免使用任何DAPP框架构建这个应用程序,因为框架抽象掉很多细节,你不了解系统的内部。此外,当您使用框架时,您将对框架为您所做的所有繁重工作有更多的感激!

    在许多方面,这篇文章是上一篇文章的续篇。如果你是刚接触以太坊,那我推荐你先阅读以下我的上一篇文章。

    这个练习的目的是:
            建立开发环境。
            学习编写智能合约、编译和将其部署到开发环境中的过程。
            与通过Nodejs控制台与blockchain上的智能合约交互

            通过一个简单的网页与智能合约交互,通过页面进行投票,显示候选人的票数

    整个应用程序运行在Ubuntu 16.04 xenial。macOS上我也测试过了。

    下面的视图将是我们要构建的应用


    1. 设置开发环境
         我们使用一个模拟的内存区块链(ganache)代替真实的区块链在进行开发。在本教程的2章,我们将与真实的区块链交互。下面是安装ganache、web3js的步骤,然后在linux上启动一个测试链。在macOS上安装过程也是一样的。对于Windows,你可以参考

    注意:本教程目前与web3js 0.20.2版本。web3js 1.0稳定版本如果发布,我会更新一下教程.


    你可以看到ganache-cli自动创建了10个测试账号,每个账号预分配了100(虚构的)ethers

    2.简单的投票合约
     我们将使用solidity编程语言来编写我们的合约。如果您熟悉面向对象编程,学习编写solidity合约应该是轻而易举的事。我们将编写一个合约对象,含有一个构造函数初始化候选人数组。合约对象有2个方法:

    1.返回候选人获得的总票数

    2.增加候选人的投票数。

    注意:构造函数只被调用一次,当您部署合约到区块链。不像在网络世界里的每一个部署你的代码覆盖旧的代码,部署后的代码在区块链上是不变的。例如,如果你更新你的合约并且再次部署,旧合约仍然会在区块链上, 它所存储的数据不受影响,新的部署将创建一个新实例的合约。

    下面是投票合约的代码:

    pragma solidity ^0.4.18;
    // We have to specify what version of compiler this code will compile with
    
    contract Voting {
      /* mapping field below is equivalent to an associative array or hash.
      The key of the mapping is candidate name stored as type bytes32 and value is
      an unsigned integer to store the vote count
      */
      
      mapping (bytes32 => uint8) public votesReceived;
      
      /* Solidity doesn't let you pass in an array of strings in the constructor (yet).
      We will use an array of bytes32 instead to store the list of candidates
      */
      
      bytes32[] public candidateList;
    
      /* This is the constructor which will be called once when you
      deploy the contract to the blockchain. When we deploy the contract,
      we will pass an array of candidates who will be contesting in the election
      */
      function Voting(bytes32[] candidateNames) public {
        candidateList = candidateNames;
      }
    
      // This function returns the total votes a candidate has received so far
      function totalVotesFor(bytes32 candidate) view public returns (uint8) {
        require(validCandidate(candidate));
        return votesReceived[candidate];
      }
    
      // This function increments the vote count for the specified candidate. This
      // is equivalent to casting a vote
      function voteForCandidate(bytes32 candidate) public {
        require(validCandidate(candidate));
        votesReceived[candidate] += 1;
      }
    
      function validCandidate(bytes32 candidate) view public returns (bool) {
        for(uint i = 0; i < candidateList.length; i++) {
          if (candidateList[i] == candidate) {
            return true;
          }
        }
        return false;
      }
    }

    Voting.sol hosted with ❤ by GitHub

    复制上面的代码,在hello_world_voting目录下创建一个Voting.sol文件。现在让我们来编译代码并将其部署到ganache的区块链上.

    为了编译solidity代码,我们需要安装名字为solc的npm模块

    mahesh@projectblockchain:~/hello_world_voting$ npm install solc

    我们将在node控制台中使用这个库来编译我们的合约。在上一篇文章中我们提到,web3js是一个让我们可以通过rpc访问区块链的库。我们将使用该库来部署我们的应用程序并与之交互。

    首先,在命令行中断运行`node`命令进入node控制台,初始化solc和文本对象。下面的所有代码片段都需要在mode控制台中键入

    mahesh@projectblockchain:~/hello_world_voting$ node
    > Web3 = require('web3')
    > web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

    为了确保web3对象已经初始化、区块链能够访问,让我们试一下查询区块链上的所有账户。您应该看到如下的结果:

    > web3.eth.accounts
    ['0x9c02f5c68e02390a3ab81f63341edc1ba5dbb39e',
    '0x7d920be073e92a590dc47e4ccea2f28db3f218cc',
    '0xf8a9c7c65c4d1c0c21b06c06ee5da80bd8f074a9',
    '0x9d8ee8c3d4f8b1e08803da274bdaff80c2204fc6',
    '0x26bb5d139aa7bdb1380af0e1e8f98147ef4c406a',
    '0x622e557aad13c36459fac83240f25ae91882127c',
    '0xbf8b1630d5640e272f33653e83092ce33d302fd2',
    '0xe37a3157cb3081ea7a96ba9f9e942c72cf7ad87b',
    '0x175dae81345f36775db285d368f0b1d49f61b2f8',
    '0xc26bda5f3370bdd46e7c84bdb909aead4d8f35f3']
    
    

    从voting.sol加载代码,保存在一个字符串变量中,然后开始编译

    > code = fs.readFileSync('Voting.sol').toString()
    > solc = require('solc')
    > compiledCode = solc.compile(code)
    
    
     当你的代码编译成功并打印了合约对象的内容(在node控制台中输出的内容),有2个字段很重要,需要理解它们:
    compiledCode.contracts[‘:Voting’].bytecode: Voting.sol源代码编译后得到的字节码。这是将被部署到blockchain的代码。
    compiledCode.contracts[‘:Voting’].interface: 合约接口或模板(称为ABI)告诉用户合约含有哪些方法。您需要这些ABI的定义,因为将来你总是需要与合约交互的。更多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将智能合约部署到区块链。第一个参数是一个候选人数组。让我们看看第二个参数中的是什么:

    data:这是我们部署到链上的字节码。

    from:blockchain要追踪谁部署了合约。目前,我们只是挑选web3.eth.accounts中的第一个帐户作为合约的拥有者。记住,在我们启动测试链时web3.eth.accounts有10个ganache创建的账号。在真是的链上,你不能使用任何账号,你必须先获取并解锁账号。创建账号时会让你设置密码,用来保证你的这个帐户的所有权。Ganache为了方便默认10个账户都解锁了。

    gas:与区块链交互的手续费。这些钱是给那些把你的合约写入区块链等所有动作的矿工们的。通过‘gas’字段,你设置想要付多少钱给矿工。你的账户中的ether(ETH)资产将用于购买gas。gas的价格是由网络设定的。

    我们现在已经部署了合约并拥有了合约实例(上面的变量:contractinstance),可以用它来跟合约交互。在区块链上的智能合约数量成千上万。那么,怎么去标识合约呢?答:deployedContract.address。当你想要跟合约交互时,你需要合约的部署地址和前面提到的 ABI定义。

    3. 在nodejs控制台中与智能合约交互

    > contractInstance.totalVotesFor.call('Rama')
    { [String: '0'] s: 1, e: 0, c: [ 0 ] }
    > contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
    '0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53'
    > contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
    '0x02c054d238038d68b65d55770fabfca592a5cf6590229ab91bbe7cd72da46de9'
    > contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
    '0x3da069a09577514f2baaa11bc3015a16edf26aad28dffbcd126bde2e71f2b76f'
    > contractInstance.totalVotesFor.call('Rama').toLocaleString()
    '3'
    
    

    在nodejs控制台中尝试以上命令你将看到选票计数在递增。每次你投票给一个候选人,你得到一个交易ID:例如:“0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53。这个交易ID是发生交易的证明,在任何时间你都可以通过交易ID溯源。交易是不可篡改的,这也是想以太坊这样的区块链的一大优势。在后续教程我们将利用这一特性来构建应用.

    4. 连接区块链并且可以投票的网页

    现在大部分的工作已经完成,我们现在要做的就是用HTML和js实现一个简单页面来展示候选人和调用投票命令。下面你可以找到HTML代码和js文件。把它们都丢在hello_world_voting目录并在浏览器中打开index.html。

    index.html hosted with ❤ by GitHub

    <!DOCTYPE html>
    <html>
    <head>
      <title>Hello World DApp</title>
      <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
      <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
    </head>
    <body class="container">
      <h1>A Simple Hello World Voting Application</h1>
      <div class="table-responsive">
        <table class="table table-bordered">
          <thead>
            <tr>
              <th>Candidate</th>
              <th>Votes</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Rama</td>
              <td id="candidate-1"></td>
            </tr>
            <tr>
              <td>Nick</td>
              <td id="candidate-2"></td>
            </tr>
            <tr>
              <td>Jose</td>
              <td id="candidate-3"></td>
            </tr>
          </tbody>
        </table>
      </div>
      <input type="text" id="candidate" />
      <a href="#" οnclick="voteForCandidate()" class="btn btn-primary">Vote</a>
    </body>
    <script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
    <script src="./index.js"></script>
    </html>

    index.js

    web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
    abi = JSON.parse('[{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"x","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"contractOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"type":"constructor"}]')
    VotingContract = web3.eth.contract(abi);
    // 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
    contractInstance = VotingContract.at('0x2a9c1d265d06d47e8f7b00ffa987c9185aecf672');
    candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}
    
    function voteForCandidate() {
      candidateName = $("#candidate").val();
      contractInstance.voteForCandidate(candidateName, {from: web3.eth.accounts[0]}, function() {
        let div_id = candidates[candidateName];
        $("#" + div_id).html(contractInstance.totalVotesFor.call(candidateName).toString());
      });
    }
    
    $(document).ready(function() {
      candidateNames = Object.keys(candidates);
      for (var i = 0; i < candidateNames.length; i++) {
        let name = candidateNames[i];
        let val = contractInstance.totalVotesFor.call(name).toString()
        $("#" + candidates[name]).html(val);
      }
    });
    如果您还记得,我们前面说过,我们需要ABI和合约地址来与智能合约进行交互。你可以看到在index.js文件中是如何使用的。

    这是你在浏览器打开index.html将看到的画面


    如果您能够在文本框中输入候选名称并进行投票并看到选票计数在增加,那么你的第一个应用成功了!祝贺你!
    总结: 设置你的开发环境,编写了一个简单的合约,编译并部署合约到blockchain,通过nodejs控制台与合约交互, 通过网页与合约交互。如果你还没有开始的话,现在是你佩服自己的好时机:)

    第2章中,我们将把这个合约部署到公共测试网络,以便全世界都能看到它并为候选人投票。我们也会变得越来越复杂和使用truffle开发框架(而不使用Nodejs控制台管理全过程)。希望这个教程帮助你得到一个实际的想法如何开始在以太坊平台开发DAPP。

    如果你想要一个更具挑战性的项目,我创建了一个教程:基于以太坊和IPFS建立一个去中心化的的eBay
    展开全文
  • 以太坊有几个公共测试网络和一个主网。1. 测试网络:如ropsten blockchains,Rinkeby,Kovan。它们仅用于测试目的。在这些网络上使用的以太币都是假的。2. 主网(Homestead):这是整个世界真实交易的链。你在这个网络...

    在第1章(全栈投票系统),我们用ganache在我们的开发环境建立了一个简单的投票程序。现在,让我们把这个应用在真正的blockchain。以太坊有几个公共测试网络和一个主网。

    1. 测试网络:如ropsten blockchains,Rinkeby,Kovan。它们仅用于测试目的。在这些网络上使用的以太币都是假的。

    2. 主网(Homestead):这是整个世界真实交易的链。你在这个网络上使用的以太币有真正的价值。

    在本教程中,我们将完成以下内容:

    1. 安装geth-用于下载以太坊区块链和在本地运行以太坊节点的客户端。
    2. 安装Truffle 用于编译和部署智能合约的以太坊DAPP开发框架
    3. 我们要在上一章的基础上做一些小改动, 以便之前的投票系统能够运行在truffle框架上
    4. 编译并部署合约到Rinkeby测试网络

    5. 通过truffle控制台与合约交互, 然后通过网页与合约交互。


    1. 安装geth并同步区块数据

    我已经在MacOS和Ubuntu安装和测试。安装非常简单:

    Mac:

    mahesh@projectblockchain:~$ brew tap ethereum/ethereum
    mahesh@projectblockchain:~$ brew install ethereum

    Ubuntu:

    mahesh@projectblockchain:~$ sudo apt-get install software-properties-common
    mahesh@projectblockchain:~$ sudo add-apt-repository -y ppa:ethereum/ethereum
    mahesh@projectblockchain:~$ sudo apt-get update
    mahesh@projectblockchain:~$ sudo apt-get install ethereum

    在这里你可以找到各种平台的安装说明:https://github.com/ethereum/go-ethereum/wiki/building-ethereum

    一旦你已经安装了geth,在命令行控制台运行下面的命令:

    mahesh@projectblockchain:~$ geth --rinkeby --syncmode "fast" --rpc --rpcapi db,eth,net,web3,personal --cache=1024  --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*"

    这将启动以太坊节点,连接到其他节点开始下载区块。下载时间取决于多种因素,如您的网速,计算机的内存,硬盘等,cache根据你的机器内存调整, 我的机器是8GB内存50Mbps带宽,下载区块花了我30–45分钟。

    geth运行的时候,你会看到类似下面的输出。你可以在输出中查看区块号。如果你的区块完全同步了,你会看到区块号与这个页面上的接近:https://rinkeby.etherscan.io/

    I0130 22:18:15.116332 core/blockchain.go:1064] imported   32 blocks,    49 txs (  6.256 Mg) in 185.716ms (33.688 Mg/s). #445097 [e1199364… / bce20913…]
    I0130 22:18:20.267142 core/blockchain.go:1064] imported    1 blocks,     1 txs (  0.239 Mg) in  11.379ms (20.963 Mg/s). #445097 [b4d77c46…]
    I0130 22:18:21.059414 core/blockchain.go:1064] imported    1 blocks,     0 txs (  0.000 Mg) in   7.807ms ( 0.000 Mg/s). #445098 [f990e694…]
    I0130 22:18:34.367485 core/blockchain.go:1064] imported    1 blocks,     0 txs (  0.000 Mg) in   4.599ms ( 0.000 Mg/s). #445099 [86b4f29a…]
    I0130 22:18:42.953523 core/blockchain.go:1064] imported    1 blocks,     2 txs (  0.294 Mg) in   9.149ms (32.136 Mg/s). #445100 [3572f223…]

    2. 安装Truffle框架

    使用npm安装truffle,使用的truffle版本是 3.1.1.

    npm install -g truffle
    3. 启动投票合约

    首先,启动truffle工程:

    mahesh@projectblockchain:~$ mkdir voting
    mahesh@projectblockchain:~$ cd voting
    mahesh@projectblockchain:~/voting$ npm install -g webpack
    mahesh@projectblockchain:~/voting$ truffle unbox webpack
    mahesh@projectblockchain:~/voting$ ls
    README.md               contracts               node_modules            test                    webpack.config.js       truffle.js
    app                     migrations              package.json            
    mahesh@projectblockchain:~/voting$ ls app/
    index.html  javascripts  stylesheets
    mahesh@projectblockchain:~/voting$ ls contracts/
    ConvertLib.sol  MetaCoin.sol  Migrations.sol
    mahesh@projectblockchain:~/voting$ ls migrations/
    1_initial_migration.js  2_deploy_contracts.js

    正如您在上面看到的,truffle创建了运行完整dapp所需的必要文件和目录。truffle还创建了一个示例应用程序来帮助您开始使用(本教程中我们不会使用它)。您可以安全地删除该项目的contracts目录中的ConvertLib.sol和MetaCoin.sol文件。


    理解migrations目录的内容很重要。这些migrations文件用于将合约部署到区块链。 (如果您还记得,在上一篇文章中,我们使用了VotingContract.new将合约部署到区块链,现在我们不需要这样做)。第一个文件:1_initial_migration.js将名为Migrations的合同部署到区块链中,并用于存储您已部署的最新合约。每次运行migration时,truffle都会查询区块链以获取已部署的最后一个合约,然后部署尚未部署的任何合约。然后更新Migrations合约中的last_completed_migration字段,以指示部署的最新合约。您可以简单地将其视为名为migration的数据库表,其中名为last_completed_migration的列始终保持最新状态。你可以在truffle文件页面找到更多细节。

    现在让我们用我们在前一个教程中编写的所有代码更新该项目,并在下面进行一些更改。

    首先,将上一教程中的Voting.sol复制到contracts目录(这个文件没有变化)。

    pragma solidity ^0.4.18;
    // We have to specify what version of compiler this code will compile with
    
    contract Voting {
      /* mapping field below is equivalent to an associative array or hash.
      The key of the mapping is candidate name stored as type bytes32 and value is
      an unsigned integer to store the vote count
      */
      
      mapping (bytes32 => uint8) public votesReceived;
      
      /* Solidity doesn't let you pass in an array of strings in the constructor (yet).
      We will use an array of bytes32 instead to store the list of candidates
      */
      
      bytes32[] public candidateList;
    
      /* This is the constructor which will be called once when you
      deploy the contract to the blockchain. When we deploy the contract,
      we will pass an array of candidates who will be contesting in the election
      */
      function Voting(bytes32[] candidateNames) public {
        candidateList = candidateNames;
      }
    
      // This function returns the total votes a candidate has received so far
      function totalVotesFor(bytes32 candidate) view public returns (uint8) {
        require(validCandidate(candidate));
        return votesReceived[candidate];
      }
    
      // This function increments the vote count for the specified candidate. This
      // is equivalent to casting a vote
      function voteForCandidate(bytes32 candidate) public {
        require(validCandidate(candidate));
        votesReceived[candidate] += 1;
      }
    
      function validCandidate(bytes32 candidate) view public returns (bool) {
        for(uint i = 0; i < candidateList.length; i++) {
          if (candidateList[i] == candidate) {
            return true;
          }
        }
        return false;
      }
    }

    Voting.sol hosted with ❤ by GitHub

    mahesh@projectblockchain:~/voting$ ls contracts/
    Migrations.sol  Voting.sol

    接下来,用以下内容替换migrations目录中的2_deploy_contracts.js的内容:

    var Voting = artifacts.require("./Voting.sol");
    module.exports = function(deployer) {
      deployer.deploy(Voting, ['Rama', 'Nick', 'Jose'], {gas: 6700000});
    };
    /* As you can see above, the deployer expects the first argument to   be the name of the contract followed by constructor arguments. In our case, there is only one argument which is an array of
    candidates. The third argument is a hash where we specify the gas required to deploy our code. The gas amount varies depending on the size of your contract.
    */


    您还可以将gas值设置为truffle.js中的全局设置。像下面那样添加gas选项,以便将来如果您在特定migration文件忘记设置gas,它将默认使用全局值。

    require('babel-register')
    module.exports = {
      networks: {
        development: {
          host: 'localhost',
          port: 8545,
          network_id: '*',
          gas: 470000
        }
      }
    }

    用下面的内容替换app/javascripts/app.js的内容。

    // Import the page's CSS. Webpack will know what to do with it.
    import "../stylesheets/app.css";
    
    // Import libraries we need.
    import { default as Web3} from 'web3';
    import { default as contract } from 'truffle-contract'
    
    /*
     * When you compile and deploy your Voting contract,
     * truffle stores the abi and deployed address in a json
     * file in the build directory. We will use this information
     * to setup a Voting abstraction. We will use this abstraction
     * later to create an instance of the Voting contract.
     * Compare this against the index.js from our previous tutorial to see the difference
     * https://gist.github.com/maheshmurthy/f6e96d6b3fff4cd4fa7f892de8a1a1b4#file-index-js
     */
    
    import voting_artifacts from '../../build/contracts/Voting.json'
    
    var Voting = contract(voting_artifacts);
    
    let candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}
    
    window.voteForCandidate = function(candidate) {
      let candidateName = $("#candidate").val();
      try {
        $("#msg").html("Vote has been submitted. The vote count will increment as soon as the vote is recorded on the blockchain. Please wait.")
        $("#candidate").val("");
    
        /* Voting.deployed() returns an instance of the contract. Every call
         * in Truffle returns a promise which is why we have used then()
         * everywhere we have a transaction call
         */
        Voting.deployed().then(function(contractInstance) {
          contractInstance.voteForCandidate(candidateName, {gas: 140000, from: web3.eth.accounts[0]}).then(function() {
            let div_id = candidates[candidateName];
            return contractInstance.totalVotesFor.call(candidateName).then(function(v) {
              $("#" + div_id).html(v.toString());
              $("#msg").html("");
            });
          });
        });
      } catch (err) {
        console.log(err);
      }
    }
    
    $( document ).ready(function() {
      if (typeof web3 !== 'undefined') {
        console.warn("Using web3 detected from external source like Metamask")
        // Use Mist/MetaMask's provider
        window.web3 = new Web3(web3.currentProvider);
      } else {
        console.warn("No web3 detected. Falling back to http://localhost:8545. 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:8545"));
      }
    
      Voting.setProvider(web3.currentProvider);
      let candidateNames = Object.keys(candidates);
      for (var i = 0; i < candidateNames.length; i++) {
        let name = candidateNames[i];
        Voting.deployed().then(function(contractInstance) {
          contractInstance.totalVotesFor.call(name).then(function(v) {
            $("#" + candidates[name]).html(v.toString());
          });
        })
      }
    });

    app.js hosted with ❤ by GitHub

    将app/index.html的内容替换为以下内容。 即使这个文件与上一章几乎相同,除了包含的js文件是第41行的app.js。

    <!DOCTYPE html>
    <html>
    <head>
      <title>Hello World DApp</title>
      <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
      <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
    </head>
    <body class="container">
      <h1>A Simple Hello World Voting Application</h1>
      <div id="address"></div>
      <div class="table-responsive">
        <table class="table table-bordered">
          <thead>
            <tr>
              <th>Candidate</th>
              <th>Votes</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Rama</td>
              <td id="candidate-1"></td>
            </tr>
            <tr>
              <td>Nick</td>
              <td id="candidate-2"></td>
            </tr>
            <tr>
              <td>Jose</td>
              <td id="candidate-3"></td>
            </tr>
          </tbody>
        </table>
        <div id="msg"></div>
      </div>
      <input type="text" id="candidate" />
      <a href="#" οnclick="voteForCandidate()" class="btn btn-primary">Vote</a>
    </body>
    <script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
    <script src="app.js"></script>
    </html>

    index.html hosted with ❤ by GitHub

    4.将合约部署到Rinkeby测试网络

    在我们可以部署合约之前,我们需要一个帐户和一些以太币。当我们使用ganache时,它创建了10个测试账户并预分配100个虚假以太币。 但是对于测试网络和主网,我们必须创建帐户并自己添加一些ETH。

    在您的命令行终端中,执行以下操作:

    mahesh@projectblockchain:~/voting$ truffle console
    truffle(default)> web3.personal.newAccount('verystrongpassword')
    '0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1'
    truffle(default)> web3.eth.getBalance('0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1')
    { [String: '0'] s: 1, e: 0, c: [ 0 ] }
    truffle(default)> web3.personal.unlockAccount('0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1', 'verystrongpassword', 15000)
    // Replace 'verystrongpassword' with a good strong password.
    // The account is locked by default, make sure to unlock it before using the account for deploying and interacting with the blockchain.

    在之前的文章中,我们启动了一个node控制台并初始化了web3对象。当我们执行truffle控制台时,所有这些都是自动完成的,并且我们得到一个可以使用的web3对象。我们现在有一个地址为0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1的账户(您的账户中会是不同的地址),余额为0。

    您可以通过这个网站为Rinkeby网络分配一些测试以太币:https://faucet.rinkeby.io/。再次尝试web3.eth.getBalance以确保您拥有以太币。您也可以在https://rinkeby.etherscan.io/上输入您的地址以查看您的帐户余额。如果您可以在https://rinkeby.etherscan.io/上看到非零余额,但是如果web3.eth.getBalance仍显示0,则表示您的同步尚未完成。您只需等待本地区块链同步并追上即可。


    现在您已经有了一些以太币,请继续编译并将合约部署到区块链。如果一切顺利,下面是运行的命令和输出。

    注意: 在部署合同之前,不要忘记解锁账户(默认使用 web3.eth.accounts[0])。

    mahesh@projectblockchain:~/voting$ truffle migrate
    Compiling Migrations.sol...
    Compiling Voting.sol...
    Writing artifacts to ./build/contracts
    Running migration: 1_initial_migration.js
    Deploying Migrations...
    Migrations: 0x3cee101c94f8a06d549334372181bc5a7b3a8bee
    Saving successful migration to network...
    Saving artifacts...
    Running migration: 2_deploy_contracts.js
    Deploying Voting...
    Voting: 0xd24a32f0ee12f5e9d233a2ebab5a53d4d4986203
    Saving successful migration to network...
    Saving artifacts...
    mahesh@projectblockchain:~/voting$

    在我的机器上,部署合约大概花了70-80秒。

    5.与投票合约交互

    如果您能够成功部署合约,您现在应该能够获取投票计数并通过truffle控制台进行投票。

    mahesh@projectblockchain:~/voting$ truffle console
    truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.voteForCandidate('Rama').then(function(v) {console.log(v)})})
    // After a few seconds, you should see a transaction receipt like this:
    receipt:
    { blockHash: '0x7229f668db0ac335cdd0c4c86e0394a35dd471a1095b8fafb52ebd7671433156',
    blockNumber: 469628,
    contractAddress: null,
    ....
    ....
    truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.totalVotesFor.call('Rama').then(function(v) {console.log(v)})})
    { [String: '1'] s: 1, e: 0, c: [ 1] }

    如果你能够做到这一点,那表示成功了,你的已经生效了,现在继续,启动服务器

    mahesh@projectblockchain:~/voting$ npm run dev


    通过浏览器,您应该可以在localhost:8080上看到投票页面,并能够投票并看到所有候选人的投票计数。 由于我们正在处理一个真正的区块链,因此每次写入区块链(voteForCandidate)将需要几秒钟(矿工必须将您的交易包括在区块中并将区块包含在区块链中)。


    如果你看到这个页面并且能够投票,你可以在公共测试网络上建立一个完整的以太坊应用程序,恭喜!

    由于您所有的交易都是公开的,您可以在这里查看它们:http://rinkeby.etherscan.io/。 只需输入您的帐户地址,它会显示您所有的交易与时间戳。

    希望你能够跟得上我并让程序工作。 你可以在这里找到github仓库中的所有文件。 如果遇到让应用程序工作的问题,请随时通过twitter @zastrinlab来戳我。

    [本教程的第3部分现已发布!]

    如果你想要一个更具挑战性的项目,我创建了一个在Ethereum和IPFS上构建去中心化eBay的课程

    一如既往地感谢Raine Rupert Revere的所有反馈,并感谢Craig Skipsey找到所有的错误!

    展开全文
  • 以其使以太坊区块链拥挤而闻名,是dApp的一个很好的例子,它将可养殖和可收藏的概念与区块链相结合。这个耸人听闻的游戏只是一个创造性的例子,几乎有无限的机会。 虽然看似非常复杂,但已经开发出某些框架和工具来...

    在整个加密货币市场的市值超过7000亿美元之后,加密货币市场在过去几个月太疯狂了,但这只是一个开始。随着区块链系统的不断发展和扩展,进入这一新领域并利用这项技术的一个好方法是使用去中心化应用程序,也称为dApps。

    CryptoKitties以其使以太坊区块链拥挤而闻名,是dApp的一个很好的例子,它将可养殖和可收藏的概念与区块链相结合。这个耸人听闻的游戏只是一个创造性的例子,几乎有无限的机会。 虽然看似非常复杂,但已经开发出某些框架和工具来抽象你与区块链和智能合约的交互。在这篇博文中,我将通过一种方式在以太坊上创建一个去中心化的投票应用程序。我将简要介绍以太坊,但你可能应该对它有所了解,以便充分利用本指南。另外,我希望你知道Javascript。

    为什么要开发去中心化投票应用?

    从本质上讲,利用区块链技术的去中心化应用程序允许你在没有可信赖的第三方的情况下执行与今天相同的操作(如转移资金)。最好的dApp具有特定的真实世界的用例,以便利用区块链的独特特征。

    • 从本质上讲,区块链是一个共享的,可编程的,加密安全的,可信赖的分类账本,没有任何一个用户可以控制,任何人都可以查询。- Klaus Schwab

    即使投票应用对大家来说可能不是一个伟大的应用程序,但是我选择使用它作为本指南,这是因为区块链解决的主要问题:透明度,安全性,可访问性,可信任,是困扰当前民主选举的主要问题。

    由于区块链是去中心化的交易(投票)的永久记录,因此每次投票都可以无可辩驳地追溯到它发生的时间和地点,而不会泄露选民的身份。此外,过去的投票也不能被改变,而现在也不能被黑客攻击,因为每个交易都是由网络中的每个节点验证的。任何外部或内部攻击者必须控制51%的节点才能改变记录。

    即使攻击者能够在伪造用户输入真实身份证投票时也能实现这一点,但端到端投票系统可以让选民验证他们的投票是否在系统中正确输入,这使得系统极其安全。

    以太坊的核心组成部分

    我希望你读本指南的其余部分前,了解了区块链以太坊。这里有一个很棒的指南,写了我想让你知道的核心组件的简要概述。

    • 智能合约充当后端逻辑和存储。合约是用Solidity一种智能合约语言编写的,是一个代码和数据的集合,驻留在以太坊区块链的特定地址。它与面向对象编程中的类非常相似,它包含函数和状态变量。智能合约以及区块链是所有权力下放应用程序的基础。像Blockchain一样,它们是不可变的和分布式的,这意味着如果它们已经在以太坊网络上,升级它们将是一种痛苦。幸运的是,这里有一些方法可以做到这一点。
    • 以太坊虚拟机(EVM)处理整个以太坊网络的内部状态和计算。将EVM视为这种大规模去中心化计算机,其中包含能够执行代码,更改数据和相互交互的addresses
    • Web3.js是一个Javascript API,允许你与区块链进行交互,包括进行交易和调用智能合约。此API抽象了与以太坊客户端的通信,允许开发人员专注于他们的应用程序的内容。你必须在浏览器中嵌入一个web3实例才能执行此操作。

    我们将使用的其他工具

    • Truffle是以太坊的流行测试开发框架。它包括开发区块链,编译和迁移脚本,用于将合约部署到区块链,合约测试等。它使开发更容易!
    • Truffle Contracts是Web3 Javascript API之上的抽象,允许你轻松连接智能合约并与之互动。
    • Metamask将以太坊带入你的浏览器。它是一个浏览器扩展,提供链接到你的以太坊地址的安全web3实例,允许你使用去中心化应用程序。我们不会在本教程中使用Metamask,但它是人们在生产中与DApp交互的一种方式。相反,我们将在开发期间注入我们自己的web3实例。有关更多信息,请查看此链接

    开始吧!

    为简单起见,我们实际上不会构建我之前描述的完整投票系统。为了便于说明,它只是一个单页应用程序,用户可以输入他们的ID并为候选人投票。还将有一个按钮,计算并显示每个候选人的投票数。

    这样,我们将能够专注于在应用程序中创建智能合约并与之交互的过程。整个应用程序的源代码将在此存储库中,你需要安装Node.js和npm。

    1.首先,让我们在全局范围内安装Truffle。

    npm install -g truffle
    

    要使用Truffle命令,必须在现有项目中运行它们。

    git clone https://github.com/tko22/truffle-webpack-boilerplate
    cd truffle-webpack-boilerplate
    npm install
    

    这个存储库只是一个Truffle Box的框架,它是可以在一个命令中获得的样板或示例应用程序 - truffle unbox [box name]。但是,带有webpack的Truffle box未使用最新版本进行更新,并包含一个示例应用程序。因此,我创建了这个repo

    2.目录结构

    你的目录结构应包括以下内容:

    • contracts/:包括所有合约的文件夹。不要删除Migrations.sol
    • migrations/:包含Migration files的文件夹,可帮助你将智能合约部署到区块链中。
    • src/:保存应用程序的HTML/CSS和Javascript文件。
    • truffle.js:truffle配置文件。
    • build/:在编译合约之前,你不会看到此文件夹。此文件夹包含构建文件,因此不要修改任何这些文件!构建文件描述了合约的功能和体系结构,并提供了有关如何与区块链中的智能合约进行交互的Truffle Contracts和web3信息。

    1.写下你的智能合约

    设置和介绍完,让我们开始写代码吧!首先,我们将编写我们的智能合约,这是用Solidity编写的(其他语言不那么受欢迎)。这可能看起来很不爽,但事实并非如此。

    对于任何应用程序,你希望智能合约尽可能简单,甚至是非常简单。请记住,你必须为你所做的每笔计算/交易付费,而你的智能合约将永远存在于区块链中。所以,你真的希望它能够完美地运作——也就是说,它越复杂,就越容易犯错误。

    我们的合约将包括:

    • 状态变量:包含永久存储在区块链中的值的变量。我们将使用状态变量来保存选民和候选人的名单和数量。
    • 函数:函数是智能合约的可执行文件。它们是我们要求与区块链进行交互的内容,具有不同级别的内部和外部可见性。请记住,无论何时你想要更改变量的值/状态,都必须进行交易——这要耗费以太币。你也可以calls区块链,这不会花费任何以太,因为你所做的更改将被销毁(当我们实际进行transactionscall时,在下面会有更多内容)。
    • 事件:每当调用事件时,传递给事件的值都将记录在交易日志中。这允许Javascript回调函数或已解析的promises查看你想要在交易之后传回的特定值。这是因为每次进行交易时,都会返回交易日志。我们将使用一个事件来记录新创建的候选者的ID,我们将显示该ID。
    • 结构类型 - 这与C结构非常相似。Structs允许你保存多个变量,并且对于具有多个属性的事物非常棒。Candidates只会有他们的名字和党派,但你绝对可以为他们添加更多属性。
    • 映射 - 将它们视为hash映射或字典,它具有键值对。我们将使用两个映射。

    这里没有列出更多类型,但有些类型稍微复杂一些。这五个包含了智能合约通常使用的大部分结构。这里将更深入地解释这些类型。

    作为参考,这是智能合约的代码。请注意,此文件应该被称为Voting.sol但我希望Github gist具有style,所以我给它一个.js扩展名。与本指南的其余部分一样,我将在代码中提供注释解释它正在做什么,然后我将在指出某些警告和逻辑的同时解释大体思路。

    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);
        }
    }
    

    基本上,我们有两个Structs(包含多个变量的类型),用于描述选民和候选人。使用Structs,我们可以为它们分配多个属性,例如电子邮件,地址等。

    为了跟踪选民和候选人,我们将它们放入单独的映射中,它们是整数索引的。候选人或选民的索引/密钥——让我们称之为ID——是函数访问它们的唯一方式。

    我们还会跟踪选民和候选人的数量,这将有助于我们为他们编制索引。此外,不要忘记第8行中的事件,该事件将在添加时记录候选人的ID。我们的界面将使用此事件,因为我们需要跟踪候选人的ID以便为候选人投票。

    • 1.我知道,与我之前所说的关于使合约变得非常简单的说法相反,我认为这个合约与这个应用实际上做的相比有点复杂。但是,我这样做是为了让你们更容易进行编辑并在之后为此应用程序添加功能(最后更多内容)。如果你想制作一个更简单的投票应用程序,智能合约可以在不到15行代码。
    • 2.请注意,状态变量numCandidatesnumVoters未声明为public。默认情况下,这些变量具有internal可见性,这意味着它们只能由当前合约或派生合约直接访问(不用担心,我们不会使用它)。
    • 3.我们使用32bytes用于字符串而不是使用string类型。我们的EVM具有32字节的字大小,因此它被optimized以处理32字节的块中的数据。(当数据不是32字节的块时,编译器,例如Solidity,必须做更多的工作并生成更多的字节码,这实际上会导致更高的天然气成本。)
    • 4.当用户投票时,会创建一个新的Voter结构并将其添加到映射中。为了计算某个候选人的投票数,你必须遍历所有选民并计算投票数。候选人的行为相同。因此,这些映射将保留所有候选人和选民的历史。

    2.实例化web3和合约

    完成我们的智能合约后,我们现在需要运行我们的测试区块链并将此合约部署到区块链上。我们还需要一种方法来与它交互,这将通过web3.js完成。

    在我们开始测试区块链之前,我们必须在/contracts文件夹中创建一个名为2_deploy_contracts.js的文件,告诉它在迁移时包含你的投票智能合约。

    var Voting = artifacts.require("Voting")
    
    module.exports = function(deployer) {
      deployer.deploy(Voting)
    }
    

    要开始开发以太坊区块链,请转到命令行并运行:

    truffle develop
    

    由于Solidity是一种编译语言,我们必须首先将其编译为字节码,以便EVM执行。

    compile
    

    你现在应该在目录中看到一个文件夹build/。此文件夹包含构建文件,这对Truffle的内部工作至关重要,因此请勿修改它们!

    接下来,我们必须迁移合约。migrations是一个truffle脚本,可帮助你在开发时更改应用程序合约的状态。请记住,你的合约已部署到区块链上的某个地址,因此无论何时进行更改,你的合约都将位于不同的地址。 迁移可帮助你执行此操作,还可帮助你移动数据。

    migrate
    

    恭喜!你的智能合约现在永远在区块链上。好吧,还不是真的...... 因为truffle develop会在每次停止时刷新。

    如果你想拥有一个持久的区块链,可以考虑一下由Truffle开发的Ganache。如果你使用的是Ganache,则无需调用truffle develop。相反,你将运行truffle compiletruffle migrate。要了解在没有Truffle的情况下部署合约需要什么,请查看此博客文章

    一旦我们将智能合约部署到区块链,我们将不得不在应用程序启动时在浏览器上使用Javascript设置web3.0实例。因此,下一段代码将放在js/app.js的底部。请注意,我们使用的是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()
    })
    

    如果你不理解这段代码,你真的不必太担心。只要知道这将在应用程序启动时运行,并将检查浏览器中是否已存在web3实例(Metamask)。如果没有,我们将创建一个与localhost:9545交互Truffle开发区块链。

    如果你正在使用Ganache,你必须将端口更改为7545.一旦创建了一个实例,我们将调用start函数。

    3.添加功能

    我们需要做的最后一件事是为应用程序编写接口。这涉及任何Web应用程序的基本要素——HTML,CSS和Javascript(我们已经编写了一些用于创建web3实例的Javascript)。首先,让我们创建我们的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" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" 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>
    

    这是一个非常简单的页面,带有用户ID的输入表单,以及用于投票和计票的按钮。点击这些按钮后,他们将调用投票的特定功能,并找到候选人的投票数。

    但是有三个重要的div元素,其中有id:candidate-boxmsgvote-box,它们分别包含每个候选者的复选框,一条消息和一个投票数。我们还导入了JQuery,Bootstrap和app.js

    现在,我们只需要与合约互动并实施投票和计算每个候选人的投票数量的功能。JQuery将控制DOM,当我们进行交易或调用Blockchain时,我们将使用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()
    })
    

    请注意,我在上一步中用于创建web3实例的代码也在这里。首先,我们导入必要的库和webpack内容,包括web3和Truffle Contracts。我们将使用Truffle Contracts,它建立在web3之上,与Blockchain进行交互。

    要使用它,我们将获取在编译投票智能合约时自动构建的构建文件,并使用它们来创建Truffle Contracts。最后,我们在全局变量windows中设置函数,用于启动应用程序,投票给候选人,以及查找投票数。

    要实际与区块链交互,我们必须使用deployed的功能创建松露合约的实例。反过来,这将返回一个承诺,该实例作为你将用于从智能合约调用函数的返回值。

    有两种方法可以与这些功能进行交互:交易和调用。交易是一种写操作,它将被广播到整个网络并由矿工处理(因此,成本为Ether)。如果要更改状态变量,则必须执行交易,因为它将更改区块链的状态。

    call是一种读操作,模拟交易但丢弃状态变化。因此,它不会花费以太。这非常适合调用getter函数(查看我们之前在智能合约中编写的四个getter函数)。

    要使用Truffle Contracts进行交易,你可以编写instance.functionName(param1,param2),将instance作为deployed函数返回的实例(例如,检查第36行)。此事务将返回一个以交易数据作为返回值的promise。因此,如果在智能合约函数中返回一个值,但是使用相同的函数执行交易,则不会返回该值。

    这就是为什么我们有一个事件会记录你想要写入要返回的交易数据的任何内容。在第36-37行,我们进行交易以添加一个候选人即Candidate。当我们确定promise时,我们在结果中有交易数据。

    要获取我们使用事件AddedCandidate()记录的候选ID(检查智能合约以查看它0),我们必须检查日志并检索它:result.logs[0].args.candidateID

    要真正了解正在发生的事情,请使用Chrome开发人员工具打印result并查看其result结构。

    要进行调用,你将编写instance.functionName.call(param1,param2)。但是,如果某个函数具有关键字view,那么Truffle Contracts将自动创建一个调用,因此你无需添加.call`。

    这就是我们的getter函数具有view关键字的原因。与进行交易不同,返回的调用promise将具有智能合约函数返回的任何返回值。

    我现在将简要解释这三个函数,但如果你构建了从数据存储中检索/更改数据并相应地操作DOM的应用程序,那么这应该非常熟悉。将Blockchain视为你的数据库,将Truffle Contracts视为从数据库获取数据的API。

    App.start()

    创建web3实例后立即调用此函数。要使Truffle Contracts正常工作,我们必须将接口设置为创建的web3实例并设置默认值(例如你正在使用的帐户以及你要为交易支付的gas量)。

    由于我们处于开发模式,我们可以使用任何数量的gas和任何帐户。在生产过程中,我们将采用MetaMask提供的帐户,并尝试找出你可以使用的最少量的gas,因为它实际上是真钱。

    设置好所有内容后,我们现在将显示每个候选人的复选框,供用户投票。为此,我们必须创建合约实例并获取候选人的信息。如果没有候选人,我们将创建他们。为了让用户投票给候选人,我们必须提供该特定候选人的ID。因此,我们使每个checkbox元素具有候选ID的id(HTML元素属性)。另外,我们将把候选数量添加到全局变量numOfCandidates中,我们将在App.findNumOfVotes()中使用它。JQuery用于将每个复选框及其候选名称附加到.candidate-box

    App.vote()

    此功能将根据单击的复选框及其id属性为某个候选人投票。

    • 1.我们将检查用户是否输入了他们的userID,这是他们的身份。如果他们没有,我们会显示一条消息告诉他们需要这样做。
    • 2.我们将检查用户是否正在为候选人投票,检查是否至少有一个被点击的复选框。如果没有点击任何复选框,我们也会显示一条消息,告诉他们请投票给候选人。如果单击其中一个复选框,我们将获取该复选框的id属性,该属性也是链接候选人的ID,并使用该属性为候选人投票。

    交易完成后,我们将解决退回的承诺并显示Voted已经完成投票的消息。

    App.findNumOfVotes()

    最后一个函数将找到每个候选人的投票数并显示它们。我们将通过候选人并调用两个智能合约函数,getCandidatetotalVotes。我们将解决这些承诺并为该特定候选人创建HTML元素。

    现在,启动应用程序,你将在`http://localhost:8080/上看到它!

    npm run dev
    

    资源

    我知道,这很多......当你慢慢开发这个应用程序并真正了解正在发生的事情时,你可能会暂时打开这篇文章。但那是在学习!请使用以太网,truffle以及我在下面提供的所有文档补充本指南。我试图点击本文中的许多关键点,但这只是一个简短的概述,这些资源将有很大帮助。

    总结

    在以太坊上构建应用程序非常类似于调用后端服务的常规应用程序。最难的部分是编写一份强大而完整的智能合约。我希望本指南可以帮助你了解去中心化应用程序和以太坊的核心知识,并帮助你启动你对开发它们的兴趣。

    如果你想创建我们已经建立的东西,这里有一些想法。我实际上已经用这样的方式编写了智能合约,它可以很容易地实现我在本指南中提到的一切。

    • 显示每个候选人的一方。当我们运行getCandidate(id)时,我们已经获得了候选人的聚会。
    • 检查用户输入的ID是否唯一。
    • 询问并存储有关用户的更多信息,例如他们的出生日期和家庭住址。
    • 添加选项以查看具有特定ID的人是否已投票。你将创建一个新表单以输入ID,然后你可以在区块链中搜索该特定用户。
    • 写一个新的智能合约功能,立即计算两个候选人的选票。目前,我们必须为两个候选人分别进行两次调用,要求合约循环遍历所有用户两次。
    • 允许添加新候选人。这意味着添加一个新表单来添加候选人,但也会更改我们如何在前端显示和投票候选人。
    • 要求用户拥有以太坊地址进行投票。我不包括用户地址的逻辑是因为不希望选民让以太坊参与这个投票过程。但是,许多DApps将要求用户拥有以太坊地址。

    此外,这里有一些提示,可以防止一些错误发生:

    • 当发生奇怪的事情时,请多检查一下你的智能合约函数。我在一个bug上花了几个小时才发现我在我的一个函数中返回了错误的值。
    • 连接到开发区块链时,请检查你的URL和端口是否正确。记住:7545用于truffle开发,9545用于Ganache。这些是默认值,因此如果你无法连接到区块链,你可能已经更改了它们。
    • 我没有仔细阅读,因为这个指南已经太久了,我可能会在这个问题上再发一篇文章 - 但是你应该测试你的合约!它会有很大帮助。
    • 如果你不熟悉promises,请了解它们的工作原理以及如何使用它们。Truffle Contracts使用promises,而web3也将支持promises。如果你做错了,他们可能会搞砸你正在检索的大量数据。

    欢呼致力于去中心化和安全的互联网 - Web 3.0!

    **另外我们还提供一些加快学习过程和提供问答服务的以太坊教程如下: **

    • web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。 ,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
    • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。
    • 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
    • 以太坊开发
    • python以太坊教程,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
    • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和事件等。
    • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
    • EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。

    汇智网原创翻译,转载请标明出处。这里是原文

    转载于:https://my.oschina.net/u/2472105/blog/2086086

    展开全文
  • 阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 除此之外,你最好还了解一些HTML及JavaScript知识。 本文通过实例教大家来开发去中心化应用,应用效果如图: ...
    阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 
    
    除此之外,你最好还了解一些HTML及JavaScript知识。 
    本文通过实例教大家来开发去中心化应用,应用效果如图: 

    项目背景

    Pete有一个宠物店,有16只宠物,他想开发一个去中心化应用,让大家来领养宠物。 
    在truffle box中,已经提供了pet-shop的网站部分的代码,我们只需要编写合约及交互部分。

    环境搭建

    1. 安装Node
    2. 安装 Truffle :npm install -g truffle
    3. 安装Ganache

    *Ganache(或Ganache CLI)已经取代了 testrpc。

    创建项目

    1.建立项目目录并进入

    > mkdir pet-shop-tutorial
    > cd pet-shop-tutorial

    2.使用truffle unbox 创建项目

    > truffle unbox pet-shop
     Downloading...
     Unpacking...
     Setting up...
     Unbox successful. Sweet!
    Commands:
      Compile:        truffle compile
      Migrate:        truffle migrate
      Test contracts: truffle test
      Run dev server: npm run dev

    这一步需要等待一会 ,也可以使用truffle init 来创建一个全新的项目。

    项目目录结构

    contracts/ 智能合约的文件夹,所有的智能合约文件都放置在这里,里面包含一个重要的合约Migrations.sol(稍后再讲)。 
    migrations/ 用来处理部署(迁移)智能合约 ,迁移是一个额外特别的合约用来保存合约的变化。 
    test/ 智能合约测试用例文件夹。 
    truffle.js/ 配置文件。 
    其他代码可以暂时不用管。

     

    编写智能合约

    智能合约承担着分布式应用的后台逻辑和存储。智能合约使用solidity编写,可阅读 solidity系列文章 
    在contracts目录下,添加合约文件Adoption.sol

    pragma solidity ^0.4.17;
    contract Adoption {
      address[16] public adopters;  // 保存领养者的地址
        // 领养宠物
      function adopt(uint petId) public returns (uint) {
        require(petId >= 0 && petId <= 15);  // 确保id在数组长度内
        adopters[petId] = msg.sender;        // 保存调用这地址 
        return petId;
      }
      // 返回领养者
      function getAdopters() public view returns (address[16]) {
        return adopters;
      }
    }

     

    编译部署智能合约

    Truffle集成了一个开发者控制台,可用来生成一个开发链用来测试和部署智能合约。

    编译

    Solidity是编译型语言,需要把可读的Solidity代码编译为EVM字节码才能运行。 
    dapp的根目录pet-shop-tutorial下,

    > truffle compile

    输出

    Compiling ./contracts/Adoption.sol...
    Writing artifacts to ./build/contracts

     

    部署

    编译之后,就可以部署到区块链上。 
    在migrations文件夹下已经有一个1_initial_migration.js部署脚本,用来部署Migrations.sol合约。 
    Migrations.sol 用来确保不会部署相同的合约。 
    现在我们来创建一个自己的部署脚本2_deploy_contracts.js

    var Adoption = artifacts.require("Adoption");
    module.exports = function(deployer) {
      deployer.deploy(Adoption);
    };

    在执行部署之前,需要确保有一个区块链运行 
    可以使用Ganache来开启一个私链来进行开发测试,默认会在7545端口上运行一个开发链。 
    Ganache 启动之后是这样: 

    接下来执行部署命令:

    > truffle  migrate

    执行后,有一下类似的输出:

     

    Using network 'develop'.
    Running migration: 1_initial_migration.js
      Deploying Migrations...
      ... 0x3076b7dac65afc44ec51508bf6f2b6894f833f0f9560ecad2d6d41ed98a4679f
      Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
    Saving successful migration to network...
      ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
    Saving artifacts...
    Running migration: 2_deploy_contracts.js
      Deploying Adoption...
      ... 0x2c6ab4471c225b5473f2079ee42ca1356007e51d5bb57eb80bfeb406acc35cd4
      Adoption: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
    Saving successful migration to network...
      ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
    Saving artifacts...

    在打开的Ganache里可以看到区块链状态的变化,现在产生了4个区块。 

     

    这时说明已经智能合约已经部署好了。

     

    测试

    现在我们来测试一下智能合约,测试用例可以用 JavaScript or Solidity来编写,这里使用Solidity。 
    在test目录下新建一个TestAdoption.sol,编写测试合约

    pragma solidity ^0.4.17;
    import "truffle/Assert.sol";   // 引入的断言
    import "truffle/DeployedAddresses.sol";  // 用来获取被测试合约的地址
    import "../contracts/Adoption.sol";      // 被测试合约
    contract TestAdoption {
      Adoption adoption = Adoption(DeployedAddresses.Adoption());
      // 领养测试用例
      function testUserCanAdoptPet() public {
        uint returnedId = adoption.adopt(8);
        uint expected = 8;
        Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
      }
      // 宠物所有者测试用例
      function testGetAdopterAddressByPetId() public {
        // 期望领养者的地址就是本合约地址,因为交易是由测试合约发起交易,
        address expected = this;
        address adopter = adoption.adopters(8);
        Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
      }
      // 测试所有领养者
      function testGetAdopterAddressByPetIdInArray() public {
      // 领养者的地址就是本合约地址
        address expected = this;
        address[16] memory adopters = adoption.getAdopters();
        Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
      }
    }

    Assert.sol 及 DeployedAddresses.sol是Truffle框架提供,在test目录下并不提供truffle目录。 
    TestAdoption合约中添加adopt的测试用例

     

    运行测试用例

    在终端中,执行

    truffle test

    如果测试通过,则终端输出:

    Using network 'develop'.
    Compiling ./contracts/Adoption.sol...
    Compiling ./test/TestAdoption.sol...
    Compiling truffle/Assert.sol...
    Compiling truffle/DeployedAddresses.sol...
      TestAdoption
        √ testUserCanAdoptPet (62ms)
        √ testGetAdopterAddressByPetId (53ms)
        √ testGetAdopterAddressByPetIdInArray (73ms)
      3 passing (554ms)

     

    创建用户接口和智能合约交互

    我们已经编写和部署及测试好了我们的合约,接下我们为合约编写UI,让合约真正可以用起来。 
    在Truffle Box pet-shop里,已经包含了应用的前端代码,代码在src/文件夹下。 
    在编辑器中打开src/js/app.js可以看到用来管理整个应用的App对象,init函数加载宠物信息,就初始化web3。 
    web3是一个实现了与以太坊节点通信的库,我们利用web3来和合约进行交互。

    初始化web3

    接下来,我们来编辑app.js修改initWeb3():

    删除注释,修改为:

    initWeb3: function() {
      // Is there an injected web3 instance?
      if (typeof web3 !== 'undefined') {
        App.web3Provider = web3.currentProvider;
      } else {
        // If no injected web3 instance is detected, fall back to Ganache
        App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
      }
      web3 = new Web3(App.web3Provider);
    
      return App.initContract();
    }

    代码中优先使用Mist 或 MetaMask提供的web3实例,如果没有则从本地环境创建一个。

     

    实例化合约

    使用truffle-contract会帮我们保存合约部署的信息,就不需要我们手动修改合约地址,修改initContract()代码如下:

    initContract: function() {
      // 加载Adoption.json,保存了Adoption的ABI(接口说明)信息及部署后的网络(地址)信息,它在编译合约的时候生成ABI,在部署的时候追加网络信息
      $.getJSON('Adoption.json', function(data) {
        // 用Adoption.json数据创建一个可交互的TruffleContract合约实例。
        var AdoptionArtifact = data;
        App.contracts.Adoption = TruffleContract(AdoptionArtifact);
    
        // Set the provider for our contract
        App.contracts.Adoption.setProvider(App.web3Provider);
    
        // Use our contract to retrieve and mark the adopted pets
        return App.markAdopted();
      });
      return App.bindEvents();
    }

    处理领养

    修改markAdopted()代码:

    markAdopted: function(adopters, account) {
      var adoptionInstance;
    
      App.contracts.Adoption.deployed().then(function(instance) {
        adoptionInstance = instance;
    
        // 调用合约的getAdopters(), 用call读取信息不用消耗gas
        return adoptionInstance.getAdopters.call();
      }).then(function(adopters) {
        for (i = 0; i < adopters.length; i++) {
          if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
            $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
          }
        }
      }).catch(function(err) {
        console.log(err.message);
      });
    }

    修改handleAdopt()代码:

     

    handleAdopt: function(event) {
      event.preventDefault();
    
      var petId = parseInt($(event.target).data('id'));
    
      var adoptionInstance;
    
      // 获取用户账号
      web3.eth.getAccounts(function(error, accounts) {
        if (error) {
          console.log(error);
        }
    
        var account = accounts[0];
    
        App.contracts.Adoption.deployed().then(function(instance) {
          adoptionInstance = instance;
    
          // 发送交易领养宠物
          return adoptionInstance.adopt(petId, {from: account});
        }).then(function(result) {
          return App.markAdopted();
        }).catch(function(err) {
          console.log(err.message);
        });
      });
    }

    在浏览器中运行

    安装 MetaMask

    MetaMask 是一款插件形式的以太坊轻客户端,开发过程中使用MetaMask和我们的dapp进行交互是个很好的选择,安装完成后,浏览器工具条会显示一个小狐狸图标。

    配置钱包

    在接受隐私说明后,会出现页面如下: 

    这里我们通过还原一个Ganache为我们创建好的钱包,作为我们的开发测试钱包。点击页面的 Import Existing DEN,输入Ganache显示的助记词。

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

    然后自己想要的密码,点击OK。 
    如图: 

     

     

    连接开发区块链网络

    默认连接的是以太坊主网(左上角显示),选择Custom RPC,添加一个网络:http://127.0.0.1:7545,点返回后,显示如下: 

     

    这时左上角显示为Private Network,账号是Ganache中默认的第一个账号。

    至此MetaMask的安装,配置已经完成。

     

    安装和配置lite-server

    接下来需要本地的web 服务器提供服务的访问, Truffle Box pet-shop里提供了一个lite-server可以直接使用,我们看看它是如何工作的。 
    bs-config.json指示了lite-server的工作目录。

    {
      "server": {
        "baseDir": ["./src", "./build/contracts"]
      }
    }

    ./src 是网站文件目录 
    ./build/contracts 是合约输出目录

    以此同时,在package.json文件的scripts中添加了dev命令:

    "scripts": {
      "dev": "lite-server",
      "test": "echo \"Error: no test specified\" && exit 1"
    },

    当运行npm run dev的时候,就会启动lite-server

     

    启动服务

    > npm run dev

    会自动打开浏览器显示我们的dapp,如本文的第一张图。 
    现在领养一直宠物看看,当我们点击Adopt时,MetaMask会提示我们交易的确认,如图: 

    点击Submit确认后,就可以看到成功领养了这个宠物。 
    在MetaMask中,也可以看到交易的清单: 

     

     

    好了,恭喜你,即将成为一名去中心化式应用开发者的你已经成为迈出了坚实的一步!

     

     

    BIZZAN(币严) 数字货币交易所官方网址:

    www.bizzan.com

    转载于:https://www.cnblogs.com/bizzan/p/10401731.html

    展开全文
  • 以太坊开发

    2021-01-08 07:05:21
    智能合约开发以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助。 以太坊的应用被称为去中心化应用(DApp),DApp开发主要包括两大部分...
  • 以太坊turffle框架初始化的demo实例,需要相关环境才能运行,可以参考我的博客,想学习dapp开发的同学可以看看
  • 智能合约开发以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助。 以太坊的应用被称为去中心化应用(DApp),DApp开发主要包括两大部分...
  • 用自己熟悉的语言学习以太坊DApp开发:Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart 1、以太坊虚拟机基本概念 在继续下面的教程之前,我们需要对以太坊和Solidity有一些基本的了解。我们...
  • 智能合约开发以太坊开发的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助。 以太坊的应用被称为去中心化应用(DApp),DApp开发主要包括两大部分...
  • 智能合约开发以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助。 以太坊的应用被称为去中心化应用(DApp),DApp开发主要包括两大部分...
  • 导读:本文旨在引导对 DApp 开发感兴趣的开发者,构建一个基于以太坊去中心化应用,通过开发一款功能完备的竞猜游戏,迈出 DApp 开发的第一步,通过实例讲解 Solidity 语言的常用语法,以及前端如何与智能合约进行...
  • 本文旨在引导对 DApp 开发感兴趣的开发者,构建一个基于以太坊去中心化应用,通过开发一款功能完备的竞猜游戏,迈出 DApp 开发的第一步,通过实例讲解 Solidity 语言的常用语法,以及前端如何与智能合约进行交互。...
  • 阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 除此之外,你最好还了解一些react知识。 本文通过实例教大家来开发去中心化应用,应用效果如图:   从本文,你可以...
  • 本文首发于深入浅出区块链...阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么除此之外,你最好还了解一些HTML及JavaScript知识。 本文通过实例教大家来开发去中心化应用,...
  • 阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 除此之外,你最好还了解一些 HTML 及 JavaScript 知识。 本文通过实例教大家来开发去中心化应用,应用效果如图: 从本文...
  • 阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 除此之外,你最好还了解一些HTML及JavaScript知识。 本文通过实例教大家来开发去中心化应用,应用效果如图: 从本文...
  • Embark 以太坊Dapp开发框架,支持IPFS、Whisper及Orbit调用 Web3Swift 一个Web3的swift SDK Porosity 反编译以太坊智能合约工具 Solidity-Coverage 检测Solidity代码覆盖 Caliper hyperledger区块链性能测试工具...
  • 智能合约开发以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助。 以太坊的应用被称为去中心化应用(DApp),DApp开发主要包括两大部分...
  • 智能合约开发以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助。 以太坊的应用被称为去中心化应用(DApp),DApp开发主要包括两大部分...
  • 最新内容会更新在主站深入浅出区块链社区 ...阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 除此之外,你最好还了解一些HTML及JavaScript知识。 本文通过实例教大家来开发...
  • Web3极客日报 #12

    2021-01-08 01:17:11
    如何在以太坊上构建TodoList – 教程@Riverhttps://www.dappuniversity.com/articles/blockchain-app-tutorial 基于Web3,Solidity,Truffle等一个完整的TodoList开发Demo。 通过构建区块链来学习区块链 – 教程@Riv

空空如也

空空如也

1 2
收藏数 30
精华内容 12
关键字:

以太坊dapp开发实例