跳转至

构建以太坊区块链应用

在前一章中,我们回顾了智能合同的基本特征以及如何编写众筹智能合同示例。在我们将智能合同部署到区块链之后,我们需要编写 web 应用程序来与智能合同进行交互。以太坊区块链通过调用智能合约函数和 getters 来提供 web3 API。

在本章中,我们将讨论以下主题:

  • 什么是分散应用 ( DApp )
  • web3js 快速概述
  • 设置以太坊开发环境
  • 开发和测试 DApp

分散式应用概述

一个分散式应用(或 DApp )是一个使用智能合约运行的应用。智能合约部署在以太坊虚拟机 ( EVM )上。它类似于客户端-服务器低层架构。DApp 可以有一个前端(web ),它通过 web3.js API 调用其后端(智能合约)。

下面的结构是我们要为我们的众筹 DApp 建立的:

我们将为 DApp 众筹项目建造的建筑

web3.js 快速概述

web3.js 是一个以太坊 JavaScript API,它提供了一组库来与本地或远程以太坊网络进行交互。web3js 和以太坊之间的连接是通过使用 HTTP 或 IPC 协议实现的。在下表中,我们快速回顾了一些重要的 web3.js API 概念:

| API 引用 | 描述 | 例子 | | web3-eth | 这个包提供了一个与以太坊区块链和智能合约交互的 API | getBalancesendTransaction, coinbasegetBlockNumber, getAccounts | | 网络 3-嘘 | 这个包提供了一个 API 来与 whisper 协议交互以进行广播 |

web3.shh.post({
        symKeyID: identities[0],
        topic: '0xffaadd11',
        payload: '0xffffffdddddd1122'
    }).then(h => console.log(`Message with hash ${h} was successfuly sent`))

| | web3-bzz | 这个包提供了一个 API 来与以太坊群交互,以太坊群是一个分散的文件存储平台 | web3.bzz.currentProvider``web3.bzz.download(bzzHash [, localpath]) | | web3-utils | 这个包为 Ethereum DApps 和其他 web3.js 包提供了一组实用函数 | web3.utils.toWei(number [, unit])``web3.utils.isAddress(address) |

供应者

一个提供者抽象出一个与以太坊区块链对话的连接。它将发出查询并将交易发送到区块链。web3 提供了JsonRpcProviderIpcProvider,可以让你连接到一个本地或者远程的以太坊节点,包括 Mainnet、Ropsten testnet、Kovan testnet、Rinkeby testnet,以及自定义的远程过程调用 ( RPC ),比如 Ganache。下面的代码片段展示了我们如何使用 web3 API 来连接以太坊节点。

var Web3 = require('web3');
var web3 = new Web3('http://localhost:8545');
// or
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
// change provider
web3.setProvider('ws://localhost:8546');
// or
web3.setProvider(new Web3.providers.WebsocketProvider('ws://localhost:8546'));

DApp 开发工具

有一些流行的区块链 web 开发工具被开发人员用来创建 DApp 项目的基本结构。下面几节列出了其中的一些。

松露

Truffle 是一个以太坊 DApp 端到端开发工具,它为编写、编译和部署测试智能合约和 dapp 提供了一个开发环境。可以为前端写 HTML、CSS、JavaScriptSolidity 用于智能合约,使用 web3.js API 与 UI 和智能合约进行交互。Truffle Boxes 提供了有用的样板文件,其中包含有用的模块、solidity 契约和库、前端代码和许多其他有用的文件。块菌盒帮助开发者快速开始他们的 DApp 项目。

Truffle 命令行使用以下格式:

  • truffle [command] [options]

以下是命令行工具中常用的选项:

| 命令 | 描述 | | compile | 编制合同文件。 | | console | 与部署的智能合约进行交互的命令行界面。 | | create | 该命令有助于创建新合同、新迁移文件和基本测试。 | | debug | 在调试器会话中试验特定的事务。 | | deploy / migration | 将合同部署到区块链网络。 | | develop | 在本地开发环境中通过命令行与合同交互。 | | init | 从以太坊包注册表安装一个包。 |

