Hyperledge 区块链开发教程(五)

原文:zh.annas-archive.org/md5/7f932e9670331dae388d1a76f72881d8

译者:飞龙

协议:CC BY-NC-SA 4.0

第十五章:构建以太坊区块链应用程序

在上一章中,我们回顾了智能合约的基本特性以及如何编写一个众筹智能合约示例。在我们将智能合约部署到区块链之后,我们需要编写 Web 应用程序来与智能合约交互。以太坊区块链通过调用智能合约函数和获取器提供了 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该包提供了与以太坊区块链和智能合约交互的 APIgetBalance, sendTransaction, coinbase, getBlockNumber, getAccounts
web3-shh该包提供了与 whisper 协议进行广播交互的 API
web3.shh.post({
        symKeyID: identities[0],
        topic: '0xffaadd11',
        payload: '0xffffffdddddd1122'
    }).then(h => console.log(`Message with hash ${h} was successfuly sent`))

|

web3-bzz该包提供了与以太坊疯蜂网络(Ethereum swarm)交互的 API,即去中心化文件存储平台web3.bzz.currentProvider``web3.bzz.download(bzzHash [, localpath])
web3-utils该包提供了一组针对以太坊 DApp 和其他 web3.js 包的实用函数web3.utils.toWei(number [, unit])``web3.utils.isAddress(address)

提供者

提供者抽象了与以太坊区块链进行通信的连接。它将向区块链发出查询并发送交易。web3 提供了 JsonRpcProviderIpcProvider,允许您连接到本地或远程以太坊节点,包括 Mainnet、Ropsten 测试网、Kovan 测试网、Rinkeby 测试网和自定义的远程过程调用(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

Truffle 是一个以太坊 DApp 端到端开发工具,提供了一个开发环境,用于编写、编译和部署测试智能合约和 DApps。您可以为前端编写 HTML、CSS 和 JavaScript;Solidity 用于智能合约,并使用 web3.js API 与 UI 和智能合约交互。Truffle Boxes 提供了有用的样板文件,其中包含了有用的模块、Solidity 合约和库、前端代码以及许多其他有用的文件。Truffle Boxes 帮助开发人员快速启动他们的 DApp 项目。

Truffle 命令行使用以下格式:

  • truffle [command] [options]

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

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

Ganache

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

  • 显示区块链日志输出

  • 提供高级挖掘控制

  • 内置区块浏览器

  • 以太坊区块链环境

  • Ganache 有一个桌面应用程序和一个命令行工具

这是 Ganache 桌面版的外观:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

命令行使用以下格式:

ganache-cli <options>

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

选项描述
-a 或 --accounts启动时要生成的账户数量。
-e 或 --defaultBalanceEther配置默认的测试账户以太币数量。默认值为 100
-b 或 --blockTime指定以秒为单位的区块时间作为挖掘间隔。如果未指定此选项,当调用事务时,Ganache 将立即挖掘新块。
-h 或 --host 或 --hostname指定要侦听的主机名。默认值为 127.0.0.1
-p 或 --port指定端口号。默认值为 8545
-g 或 --gasPrice以 Wei 指定气价(默认为 20000000000)。
-l 或 --gasLimit区块气体限制(默认为 0x6691b7)。
--debug为调试目的显示 VM 操作码。
-q 或 --quiet不记录任何日志运行 ganache-cli

设置以太坊开发环境

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

安装 Truffle

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

npm install -g truffle

安装 Ganache

打开命令行并安装 Ganache 的命���行接口,如下所示:

npm install -g ganache-cli

创建一个 Truffle 项目

要初始化一个新的 DApp 项目,我们可以运行 truffle init命令来初始化一个空的 Truffle 项目。这将创建 DApp 目录结构,包括应用程序、合约和测试以及 Truffle 配置。由于 Truffle Boxes 提供了许多可用的模板,在我们的 DApp 示例中,我们将使用宠物商店模板——一个 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命令的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样可以在本地 Ganache 区块链环境中部署我们的众筹智能合约。

要启动本地节点服务器,请运行以下命令,这将在我们的浏览器中打开宠物商店页面:

npm run dev

编写众筹去中心化应用

我们刚刚在我们的本地 Ganache 区块链环境上部署了我们的智能合约。现在,我们将开始编写 UI 代码,通过 RPC 调用触发智能合约函数。本章的源代码可在 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) {
..
    });

结果将如下显示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

处理基金功能

要筹集资金,我们需要调用 fund 函数,在我们的众筹智能合约中定义。在我们的网页中,我们使用 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

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

HTML 仅为一个简单的按钮,如下所示的代码:

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

与 fund 函数类似,我们使用以下代码在 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。

第十六章:使用 Hyperledger Fabric 探索企业区块链应用

在前一章中,我们讨论了以太坊区块链。以太坊是一个公共区块链;任何人都可以阅读区块链数据并进行合法的更改。任何人都可以向链中写入一个新区块。以太坊是完全自治的,不受任何人控制。智能合约用 Solidity 语言编写,作为一个几乎图灵完备的语言,可以在以太坊虚拟机 (EVM)上执行各种交易。开发人员可以使用这些智能合约构建和部署去中心化应用 (DApps)。以太是以太坊中的加密货币,作为执行以太坊中的每个操作的燃料,包括执行智能合约、DApps、交易等。然而,这并不是构建区块链的唯一方法。

可以创建需要在区块链节点中建立访问控制层以读取区块链上受限信息的区块链。这将限制网络中能够参与共识机制中交易的参与者数量。这种区块链称为许可区块链。

下表显示了公共和许可区块链之间的区别:

无需许可许可

| 公共 | 每个人都可以阅读交易数据。每个人都可以验证区块中的交易。

  • 速度: 差

  • 一致性: 工作量证明

  • 区块链: 比特币、以太坊

  • 代币: 需要

| 每个人都可以阅读交易数据。只有预定义的用户可以验证交易。

  • 速度: 良好

  • 一致性: 工作量证明

  • 区块链: 卡斯帕后的以太坊

  • 代币: 需要

|

| 私有 | 只有预定义的用户可以读取交易数据。只有预定义的用户可以验证交易。

  • 速度: 良好

  • 一致性: 联邦拜占庭协议 (FBA)

  • 代币: 不需要

只有预定义的用户可以读取交易数据。只有有权的用户可以验证交易。

  • 速度: 良好

  • 一致性: 实用拜占庭容错算法 (PBFT)

  • 区块链: Hyperledger Fabric

  • 代币: 不需要

|

Hyperledger Fabric 是一种私有的许可区块链之一。在本章中,我们将讨论 Hyperledger Fabric 区块链。

Hyperledger Fabric 是一种开源企业区块链技术。该项目最初由 IBM 和数字资产贡献。Hyperledger Fabric 是 Linux 基金会托管的区块链项目之一。Hyperledger Fabric 中的智能合约称为链码,它定义了 Fabric 应用的业务逻辑。模块化的架构设计使 Fabric 能够支持高度的机密性、恢复能力、灵活性和可扩展性。Fabric 中的组件,如共识和成员服务,可以插拔式部署。

在本章中,我们将涵盖以下主题:

  • 发行声明

  • 设置 Hyperledger Fabric 环境

  • 编写链码

  • 配置 Hyperledger Fabric

发行索赔

在本节中,我们将探讨并实施一个发行索赔的用例。

没有人希望有保险索赔,但当事情出错和发生事故时,这可能会导致财务损失。这些损失将由您的保险单承担。传统的保险理赔流程几十年来都保持不变,因为流程中存在许多关键问题,包括虚假索赔、欺诈检测、缓慢和繁琐的索赔处理、人为错误、不佳的客户体验以及再保险中信息流的低效。

使用区块链,分类账中的交易记录是不可变的,只有当所有各方都同意时,状态数据才能更新。区块链中的记录可以实时共享。这使得保险公司可以迅速行动,因为大部分用于索赔验证的必要信息可以在短时间内处理。保险公司可以跟踪区块链中资产数据的使用情况。文件工作可以被消除,客户可以通过网络应用提交索赔。

让我们看一下保险索赔流程,如下截图所示。为了演示目的,我们简化了索赔流程,因为在实际应用中可能会更加复杂:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于上述流程,步骤如下:

  1. 被保险人向经纪人报告索赔

  2. 经纪人提供请求的信息

  3. 经纪人向发行人提交索赔

  4. 发行人确认索赔

  5. 发行人处理并批准索赔

配置 Hyperledger Fabric 环境

到目前为止,我们已经了解了 Hyperledger Fabric 的关键概念。在本节中,我们将建立一个 Hyperledger Fabric 开发环境。在继续进行安装步骤之前,让我们看一下 fabric 安装的先决条件。

安装先决条件

安装所需的开发工具的先决条件如下:

Ubuntu Linux 14.04 / 16.04 LTS(均为 64 位),或 macOS 10.12Docker Engine:版本 17.03 或更高
Docker-Compose:版本 1.8 或更高Node:8.9 或更高(注意不支持 9 版本)
npm:v5.xgit:2.9.x 或更高
Python:2.7.x

我们将在开发环境中使用 Ubuntu。我们可以使用以下命令下载先决条件:

curl -O https://hyperledger.github.io/composer/latest/prereqs-ubuntu.sh
chmod u+x prereqs-ubuntu.sh
./prereqs-ubuntu.sh

它可能在执行过程中提示您输入密码,因为它在执行过程中使用了sudo

安装 Hyperledger Fabric

创建并转到名为insurance-claim的项目文件夹,如下所示:

mkdir ~/insurance-claim && cd ~/insurance-claim

输入以下命令以安装 Hyperledger Fabric 特定于平台的二进制文件:

curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/release-1.3/scripts/bootstrap.sh | bash

执行此命令后,它会在bin文件夹中下载以下特定于平台的二进制文件,该文件夹位于fabric-samples文件夹下。您可以将fabric-samples/bin设置为PATH变量,如下所示:

export PATH=<path to download location>/bin:$PATH

我们还从本书的代码文件中提供 bootstrap-hyperledger.sh,您可以从 Packt 网站下载。一旦获得文件,您可以直接运行以下脚本,它将创建一个 bin 文件夹并将二进制文件下载到此文件夹中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些组件将成为我们的 Hyperledger Fabric 网络的一部分。

编写链码

Chaincode 类似于智能合约。它定义并执行由特定网络中的授权参与者调用的业务逻辑。链码是用 Go 或 Node.js 编写的。在我们的示例中,我们将使用 Go。

有许多 IDE 和工具支持 Golang。以下是一些与 Golang 配合得很好的流行 IDE。

开发工具

有各种工具支持 Go 开发。一些流行的 IDE 在以下部分列出。

LiteIDE

LiteIDE 是专为 Golang 设计的开源 Go IDE。有许多适用于 Go 开发人员的有用功能,包括可配置的代码编辑器、自定义构建命令、许多构建选项和 Golang 支持。

JetBrains Gogland

Gogland 拥有强大的内置自动完成引擎、错误检测、代码重构工具等等。

Visual Studio Code

您可以在 Visual Studio Code 中安装 Go 扩展。它提供代码提示和调试代码的能力。

在本章中,我们将使用 LiteIDE 来开发我们的链码。请按照官方 LiteIDE 安装指南设置您的本地 IDE 环境,该指南可从以下链接获取:

github.com/visualfc/liteide/blob/master/liteidex/deploy/welcome/en/install.md.

Chaincode 关键概念和 API

Fabric 链码中有三个重要的函数:InitInvokeQuery。每个链码程序都必须实现链码接口,如下所示:

type Chaincode interface {
    Init(stub ChaincodeStubInterface) pb.Response
    Invoke(stub ChaincodeStubInterface) pb.Response
}

Init()在应用程序初始化其内部数据以供其他链码函数使用时被调用。当链码接收到实例化或升级事务时,它将被触发。

当应用程序客户端提出更新或查询事务时,将调用Invoke()函数。

当链码查询链码状态时,将调用Query()。Hyperledger Fabric 使用 LevelDB(键值存储)作为存储 world;state 数据的默认数据库。您可以使用键来获取当前账本状态数据。查询函数通过传入键值读取链码状态的值。

shim 包提供了用于链码访问其状态变量、事务上下文和调用其他链码的 API。

ChaincodeStubInterface 是其中一个重要的接口。它提供了各种函数,让您可以查询、更新和删除账本中的资产。这些函数如下所示:

GetState(key string) ([]byte, error)GetState 函数从账本中返回指定键的值。
PutState(key string, value []byte) errorPutState将指定的键和值放入交易的写集作为数据写提案
DelState(key string) errorDelState记录了交易提案的写集中将要删除的指定键

定义发行理赔

让我们写一段链码。打开 LiteIDE 并创建一个名为claimcontract.go的新文件,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在保险理赔使用案例分析中,我们分析了发行理赔流程中的参与者。我们需要定义一个链码的参与者有三个:被保险人、经纪人和保险人,如下例所示:

type Insuree struct {
         Id           string `json:"id"`
         FirstName    string `json:"firstName"`
         LastName     string `json:"lastName"`
         SSN          string `json:"ssn"`
         PolicyNumber string `json:"policyNumber"`
}

Insuree中,我们定义了IdfirstnameLastNameSSNpolicyNumber

在 Go 语言中,字段名的第一个字母可以是大写或小写。当我们需要一个导出字段对任何代码使用时,它需要是一个大写字母。你可以使用 JSON 包中的编码将数据解析为结构体,定义 JSON 中的字段名为firstName,如下所示:

type Member struct {
  Name string `json:"member_name"`
}

经纪人和保险人的数据模型类似,只是类型不同。我们将其定义如下:

type Company struct {
         Id   string `json:"id"`
         Type string `json:"type"`
         Name string `json:"name"`
}

在发行理赔流程中,Insuree初始化理赔请求。理赔文件将在区块链中跟踪流程的每一步。它记录了所有必要的信息,包括状态、用户理赔描述、insueeIdbrokerIdinsurerId、每个步骤的处理时间以及授权方输入的评论,如下例所示:

type Claim struct {
         Id        string `json:"id"`        //the fieldtags are needed to keep case from bouncing around
         Desc      string `json:"desc"`      //claim description
         Status    string `json:"status"`    //status of claim
         InsureeId string `json:"insureeId"` //InsureeId
         BrokerId  string `json:"brokerId"`  //BrokerId
         InsurerId string `json:"insurerId"` //InsurerId
         Comment   string `json:"comment"`   //comment
         ProcessAt string `json:"processAt"` //processAt
}

初始化链码

接下来,我们将实现Init函数。Init()允许链码初始化被保险人数据以开始理赔请求。在我们的情况下,我们将设置并注册被保险人的个人信息,如下所示:

func (c *ClaimContract) Init(stub shim.ChaincodeStubInterface) pb.Response {
         args := stub.GetStringArgs()
         if len(args) != 5 {
                 return shim.Error("Incorrect arguments. Expecting a key and a value")
         }
         insureeId := args[0]
         firstName := args[1]
         lastName := args[2]
         ssn := args[3]
         policyNumber := args[4]
         insureeData := Insuree{
                 Id:           insureeId,
                 FirstName:    firstName,
                 LastName:     lastName,
                 SSN:          ssn,
                 PolicyNumber: policyNumber}
         insureeBytes, _ := json.Marshal(insureeData)
         err := stub.PutState(insureeId, insureeBytes)
         if err != nil {
                 return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
         }
         return shim.Success(nil)
}