加纳切

Ganache 是一个私人以太坊区块链环境,允许您模仿以太坊区块链,以便您可以在自己的私人区块链中与智能合约进行交互。以下是 Ganache 提供的一些功能:

  • 显示区块链日志输出
  • 提供高级采矿控制
  • 内置块资源管理器
  • 以太坊区块链环境
  • Ganache 有一个桌面应用程序和命令行工具

这是桌面版的 Ganache 的样子:

命令行使用以下格式:

ganache-cli <options>

这些是命令行工具的常用选项:

| 选项 | 描述 | | -a or --accounts | 启动时生成的帐户数量。 | | -e or --defaultBalanceEther | 配置默认测试帐户乙醚量。默认为100。 | | -b or --blockTime | 以秒为单位指定块时间作为挖掘间隔。如果未指定该选项,Ganache 将在调用事务时立即挖掘新的块。 | | -h or --host or --hostname | 指定要监听的主机名。默认为127.0.0.1。 | | -p or --port | 指定端口号。默认为8545。 | | -g or --gasPrice | 以卫为单位指定气价(默认为20000000000)。 | | -l or --gasLimit | 阻塞气体限制(默认为0x6691b7)。 | | --debug | 显示用于调试目的的虚拟机操作码。 | | -q or --quiet | 运行ganache-cli而没有任何日志。 |

设置以太坊开发环境

按照这些说明获取以太坊开发工具,并启动以太坊私有本地区块链环境(主要用于将您的智能合约运行/部署到本地区块链)。

安装 Truffle

打开命令行并运行以下命令:

npm install -g truffle

安装 Ganache

打开命令行并安装 Ganache 的命令行界面,如下所示:

npm install -g ganache-cli

创建松露项目

为了初始化一个新的 DApp 项目,我们可以运行 truffle init命令来初始化一个空的 truffle 项目。这将创建 DApp 目录结构,包括应用程序、合同和带有 Truffle 配置的测试。由于 Truffle Boxes 提供了许多工作模板,在我们的 DApp 示例中,我们将使用 pet-shop box——JavaScript UI 库的 JQuery 版本——来开发我们的众筹 DApp 示例。

创建一个名为Crowdfunding的文件夹,打开命令行提示符,导航到Crowdfunding文件夹,并运行以下命令:

truffle unbox pet-shop

项目结构如下:

我们在前一章写了众筹智能合约。让我们将CrowdFunding.sol文件复制到Crowdfunding下的 contracts 文件夹中。

启动 Ganache 环境

打开新的终端窗口并运行以下命令:

Ganache-cli

这将在端口8545上运行Ganache-cli,Ganache 将为我们创建 10 个默认帐户。默认情况下,每个帐户有 100 个以太网。您应该会在控制台中看到类似这样的内容:

在我们的 Truffle 项目中,truffle.js7545定义为默认端口号。我们需要将端口号更新为8545,以匹配 Ganache 端口号,如下所示:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};

部署智能合同

您可能已经注意到,前面的命令创建了两个与迁移相关的文件,Migrations.sol1_initial_migration.jsMigrations.sol存储与上次应用的“迁移”脚本相对应的号码。当您添加新合同并部署合同时,商店中最后一次部署的数量将会增加。合同运行一次后,将不会再次运行。编号约定为x_script_name.js,x 从 1 开始,即1_initial_migration.js。你的新合同通常会在2_...开始。

在我们的例子中,我们将添加一个新的迁移契约来部署CrowdFunding。让我们创建一个名为2_deploy_contracts.js的文件。

CrowdFunding.sol定义构造函数如下:

constructor (address _owner, uint _minimumToRaise, uint _durationProjects,
        string _name, string _website)

要部署一个带有可选构造函数参数的契约,可以调用 truffle deploy 函数deployer.deploy(contract, args..., options)