ChaincodeStubInterface.GetStringArg获取输入参数。它期望参数的长度应为 5。拥有所有必需的保险人数据后,我们构建保险人 JSON 数据,并将其编码为 JSON 字节字符串–json.Marshal(insureeData)。然后,我们将键和值存储在分类账上。如果一切顺利,它将返回一个成功的peer.Response对象给 Fabric 的client.c

调用链码

要触发调用函数,可以调用链码应用函数的名称,并将shim.ChaincodeStubInterface作为签名传递。在保险理赔案例中,我们定义了几个函数来支持我们的用例,例如:

AddCompany, ReportLost, RequestedInfo, SubmitClaim, ConfirmClaimSubmission, ApproveClaim.

我们还定义了一个查询来跟踪当前理赔请求和getHistory来获取所有历史理赔交易记录,如下所示:

func (c *ClaimContract) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
         function, args := stub.GetFunctionAndParameters()
         if function == "AddCompany" {
                 return c.AddCompany(stub, args)
         } else if function == "ReportLost" {
                 return c.ReportLost(stub, args)
         } else if function == "RequestedInfo" {
                 return c.RequestedInfo(stub, args)
         } else if function == "SubmitClaim" {
                 return c.SubmitClaim(stub, args)
         } else if function == "ConfirmClaimSubmission" {
                 return c.ConfirmClaimSubmission(stub, args)
         } else if function == "ApproveClaim" {
                 return c.ApproveClaim(stub, args)
         } else if function == "query" {
                 return c.query(stub, args)
         } else if function == "getHistory" {
                 return c.getHistory(stub, args)
         }

         return shim.Error("Invalid function name")
}

AddCompany

AddCompany类似于我们在初始化步骤添加被保险人的方式。链码可以通过此函数注册经纪人和保险人。公司类型可以是经纪人保险人,如下所示:

func (c *ClaimContract) AddCompany(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         id := args[0]
         name := args[1]
         companyType := args[2]
         companyData := Company{
                 Id:   id,
                 Type: companyType,
                 Name: name}
         companyBytes, _ := json.Marshal(companyData)
         stub.PutState(id, companyBytes)
         return shim.Success(companyBytes)
}

报告丢失

在此步骤中,投保人向经纪人报告丢失物品,并提供所有索赔信息。此函数还在processAt字段记录当前系统处理时间。currentts.Format(2006-01-02 15:04:05)是一个 Go 自定义格式;它将当前时间转换为 YYYY-MM-dd hh:mm:ss 格式,如以下示例所示:

func (c *ClaimContract) ReportLost(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         claimId := args[0]
         desc := args[1]
         insureeId := args[2]
         brokerId := args[3]
         currentts := time.Now()
         processAt := currentts.Format("2006-01-02 15:04:05")
         //initialized claim
         claimData := Claim{
                 Id:        claimId,
                 Desc:      desc,
                 Status:    "ReportLost",
                 InsureeId: insureeId,
                 BrokerId:  brokerId,
                 InsurerId: "",
                 Comment:   "",
                 ProcessAt: processAt}
         claimBytes, _ := json.Marshal(claimData)
         stub.PutState(claimId, claimBytes)
         return shim.Success(claimBytes)
}

RequestedInfo

投保人报告损失后,下一步是经纪人返回RequestedInfo,如下所示:

func (c *ClaimContract) RequestedInfo(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         return c.UpdateClaim(stub, args, "RequestedInfo")
}
func (c *ClaimContract) UpdateClaim(stub shim.ChaincodeStubInterface, args []string, currentStatus string) pb.Response {
         claimId := args[0]
         comment := args[1]
         claimBytes, err := stub.GetState(claimId)
         claim := Claim{}
         err = json.Unmarshal(claimBytes, &claim)
         if err != nil {
                 return shim.Error(err.Error())
         }
         if currentStatus == "RequestedInfo" && claim.Status != "ReportLost" {
                 claim.Status = "Error"
                 fmt.Printf("Claim is not initialized yet")
                 return shim.Error(err.Error())
         } else if currentStatus == "SubmitClaim" && claim.Status != "RequestedInfo" {
                 claim.Status = "Error"
                 fmt.Printf("Claim must be in RequestedInfo status")
                 return shim.Error(err.Error())
         } else if currentStatus == "ConfirmClaimSubmission" && claim.Status != "SubmitClaim" {
                 claim.Status = "Error"
                 fmt.Printf("Claim must be in Submit Claim status")
                 return shim.Error(err.Error())
         } else if currentStatus == "ApproveClaim" && claim.Status != "ConfirmClaimSubmission" {
                 claim.Status = "Error"
                 fmt.Printf("Claim must be in Confirm Claim Submission status")
                 return shim.Error(err.Error())
         }
         claim.Comment = comment
         if currentStatus == "RequestedInfo" {
                 insurerId := args[2]
                 claim.InsurerId = insurerId
         }
         currentts := time.Now()
         claim.ProcessAt = currentts.Format("2006-01-02 15:04:05")
         claim.Status = currentStatus
         claimBytes0, _ := json.Marshal(claim)
         err = stub.PutState(claimId, claimBytes0)
         if err != nil {
                 return shim.Error(err.Error())
         }
         return shim.Success(claimBytes0)
}

由于剩余的流程函数非常相似,我们将UpdateClaim定义为一个通用函数,与剩余步骤共享。

UpdateClaim函数首先从输入参数中获取claimId和当前参与者评论。然后,它查询并从区块链中获取索赔以解码索赔数据,并将其转换为 JSON 字符串—json.Unmarshal(claimBytes, &claim)

在更新索赔内容之前,它将验证输入的索赔状态并确保其处于预期步骤上。如果一切顺利,我们将更新索赔状态、参与者评论和处理时间。

最后,我们使用claimId作为键在账本上更新索赔数据。

提交索赔,确认提交索赔,批准索赔

提交、确认和批准索赔与RequestedInfo非常相似,并且这些步骤由UpdateClaim函数调用。只有评论、状态和处理时间值不同。

查询

查询是您从分类帐中读取数据的方式。查询函数用于查询链码的状态。由于我们将索赔数据存储在以claimId为键的分类帐中,为了读取当前索赔,我们调用GetState,传递claimId作为键,如下所示:

func (c *ClaimContract) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         var ENIITY string
         var err error
         if len(args) != 1 {
                 return shim.Error("Incorrect number of arguments. Expected ENIITY Name")
         }
         ENIITY = args[0]
         Avalbytes, err := stub.GetState(ENIITY)         if err != nil {
                 jsonResp := "{\"Error\":\"Failed to get state for " + ENIITY + "\"}"
                 return shim.Error(jsonResp)
         }
         if Avalbytes == nil {
                 jsonResp := "{\"Error\":\"Nil order for " + ENIITY + "\"}"
                 return shim.Error(jsonResp)
         }
         return shim.Success(Avalbytes)
}

getHistory

正如其名称所示,gethistory函数读取一个键的所有历史值记录的索赔,以及TxId和索赔值。

首先我们定义了AuditHistory结构,其中包含TxId和值。GetHistoryForKey通过resultsIterator返回结果列表,其中包含所有历史交易记录。我们遍历这些记录并将它们添加到一个AuditHistory数组中。后来,我们将其转换为 JSON 字节并作为响应发送回来,如下所示:

func (c *ClaimContract) getHistory(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         type AuditHistory struct {
                 TxId  string `json:"txId"`
                 Value Claim  `json:"value"`
         }
         var history []AuditHistory
         var claim Claim
         if len(args) != 1 {
                 return shim.Error("Incorrect number of arguments. Expecting 1")
         }
         claimId := args[0]
         fmt.Printf("- start getHistoryForClaim: %s\n", claimId)

         // Get History
         resultsIterator, err := stub.GetHistoryForKey(claimId)
         if err != nil {
                 return shim.Error(err.Error())
         }
         defer resultsIterator.Close()

         for resultsIterator.HasNext() {
                 historyData, err := resultsIterator.Next()
                 if err != nil {
                          return shim.Error(err.Error())
                 }
                 var tx AuditHistory
                 tx.TxId = historyData.TxId
                 json.Unmarshal(historyData.Value, &claim)
                 tx.Value = claim              //copy claim over
                 history = append(history, tx) //add this tx to the list
         }
         fmt.Printf("- getHistoryForClaim returning:\n%s", history)

         //change to array of bytes
         historyAsBytes, _ := json.Marshal(history) //convert to array of bytes
         return shim.Success(historyAsBytes)
}

这涵盖了我们的发行索赔链码。我们将在下一节学习有关 Hyperledger Fabric 配置的信息。

配置 Hyperledger Fabric

保险索赔网络中有三个实体—投保人、经纪人和保险公司。所有这些参与者都将在 Fabric 中注册为对等节点。以下表格描述了三个对等角色和 MSP 信息:

用户 ID角色组织 MSP ID
user_001投保人Org1MSP
broker_001经纪人Org2MSP
insurer_001保险公司Org3MSP

我们有一个保险人加入了具有 MSP ID org1 的组织,一个经纪人加入了具有 MSP ID org2 的组织,以及一个保险人加入了具有 MSP ID org3 的组织。为了引导 fabric 网络,我们需要首先为我们需要运行的三个组件生成加密材料。

生成证书

我们需要定义 crypto-config.yaml 并使用 cryptogen 工具为每个节点生成证书。Cryptogen 可在工具镜像中获得。crypto-config.yaml 包含以下信息:

  • OrdererOrgs:管理排序节点的组织定义

  • PeerOrgs:管理对等节点的组织定义

OrdererOrgs 包含关于集群中排序节点的以下信息:

  • 名称:订单者的名称

  • :订单者的域 URL;在我们的案例中,它是 ic.com

  • 主机名:订单者的主机名

这里是一个例子:

OrdererOrgs:
  - Name: Orderer
    Domain: ic.com
    Specs:
      - Hostname: orderer

PeerOrgs 包含关于集群中对等节点的以下信息:

  • 名称:组织的名称;我们有三个不同的组织:Org1Org2Org3

  • 模板计数:组织的节点数

  • 用户计数:组织的用户数

这里是一个例子:

PeerOrgs:
  # ---------------------------------------------------------------------------
  # Org1
  # ---------------------------------------------------------------------------
  - Name: Org1
    Domain: org1.ic.com
    Template:
      Count: 2
    Users:
      Count: 1
  # ---------------------------------------------------------------------------
  # Org2
  # ---------------------------------------------------------------------------
  - Name: Org2
    Domain: org2.ic.com
    Template:
      Count: 2
    Users:
      Count: 1
  # ---------------------------------------------------------------------------
  # Org3
  # ---------------------------------------------------------------------------
  - Name: Org3
    Domain: org3.ic.com
    Template:
      Count: 2
    Users:
      Count: 1

以下是用于生成加密材料的命令:

cryptogen generate --config=./crypto-config.yaml

运行 cryptogen 工具后,您应该在控制台上看到以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

生成订单者创世区块

生成证书后,该过程的下一步是生成订单者创世区块。configtxgen 命令允许用户创建和检查通道配置。configtxgen 工具的输出主要由 configtx.yaml 的内容控制,如下所示:

Profiles:
    ICOrgsOrdererGenesis:
        Orderer:
            <<: *OrdererDefaults
            Organizations:
                - *OrdererOrg
        Consortiums:
            InsuranceClaimConsortium:
                Organizations:
                    - *Org1
                    - *Org2
                    - *Org3
    ICOrgsChannel:
        Consortium: InsuranceClaimConsortium
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1
                - *Org2
                - *Org3
Organizations:
    - &OrdererOrg
        Name: OrdererOrg
        ID: OrdererMSP
        MSPDir: crypto-config/ordererOrganizations/ic.com/msp
    - &Org1
        Name: Org1MSP
        ID: Org1MSP
        MSPDir: crypto-config/peerOrganizations/org1.ic.com/msp
        AnchorPeers:
            - Host: peer0.org1.ic.com
              Port: 7051
    - &Org2
        Name: Org2MSP
        ID: Org2MSP
        MSPDir: crypto-config/peerOrganizations/org2.ic.com/msp
        AnchorPeers:
            - Host: peer0.org2.ic.com
              Port: 7051         
    - &Org3
        Name: Org3MSP
        ID: Org3MSP
        MSPDir: crypto-config/peerOrganizations/org3.ic.com/msp

        AnchorPeers:
            - Host: peer0.org3.ic.com
              Port: 7051              
Orderer: &OrdererDefaults
    OrdererType: solo
    Addresses:
        - orderer.ic.com:7050
    BatchTimeout: 2s
    BatchSize:
        MaxMessageCount: 10
        AbsoluteMaxBytes: 20 MB
        PreferredMaxBytes: 512 KB
    Kafka:
        Brokers:
            - 127.0.0.1:9092
    Organizations:
Application: &ApplicationDefaults

    Organizations:

我们在 configtx 文件的 Organizations 部分定义了三个组织;我们指定了每个组织的名称、IDMSPDirAnchorPeersMSPDir 描述了 cryptogen 生成的输出 MSP 目录。AnchorPeers 指向节点的主机和端口。它更新事务以便在不同组织的节点之间启用通信,并找到通道的所有活动参与者,如下所示:

configtxgen -profile ICOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block

类似以下的输出将显示在控制台上:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

生成通道配置事务

configtxgen 通过执行通道配置事务将通道创建事务写入 channel.tx,如下所示:

configtxgen -profile ICOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID icchannel

类似以下的输出将显示在控制台上:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

执行通道配置事务的输出

Hyperledger Fabric Docker 组合器配置文件概述

Hyperledger Fabric 利用 Docker compose 来定义 fabric 应用服务。docker-compose-cli.yaml 服务部分是定义所有对等服务和相关容器的地方。Hyperledger Fabric 的 first-network 提供了一个 .yaml 模板,帮助您快速开始从头创建 yaml 文件:

github.com/hyperledger/fabric-samples/tree/release-1.2/first-network

docker-compose-cli.yaml 中,我们定义了以下信息:

  • networks:定义区块链网络名称。在我们的案例中,它是 icn

  • services:定义所有对等服务和相关 Docker 容器

  • cli:定义了用于替代 SDK 客户端的 Cli 容器,并设置了 Docker compose 命令行行为的环境变量

这是网络和服务部分的示例配置:

networks:
  icn:
services:
  orderer.ic.com:
    extends:
      file:   base/docker-compose-base.yaml
      service: orderer.ic.com
    container_name: orderer.ic.com
    networks:
      - icn
  peer0.org1.ic.com:
    container_name: peer0.org1.ic.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer0.org1.ic.com
    networks:
      - icn

如您所见,有一个文件扩展目录:base/docker-compose-base.yaml。Docker compose 支持使用 extends 字段为单个服务共享公共配置。我们稍后会详细讨论 docker-compose-base.yaml

这是 cli 部分的配置示例:

cli:
    container_name: cli
    image: hyperledger/fabric-tools
    tty: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer0.org1.ic.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/users/Admin@org1.ic.com/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash -c './scripts/script.sh ${CHANNEL_NAME} ${DELAY}; sleep $TIMEOUT'
    #for mapping the directories that are being used in the environment configurations
    volumes:
        - /var/run/:/host/var/run/
        - ./chaincode/:/opt/gopath/src/github.com/chaincode
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
        - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    depends_on:
      - orderer.ic.com
      - peer0.org1.ic.com
      - peer0.org2.ic.com
      - peer0.org3.ic.com
    networks:
      - icn

docker-compose 工具使用 docker-compose-cli.yaml 文件来初始化 fabric 运行时环境。以下是在使用 docker-compose-cli.yaml 文件时您将使用的一些最常见的命令:

TTYTTY 基本上意味着 控制台,我们将其设置为 true。
Image指向 fabric-tools 图像目录。
Environment指定环境变量,例如 GOPATH,由 cryptogen 工具生成的与 TLS 相关的文件位置。
working_dir设置了对等体的工作目录。
command指定容器启动时发出的命令。
volumes映射了在环境配置中使用的目录。
depends_on按依赖顺序启动服务。

然后生成四个 fabric-peer 事务节点容器,一个 fabric-order 订单节点容器和一个 fabric-tools cli 容器。

Fabric 项目目录结构

在我们的 Fabric 示例 first-network 中,项目结构类似于以下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如我们之前讨论的,docker-compose-cli.yaml 服务是从 base/docker-compose-base.yaml 继承的。有两个文件基目录:peer-base.yamldocker-compose-base.yaml

Docker-compose-base.yaml

此文件包含基本配置,包括每个对等体和订单容器的环境和端口号。这定义了保险索赔网络的整体拓扑,如下:

services:
  orderer.ic.com:
    container_name: orderer.ic.com
    image: hyperledger/fabric-orderer
    environment:
      - ORDERER_GENERAL_LOGLEVEL=debug
      - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
      - ORDERER_GENERAL_GENESISMETHOD=file
      - ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/orderer.genesis.block
      - ORDERER_GENERAL_LOCALMSPID=OrdererMSP
      - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp
      # enabled TLS
      - ORDERER_GENERAL_TLS_ENABLED=true
      - ORDERER_GENERAL_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key
      - ORDERER_GENERAL_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt
      - ORDERER_GENERAL_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric
    command: orderer
    volumes:
    - ../channel-artifacts/genesis.block:/var/hyperledger/orderer/orderer.genesis.block
    - ../crypto-config/ordererOrganizations/ic.com/orderers/orderer.ic.com/msp:/var/hyperledger/orderer/msp
    - ../crypto-config/ordererOrganizations/ic.com/orderers/orderer.ic.com/tls/:/var/hyperledger/orderer/tls
    ports:
      - 7050:7050

  peer0.org1.ic.com:
    container_name: peer0.org1.ic.com
    extends:
      file: peer-base.yaml
      service: peer-base
    environment:
      - CORE_PEER_ID=peer0.org1.ic.com
      - CORE_PEER_ADDRESS=peer0.org1.ic.com:7051
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.ic.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
    volumes:
        - /var/run/:/host/var/run/
        - ../crypto-config/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/msp:/etc/hyperledger/fabric/msp
        - ../crypto-config/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls:/etc/hyperledger/fabric/tls
    ports:
      - 7051:7051
      - 7053:7053
…..

Peer-base.yaml

这个文件为保险索赔 docker-compose-base.yaml 定义了对等网络配置,如下:

services:
  peer-base:
    image: hyperledger/fabric-peer
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=${COMPOSE_PROJECT_NAME}_icn
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_GOSSIP_USELEADERELECTION=true
      - CORE_PEER_GOSSIP_ORGLEADER=false
      - CORE_PEER_PROFILE_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start

在对等体中的命令让对等体安装系统链码和其他配置。

我们了解了关键的 Hyperledger Fabric 配置文件的概述,所以让我们使用以下代码启动我们的保险索赔网络:

      - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start

启动 Hyperledger Fabric 网络

现在,是时候启动我们的 Hyperledger Fabric 网络了。我们将使用 Docker 命令来初始化新的 Docker 组合:

docker-compose -f docker-compose-cli.yaml up

Docker 容器将触发在 docker-compose-cli.yaml 中定义的命令,如下所示:

command: /bin/bash -c './scripts/script.sh

script.sh 是一个包含一系列用于部署和测试命令的脚本。我们还在 utils.sh 中定义了一些业务特定的 shell 脚本函数。

创建一个通道

首先,我们需要创建一个通道以构建创世块。运行以下命令:

peer channel create -o orderer.ic.com:7050 -c icchannel -f ./channel-artifacts/channel.tx

这个命令从 channel.tx 中读取一个创世块,然后用于加入通道并创建 icchannel 通道。这是控制台上的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

控制台的输出,加入并创建通道

加入通道

在订购服务创建通道之后,我们可以将对等节点添加到通道中,如下所示:

peer channel join -b icchannel.block

这是控制台上的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将对等节点添加到通道中

我们可以看到 peer0.org1peer0.org2peer0.org3 加入了通道。

更新锚点

在我们开始与我们的发行索赔网络进行交互之前,我们需要完成的最后一个操作是更新锚定对等点。锚定对等点接收并广播事务更新给组织中的其他对等点。锚定对等点在网络中是可搜索的。因此,任何注册为锚定对等点的对等点都可以被订购对等点或任何其他对等点发现,例如:

peer channel update -f ./channel-artifacts/Org1MSPanchors.tx -c icchannel -o orderer.ic.com:7050 --tls true --cafile $ORDERER_CA

这是此步骤的控制台输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

被订购对等节点或任何其他对等节点发现

安装链码

在之前的步骤之后,我们几乎可以使用我们的发行索赔区块链应用程序了。但是首先,我们需要在我们的网络上安装 claimcontract.go 链码,如下所示:

peer chaincode install -n iccc -v 1.0 -l golang -p github.com/chaincode/claimcontract

我们将看到前述命令的输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

安装链码到我们的网络

实例化链码

在安装链码之后,我们需要实例化它。正如我们之前讨论的,我们将在 init() 链码中引入被保人。因此,我们需要传递必需的参数来创建一个被保人参与者,如下所示:

peer chaincode instantiate -o orderer.ic.com:7050 -C icchannel -n iccc -l golang -v 1.0 -c '{"Args":[ "user_001","John","Smith", "9999","4394497111/1"]}' -P "OR    ('Org1MSP.member'

这是此步骤的输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建一个被保人参与者

我们查询被保人以验证记录是否已在区块链中创建,如下所示:

peer chaincode query -C $CHANNEL_NAME -n iccc -c '{"Args":["query","user_001"]}'

从这个输出中我们可以看到,被保人(user_001)已经被添加到我们的区块链中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

被保人添加到我们的区块链中

调用添加经纪人

让我们将一个经纪人加入到我们的保险索赔区块链中,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["AddCompany","broker_001","BROKER","BNC Brokerage"]}'

这是结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将经纪人引入我们的保险索赔区块链

调用添加保险商

将最后一方保险商添加到保险索赔区块链中,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["AddCompany","insurer_001","INSURER","Western Insurance"]}'

显示的输出如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将最后一方保险商添加到保险索赔区块链中

调用 ReportLost

所有参与者都已加入网络,现在是开始保险索赔流程的时候了。被保险人向经纪人报告索赔,以下是调用’ReportLost’链代码的命令。

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["ReportLost","claim_001", "I was in Destiny shopping center and lost my IPhone 8", "user_001", "broker_001"]}