我们将使用 Ganache 给我们的第一个帐户作为所有者帐户,如下所示:

var CrowdFunding = artifacts.require("./CrowdFunding.sol");
module.exports = (deployer, network, accounts) => {
  const ownerAddress = accounts[0];
  deployer.deploy(CrowdFunding, ownerAddress, 30, 60, "smartchart", "smartchart.tech");
}

让我们将智能合约部署到我们的网络中。运行truffle命令,如下所示:

truffle migrate

下面的截图显示了运行truffle migrate命令的结果:

这将我们的众筹智能合同部署在当地的加纳切区块链环境中。

要启动本地节点服务器,请运行以下命令,这将在我们的浏览器中启动 pet store 页面:

npm run dev

编写活动分散应用程序

我们刚刚在本地加纳切区块链环境中部署了智能合同。现在,我们将开始编写 UI 代码,通过 RPC 调用来触发智能合约功能。本章的源代码可在 https://bit.ly/2X8xPBL 获得。

选择 web3 提供商

当我们加载一个网页时,我们需要连接到一个 web3 提供者。如果您已经安装了诸如 MetaMask 之类的提供程序,则可以使用正确的提供程序选项,如下所示:

App.web3Provider = web3.currentProvider;

在我们的众筹示例中,为了简单起见,我们将直接连接到我们的本地 Ganache 服务器,如下所示:

App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');

正在加载帐户信息

为了加载帐户,我们定义了一个包含空内容的下拉菜单,如下所示:

<div class="form-group">
                <label for="exampleFormControlSelect1">Accounts</label>
                <select class="form-control" id="accts">
                </select>
              </div>

当我们加载页面时,我们将使用web3.eth.accounts来获取所有 10 个默认帐户。请注意,第一个帐户的余额为 99.84;这是因为我们使用了第一个帐户作为所有者帐户来部署合同,并消耗了一些汽油作为交易费用,如以下代码所示:

    web3.eth.accounts.forEach( function(e){
        $('#accts').append($('<option>', {
            value:e,
            text : e + " (" +web3.fromWei(web3.eth.getBalance(e), "ether") + " ether)"
        }));
})

帐户加载后,将显示如下:

正在加载项目信息

在众筹中,我们定义了一个包含筹款信息的项目结构,如下所示:

struct Project {
        address addr;
        string name;
        string website;
        uint totalRaised;
        uint minimumToRaise;
        uint currentBalance;
        uint deadline;
        uint completeAt;
        Status status;  
    }

让我们在 HTML 中定义一些相关信息,例如:

<table class="table table-hover table-striped">
                  <tbody>
                    <tr>
                      <th scope="row">address</th>
                      <td><span class="text-info" id="address"></span
</td>
                    </tr>
                    <tr>
                      <th scope="row">name</th>
                      <td><span class="text-info" id="name"></span></td>
                    </tr>
                    <tr>
                        <th scope="row">website</th>
                        <td><span class="text-info" id="website"></span></td>
                    </tr>
                    <tr>
                        <th scope="row">totalRaised</th>
                        <td><span class="text-info" id="totalRaised"></span></td>
…
                  </tbody>
                </table>

CrowdFunding.deployed()函数将创建一个代表由CrowdFunding管理的默认地址的CrowdFunding实例。这里的代码向我们展示了如何显示项目信息:

    App.contracts.CrowdFunding.deployed().then(function(instance) {
      crowdFundingInstance = instance;
      return crowdFundingInstance.project();
    }).then(function(projectInfo) {
        $("#address").text(projectInfo[0].toString());
        $("#name").text(projectInfo[1]);
        $("#website").text(projectInfo[2]);
        $("#totalRaised").text(projectInfo[3].toString());
        ..
        if(projectInfo[6].toString().length>0) {
          var deadline = new
Date(Number(projectInfo[6].toString())*1000);
          deadlineDate = moment(deadline).format("YYYY-MM-DD h:mm:ss");
          $("#deadline").text(deadlineDate);
        }
        if(projectInfo[7].toString().length>0 && projectInfo[7].toString()!='0') {
          console.log(projectInfo[7].toString());
          var completeAt = new Date(Number(projectInfo[7].toString())*1000);
          completeAtDate = moment(completeAt).format("YYYY-MM-DD h:mm:ss");
          $("#completeAt").text(completeAtDate);
        }   
    }).catch(function(error) {
..
    });