将显示以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

被保险人向经纪人报告索赔

调用 RequestedInfo

经纪人提供请求的信息,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["RequestedInfo","claim_001", "Broker processsed user John Smith report and sent Requested Info to user.", "insurer_001"]}'

将显示以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

提供所请求的信息

调用 SubmitClaim

经纪人向发行人提交索赔,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["SubmitClaim","claim_001", "Broker submitted a claim"]}'

将显示以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

向发行人提交索赔

调用 ConfirmClaimSubmission

发行人确认索赔,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["ConfirmClaimSubmission","claim_001", "Insurer received and confirmed a claim"]}'

将显示以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

确认索赔

调用 ApproveClaim

发行人处理并批准索赔,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["ApproveClaim","claim_001", "Insurer processed and approved the claim."]}'

将显示以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

处理并批准索赔

查询索赔历史

在发行人批准索赔后,整个流程完成,我们可以使用 Fabric API 查询索赔的整个生命周期,如下所示:

peer chaincode query -C icchannel -n iccc -c '{"Args":["getHistory","claim_001"]}'

从此查询获得的输出中,我们可以看到索赔请求的整个 Fabric 交易历史。

测试执行结束。

端到端测试执行

我们已经完成了保险索赔流程的每一步。为了简化整个端到端应用程序流程,您可以导航到insurance-claim文件夹,然后运行以下命令:

cd ~/insurance-claim
#change path if insurance-claim directory is different
export PATH=/home/ubuntu/insurance-claim/bin:$PATH 
./icn.sh -m up

输出结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简化整个端到端应用程序流程

最终输出如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

保险索赔端到端测试完成

摘要

在本章中,我们学习了 Hyperledger Fabric 的基础知识。在设置开发环境之后,我们为保险索赔用例编写了链代码。然后,我们学习了面料组合器配置。最后,我们对我们的保险索赔应用程序进行了端到端的面料测试执行。我们可以看到,使用 Hyperledger Fabric 实现保险索赔应用程序相当复杂。在下一章中,我们将学习如何使用 Hyperledger Composer 快速编写保险索赔应用程序。

第十七章:使用 Hyperledger Composer 实现业务网络

Hyperledger Composer 是一个高级工具集和框架,旨在快速构建和运行基于 Hyperledger Fabric 区块链的应用程序。

在前一章中我们了解了 Hyperledger Fabric,所以你已经知道开发基于 Fabric 的应用程序相当复杂,因为它需要处理许多网络级别的配置。

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

  • Hyperledger Composer — 一个快速概述

  • 设置 Hyperledger Composer 环境

  • 分析业务场景

  • 业务网络存档

  • 实现业务交易功能

Hyperledger Composer — 一个快速概述

Hyperledger Composer 是一组基于 JavaScript 的高级工具集和框架,可以简化并快速构建和运行基于 Hyperledger Fabric 区块链的应用程序。业务所有者和开发人员可以通过 composer 工具快速创建智能合约和应用程序。Composer 工具生成一个 RESTful 端点,用于与 Fabric 通道交互。与使用 Golang 编写链码不同,Composer 使用模型语言为区块链网络生成业务网络存档(.BNA)文件。

这是一个 Hyperledger Composer 解决方案架构的示例:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Hyperledger Composer 包含以下部分。

Yeoman 生成器

Yeoman 中的 npm 模块 generator-hyperledger-composer 用于创建 Hyperledger Composer 的模板。它支持并生成三种不同类型的模板:

  • CLI 应用程序

  • Angular 2 应用程序

  • 骨架业务网络

你可以使用 Yeoman 生成的 angular 骨架连接到 Hyperledger Composer REST 服务器。

Composer REST 服务器

Composer 的 REST 服务器利用一个独立的 Node.js 进程,并从部署的 composer 业务网络公开一组 RESTful API 端点。这些生成的 API 可以与 fabric 链码进行交互。侧面的代码然后可以触发 创建读取更新删除CRUD)资产、参与者和交易。

LoopBack 连接器

LoopBack 连接器利用 Node.js 的 LoopBack 框架来为业务网络中定义的资产、参与者和交易暴露 GET/POST/PUT/DELETE 操作。

JavaScript SDK

JavaScript SDK API 用于与部署的业务网络交互。它由客户端和管理 API 组成。

客户端 API 提供了从客户端应用程序查询、创建、更新和删除资源(资产和参与者),以及提交交易的功能。

管理 API 用于部署业务网络。

Composer playground

Hyperledger Composer playground 是一个基于浏览器的界面,用于创建和测试业务网络。你可以使用 playground 来构建和测试你的业务网络。

Composer-cli

Composer-cli 是一个命令行工具,可以让你部署和管理业务网络。

以下是一些命令的列表:

命令描述
composer archive create创建业务网络存档文件(nba)的命令。
composer archive list验证业务网络存档的内容。
composer card create从个别组件创建业务网络卡。
composer card delete从个别组件中删除业务网络卡。
composer card list列出存储在本地钱包中的所有业务网络卡。
composer network deploy将业务网络存档从本地磁盘部署到 Hyperledger Fabric 网络。
composer network list列出业务网络卡的详细信息。
composer network ping测试已部署业务网络的连接。

设置 Hyperledger Composer 环境

我们刚刚审查了 Hyperledger Composer 解决方案体系结构。在本节中,我们将设置 Hyperledger 开发环境。

安装先决条件

在安装 composer 工具之前,请确保按照 Hyperledger Fabric 环境设置 - 安装先决条件一节来获取所需的先决条件。

安装开发环境

以下是开发环境安装命令:

  • 安装 CLI 工具:
 npm install -g composer-cli@0.20
  • 安装composer-rest-server
 npm install -g composer-rest-server@0.20
  • 安装 Hyperledger Composer 生成器:
 npm install -g generator-hyperledger-composer@0.20
  • 安装 Yeoman:
 npm install -g yo
  • 安装游乐场:
 npm install -g composer-playground
  • 安装 fabric 运行时:

下载并安装 composer 的 fabric 运行时如下:

 mkdir ~/fabric-devserver && cd ~/fabric-devserver
 curl -O https://raw.githubusercontent.com/hyperledger/composer- tools/master/packages/fabric-dev-servers/fabric-dev-servers.zip
 unzip fabric-dev-servers.zip
 export FABRIC_VERSION=hlfv12
 ./downloadFabric.sh

在这一步,你已经安装了典型的 composer 开发环境所需的一切。

分析业务场景

第十六章使用 Hyperledger Fabric 探索企业区块链应用程序中,我们讨论了对保险索赔的区块链用例。它包括以下步骤:

  1. 保险人向经纪人报告索赔

  2. 经纪人提供请求的信息

  3. 经纪人向发行者提交索赔

  4. 发行者确认索赔

  5. 发行者处理并批准索赔

在本章中,我们将使用相同的保险索赔用例,但也通过 Hyperledger Composer 构建端到端应用程序。

业务网络存档

Composer 业务由四种不同类型的文件组成:模型文件(.cto)、脚本文件(.js)、访问控制列表(ACL)文件(.acl)和查询文件(.qry)。

网络模型文件(.cto)

CTO 文件由以下元素组成:

元素描述
单个命名空间定义 composer 模型命名空间;每个.cto 模型文件都需要一个命名空间。
资源 - 资产可以在各方之间交换的任何有价值的东西。
资源 - 参与者业务网络成员。
资源 - 枚举由一组命名值组成的数据类型。
资源 - 概念你想要建模的任何对象,而不是其他类型的对象。
资源 - 交易定义区块链业务逻辑。
Resources - events区块链事务通知。
Import从其他命名空间导入资源。

Composer 模型语言,就像其他编程语言一样,具有包括 String、Double、Integer 等数据类型。

让我们看一些资产、参与者、交易和事件的示例。

IBM Bluemix 提供了一个无需安装的浏览器版本 Playground;我们可以使用这个工具快速进行原型设计。这是链接:composer-playground.mybluemix.net/

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

连接到基本样本网络。 Playground 将为您生成一些默认的示例资产、参与者、交易和事件,例如:

sample.cto
/**
* Sample business network definition.
 */
namespace org.example.basic
asset SampleAsset identified by assetId {
  o String assetId
  --> SampleParticipant owner
  o String value
}
participant SampleParticipant identified by participantId {
  o String participantId
  o String firstName
  o String lastName
}
transaction SampleTransaction {
  --> SampleAsset asset
  o String newValue
}
event SampleEvent {
  --> SampleAsset asset
  o String oldValue
  o String newValue
}

sample.cto.SampleAsset 中定义了一个 namespace org.example.basic,这是一个 Asset 类的示例。它定义了一个资产,其名称后跟着一个标识性的 field.o String assetIdSampleAsset 的一个字段。--> SampleParticipant owner:字段指向 SampleParticipant instance.SampleParticipantParticipant 类的一个示例,语法与 SampleAsset.SampleTransaction 是一个事务的示例 class.SampleEvent 是一个事件类的示例。

脚本文件 (.js)

我们在模型文件中定义了交易和事件,脚本文件实现了这些交易功能。注释中的装饰器用于用于事务处理所需的元数据注释函数,例如:

/**
 * Sample transaction processor function.
 * @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
 * @transaction
 */
async function sampleTransaction(tx) {  // eslint-disable-line no-unused-vars
..
    emit(event);
}

sampleTransaction 函数中,@param 标签后跟着触发事务处理器函数的事务的资源名称。 @transaction 将此函数标记为事务处理器函数。

访问控制列表 (ACL) 文件 (.acl)

ACL 文件定义了业务网络中参与者的权限,例如:

rule OwnerHasFullAccessToTheirAssets {
    description: "Allow all participants full access to their assets"
    participant(p): "org.example.basic.SampleParticipant"
    operation: ALL
    resource(r): "org.example.basic.SampleAsset"
    condition: (r.owner.getIdentifier() === p.getIdentifier())
    action: ALLOW
}

在前面的 ACL 示例中,指定参与者为 SampleParticipant。任何注册为 SampleParticipant 的实例都可以对所有的 org.example.SampleAsset 实例执行 ALL 操作。此事务在 SampleAsset 的所有者与提交事务的参与者相同时触发。

查询文件 (.qry)

查询文件定义了用于返回关于区块链世界状态的数据的查询。查询语法与 SQL 语言非常相似,例如:

query queryName {
    description: "Select SampleAsset by assetId "
    statement:
        SELECT org.example.basic.SampleAsset
            WHERE (_$assetId = assetId)
}

设计业务模型

现在我们已经审查了基本的 Composer 模型语言和结构,是时候使用 Hyperledger Composer 实现一个保险理赔了。

为简单起见,我们将允许参与者在此示例中有权限读取和写入所有资源。删除与 ACL 相关的示例资源,并更新规则如下:

rule EverybodyCanReadEverything {
    description: "Allow all participants read access to all resources"
    participant: "**"
    operation: READ
    resource: "com.packt.quickstart.claim.*"
    action: ALLOW
}
rule EverybodyCanSubmitTransactions {
    description: "Allow all participants to submit transactions"
    participant: "**"
    operation: CREATE
    resource: "**"
    action: ALLOW
}

简化后的 ACL,我们开始按照以下方式处理我们的模型文件:

  1. sample.cto 重命名为 insurance-claim.cto

  2. 将命名空间更改为 com.packt.quickstart.claim 并删除其余代码

  3. 定义参与者和资产

我们在第十六章中编写了一个名为claimcontract.go的链码,使用 Hyperledger Fabric 探索企业区块链应用程序,该链码定义了被保险人、经纪人、保险人和索赔的结构。我们可以类似于这个结构定义参与者和资产。如下所示,这非常简单:

      namespace com.packt.quickstart.claim
      participant Insuree identified by id {
        o String id
        o String firstName
        o String lastName
        o String ssn
        o String policyNumber
      }
      participant Company identified by id {
        o String id
        o String type
        o String name
      }
      asset Claim identified by id {
        o String id
        o String desc
        o Integer status
        o String insureeId
        o String brokerId
        o String insurerId
        o String comment
        o String processAt
      }
  1. 定义交易和事件。通过使用Init函数,我们登记被保险人,如下所示:
      transaction Init {
        o String insureeId
        o String firstName
        o String lastName
        o String ssn
        o String policyNumber
      }
      event InitEvent {
        --> Insuree insuree
      }
  1. Composer 的 JavaScript API 提供了用于创建资源(包括参与者)的 CRUD。对于保险人和经纪人,我们将使用这种方法。我们在进行测试时会更详细地解释这一点。

  2. 定义ReportLost:被保险人向经纪人报告索赔—这启动了一个索赔,如下所示:

      transaction ReportLost {
        o String claimId
        o String desc
        o String insureeId
        o String brokerId
      }
      event ReportLostEvent {
         --> Claim claim
      }
  1. 定义RequestedInfo:经纪人提供请求的信息,如下所示:
      transaction RequestedInfo {
        --> Claim claim
      }
      event RequestedInfoEvent {
        --> Claim claim
      }
  1. 定义SubmitClaim:经纪人向发行人提交索赔。

  2. 定义ConfirmClaimSubmission:发行人确认索赔。

  3. 定义ApproveClaim:发行人处理并批准索赔。

步骤 8、9 和 10 是交易函数,与步骤 7 非常相似。

我们在模型文件中定义了所有的交易、参与者和资产。作为下一步,我们将实现模型文件中定义的交易。

实现业务交易函数

我们通过审查SampleTransaction在前一节学习了如何实现交易函数。按照类似的方法,我们将实现一个保险索赔交易函数。将sample.js重命名为logic.js

实现Init函数,如下所示:

Init() function is used to register insuree person information.
/** 
  * Create the insuree
  * @param {com.packt.quickstart.claim.Init} initalAppliation - the InitialApplication transaction
  * @transaction
  */
 async function Init(application) { // eslint-disable-line no-unused-vars
     const factory = getFactory();
     const namespace = 'com.packt.quickstart.claim';
     const insuree = factory.newResource(namespace, 'Insuree', application.insureeId);
     insuree.firstName = application.firstName;;     insuree.lastName = application.lastName

     insuree.ssn = application.ssn;;
     insuree.policyNumber = application.policyNumber;;
     const participantRegistry = await
getParticipantRegistry(insuree.getFullyQualifiedType());
     await participantRegistry.add(insuree);
     // emit event
     const initEventEvent = factory.newEvent(namespace, 'InitEvent');
     initEventEvent.insuree = insuree;
     emit(initEventEvent);
 }

实现ReportLost,设置并创建索赔,如下所示:

/**
  * insuree report lost item
  * @param {com.packt.quickstart.claim.ReportLost} ReportLost - the ReportLost transaction
  * @transaction
  */
 async function ReportLost(request) {
     const factory = getFactory();
     const namespace = 'com.packt.quickstart.claim';
     let claimId = request.claimId;
     let desc = request.desc;
     let insureeId = request.insureeId;
     let brokerId = request.brokerId;
     const claim = factory.newResource(namespace, 'Claim', claimId);
     claim.desc = desc;
     claim.status = "ReportLost";
     claim.insureeId = insureeId;
     claim.brokerId = brokerId;
     claim.insurerId = "";
     claim.comment = "";
     claim.processAt = (new Date()).toString();
     const claimRegistry = await getAssetRegistry(claim.getFullyQualifiedType());
     await claimRegistry.add(claim);
     // emit event
     const reportLostEvent = factory.newEvent(namespace, 'ReportLostEvent');
     reportLostEvent.claim = claim;
     emit(reportLostEvent); }