结果将显示如下:

处理资金职能

要筹集资金,需要调用基金函数,这个函数在我们的众筹智能合约中有定义。在我们的网页中,我们使用 HTML 范围输入滑块组件来提供基金金额,如下所示:

<form id="fund-form" method="post" role="form" style="display: block;">
                                      <div class="form-group row">
                                          <div class="row">
                                              <div class="col-lg-12">
                                                  <input type="range" name="ageInputName" id="ageInputId" value="0" min="1" max="100" oninput="ageOutputId.value = ageInputId.value">
                                                  <div style="display: inline;"><output name="ageOutputName" id="ageOutputId">0</output> <span>ether</span></div>             
                                              </div>
                                            </div>
                                       </div>
                                      <div class="form-group">
                                        <div class="row">
                                          <div class="col-lg-12">
                                              <button type="button" id="fundBtn" class="btn btn-primary pull-left">Submit</button>
                                          </div>
                                        </div>
                                      </div>                              
                                  </form>

Crowdfunding fund功能是应付回退功能;因此,我们需要从 UI 中传递msg.sendermsg.value来调用它,如下所示。

   function fund() public atStage(Status.Fundraising) payable {
        contributions.push(
            Contribution({
                addr: msg.sender,
                amount: msg.value
                })
            );
……
}

您可以如下定义发送地址和值参数:

  handleFund: function(event) {
    event.preventDefault();
    var fundVal =  $('#ageOutputId').val();
    var selectAcct = $('#accts').find(":selected").val();
    $("#displayMsg").html("");
    App.contracts.CrowdFunding.deployed().then(function(instance) {
      return instance.fund({ from: selectAcct, value:web3.toWei(fundVal, "ether"), gas:3500000});
    }).then(function(result) {
      App.loadProject();
    }).catch(function(err) {
      console.error(err);
      $("#displayMsg").html(err);
    });
  },

一旦我们得到结果,我们将调用loadProject函数来刷新项目信息。我们可以看到当前结余资金增加了,如下图所示:

达到检查目标

一旦达到筹资目标,众筹所有者将通过运行checkGoalReached方法收集所有资金的。

HTML 只是一个简单的按钮,如下面的代码所示:

<button type="button" id="checkGoal" class="btn btn-success">CheckGoal</button>

类似于基金函数,我们使用以下代码在 JavaScript 中调用智能合约:

instance.checkGoalReached({ from: selectAcct, gas:3500000});

下面是详细的逻辑:

  handleCheckGoal: function(event) {
    event.preventDefault();
    $("#displayMsg").html("");
    var selectAcct = $('#accts').find(":selected").val();
    App.contracts.CrowdFunding.deployed().then(function(instance) {
      return instance.checkGoalReached({ from: selectAcct, gas:3500000});
    }).then(function(result) {
      App.loadProject();
    }).catch(function(err) {
      console.error(err);
      $("#displayMsg").html(err);
    });
  },

结果将显示如下:

如果您遵循了整个示例并运行了这一步,那么恭喜您!你现在能够编写和运行一个众筹 DApp。

摘要

在这一章中,我们学习了 DApp 基础知识,现在我们了解了 web3.js API。通过运行 Ganache 作为我们的本地以太坊环境,我们可以使用 Truffle 开发工具来创建一个众筹项目并编写一个 DApp 组件。最后,我们部署并推出了众筹 DApp。在下一章中,我们将开始探索最流行的企业区块链——Hyperledger Fabric。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组