实现RequestedInfo以验证和更新索赔状态,如下所示:

/**
  * broker send Requested Info to insuree
  * @param {com.packt.quickstart.claim.RequestedInfo} RequestedInfo - the RequestedInfo transaction
  * @transaction
  */
 async function RequestedInfo(request) { // eslint-disable-line no-unused-vars
     const factory = getFactory();
     const namespace = 'com.packt.quickstart.claim';
     let claim = request.claim;
     if (claim.status !== 'ReportLost') {
         throw new Error ('This claim should be in ReportLost status');
     }
     claim.status = 'RequestedInfo';
     claim.processAt = (new Date()).toString();
     const assetRegistry = await getAssetRegistry(request.claim.getFullyQualifiedType());
     await assetRegistry.update(claim);
     // emit event
     const requestedInfoEventEvent = factory.newEvent(namespace, 'RequestedInfoEvent');
     requestedInfoEventEvent.claim = claim;
     emit(requestedInfoEventEvent); }

实现SubmitClaimConfirmClaimSubmissionApproveClaim。这些功能与RequestedInfo类似。

在游乐场进行测试

我们刚刚在前一节中实现了所有的模型和逻辑文件,所以现在是测试我们的 composer 应用程序的时候了:

  1. 单击游乐场左下角的部署更改按钮。这将部署 composer 代码。

  2. 点击顶部导航栏上的测试链接。将弹出提交交易页面。从交易类型下拉菜单中选择init方法。输入 JSON 值,如下截图所示;输入数据与我们在第十六章中测试的相同,使用 Hyperledger Fabric 探索企业区块链应用程序。实例化面料链码步骤。提交交易,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果交易提交成功,我们将能够看到被保险人参与者已创建,例如:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 现在,让我们入职经纪人和保险人。在参与者部分中点击公司,然后点击创建新参与者。输入经纪人数据,方式与我们在第十六章中的chaincodeInvokeAddBroker步骤相同,使用 Hyperledger Fabric 探索企业区块链应用程序。点击创建新,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果交易提交成功,这将入职经纪人。重复相同步骤以入职保险人,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 提交ReportLost,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 用以下结果测试RequestedInfo

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

剩下的SubmitClaimConfirmClaimSubmissionApproveClaim步骤与RequestedInfo非常相似。

部署业务网络

我们在游乐场中测试了 composer 应用程序,接下来我们将把它部署到区块链上:

  1. 创建一个名为insurance-claim-network的文件夹,并导航到该文件夹。

  2. 生成业务网络项目模板,如下所示:

 yo hyperledger-composer:businessnetwork

它会提示几个问题。输入insurance-claim-network作为网络名称,并选择空模板网络,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这将生成一些具有默认模板的文件。替换com.packt.quickstart的内容,如下所示:

用我们之前测试过的模型文件.claim.cto

创建一个名为lib的新文件夹,在lib文件夹下,将测试过的logic.js复制到这里。

用被测试过的acl文件替换permissions.acl,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 启动 Hyperledger Fabric,如下所示:
 cd ~/fabric-devservers
 export FABRIC_VERSION=hlfv12
 ./startFabric.sh
 ./createPeerAdminCard.sh

这将创建PeerAdminCard,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 生成业务网络档案。从insurance-claim-network目录中运行以下命令:
 composer archive create -t dir -n 

这将生成insurance-claim-network@0.0.1.bna

  1. 安装业务网络。从insurance-claim-network目录中运行以下命令:
 composer network install --card PeerAdmin@hlfv1 --archiveFile 
      insurance-claim-network@0.0.1.bna
  1. 启动业务网络。从insurance-claim-network目录中运行以下命令:
 composer network start --networkName insurance-claim-network --
      networkVersion 0.0.1 --networkAdmin admin --networkAdminEnrollSecret 
      adminpw --card PeerAdmin@hlfv1 --file networkadmin.card
  1. 导入网络管理员卡。从insurance-claim-network目录中运行以下命令。这将会将insurance-claim-network导入到网络中:
 composer card import --file networkadmin.card
  1. 检查业务网络是否已成功部署。从insurance-claim-network目录中运行以下命令:
 composer network ping --card admin@insurance-claim-network

结果应如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

检查业务网络是否已成功部署

与 REST 服务器集成

我们刚刚在 fabric 网络中部署了insurance-claim-network。下一步是构建一个保险索赔客户端 API,与网络中的智能合约函数进行交互。Hyperledger Composer REST 服务器可用于生成 REST API。REST 客户端可以调用这些端点函数,并与 Fabric 区块链上的业务网络链码交互。

生成 Hyperledger Composer REST API

运行以下命令生成 composer 服务器 API:

composer-rest-server

从业务网络卡中输入admin@insurance-claim-network,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入业务网络卡

这将生成 REST API,将其暴露为http://serverIP:3000http://serverIP:3000/explorer

打开浏览 URL。你会看到生成的 REST 端点,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们尝试多种方法来演示这些端点是如何与 fabric 网络进行交互。

从端点中选择init Post方法,并提供 post JSON 数据,然后点击 Try it out! 按钮。JSON 数据示例如下所示:

{
  "$class": "com.packt.quickstart.claim.Init",
  "insureeId": "user-001",
  "firstName": "John",
  "lastName": "Smith",
  "ssn": "9999",
  "policyNumber": "string"
}

这是点击 Try it out! 按钮后显示的结果截图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

JSON 数据示例

API 将调用 fabric 网络中的Init链码,并将响应返回给浏览器。

使用 post 方法选择一个公司来创建被保险人。输入以下 JSON 请求如下:

{
  "$class": "com.packt.quickstart.claim.Init",
  "insureeId": "user-001",
  "firstName": "John",
  "lastName": "Smith",
  "ssn": "9999",
  "policyNumber": "string"
}

你应该会看到一个成功的返回,类似于下面截图中展示的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 post 方法选择一个公司来创建被保险人,输入以下 JSON 请求

从端点中选择ReportLost Post方法,并提供 post JSON 数据,然后点击Try it out!:

{
  "$class": "com.packt.quickstart.claim.ReportLost",
  "claimId": "claim_001",
  "desc": "I was in Destiny shopping center and lost my IPhone 8",
  "insureeId": "user_001",
  "brokerId": "broker_001"
}

您应该会从区块链中得到一个成功的响应。

要验证索赔是否成功在网络中创建,您可以选择索赔 get 方法并点击 Try it out! 您应该能够获取到索赔结果,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

验证网络中索赔是否成功创建

其他保险索赔端点 API 将会和我们探索过的很相似。

总结

我们已经到达本章末尾。在这一章中,我们概述了 Hyperledger Composer 并安装了相关工具。我们使用 composer 模型语言开发了同样的保险索赔用例,就像第十六章中所探索过的使用 Hyperledger Fabric 探索企业区块链应用,并将其部署到 fabric 网络中。最后,我们将应用与 composer REST 服务器集成,生成客户端 API,并从 web 中与这些 API 进行交互。

到这一步,你应该已经熟悉了 Hyperledger Composer 的工作方式。现在我们已经到了本章的结尾,我们已经了解了两种最流行的公共和企业级区块链。作为一名区块链开发者,你应该具备基本的区块链知识,以便能够编写你自己的区块链应用程序。在下一章中,我们将讨论各种真实世界的区块链使用案例。

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值