代码

'use strict';


var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');


var fabric_client = new Fabric_Client();

// 设置fabric网络
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://localhost:7051');
channel.addPeer(peer);

//
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;

var query =async (fcn,args)=>{
    try {
        // create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
        var state_store = await Fabric_Client.newDefaultKeyValueStore({path: store_path});
        // assign the store to the fabric client
        fabric_client.setStateStore(state_store);
        var crypto_suite = Fabric_Client.newCryptoSuite();
        // use the same location for the state store (where the users' certificate are kept)
        // and the crypto store (where the users' keys are kept)
        var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
        crypto_suite.setCryptoKeyStore(crypto_store);
        fabric_client.setCryptoSuite(crypto_suite);

        // get the enrolled user from persistence, this user will sign all requests
        var user_from_store = await fabric_client.getUserContext('user1', true);

        if (user_from_store && user_from_store.isEnrolled()) {
            console.log('Successfully loaded user1 from persistence');
            member_user = user_from_store;
        } else {
            throw new Error('Failed to get user1.... run registerUser.js');
        }

        // queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
        // queryAllCars chaincode function - requires no arguments , ex: args: [''],
        const request = {
            //targets : --- letting this default to the peers assigned to the channel
            chaincodeId: 'fabcar',
            fcn: fcn,
            args: args
        };

        // send the query proposal to the peer
        var query_responses = await channel.queryByChaincode(request);

        console.log("Query has completed, checking results");
        // query_responses could have more than one  results if there multiple peers were used as targets
        if (query_responses && query_responses.length == 1) {
            if (query_responses[0] instanceof Error) {
                console.error("error from query = ", query_responses[0]);
            } else {
                console.log("Response is ", query_responses[0].toString());
            }
        } else {
            console.log("No payloads were returned from query");
        }
    }catch (err){
        console.error('Failed to query successfully :: ' + err);
    }
};

console.log(process.argv[2]);
console.log(process.argv[3]);
var args = new Array(process.argv[3]);
query(process.argv[2],args);
const express = require('express')
const app = express()


var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');


var fabric_client = new Fabric_Client();

// 设置fabric网络
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://localhost:7051');
channel.addPeer(peer);

//
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;

var query =async (fcn,args)=>{
    try {
        // create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
        var state_store = await Fabric_Client.newDefaultKeyValueStore({path: store_path});
        // assign the store to the fabric client
        fabric_client.setStateStore(state_store);
        var crypto_suite = Fabric_Client.newCryptoSuite();
        // use the same location for the state store (where the users' certificate are kept)
        // and the crypto store (where the users' keys are kept)
        var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
        crypto_suite.setCryptoKeyStore(crypto_store);
        fabric_client.setCryptoSuite(crypto_suite);

        // get the enrolled user from persistence, this user will sign all requests
        var user_from_store = await fabric_client.getUserContext('user1', true);

        if (user_from_store && user_from_store.isEnrolled()) {
            console.log('Successfully loaded user1 from persistence');
            member_user = user_from_store;
        } else {
            throw new Error('Failed to get user1.... run registerUser.js');
        }

        // queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
        // queryAllCars chaincode function - requires no arguments , ex: args: [''],
        const request = {
            //targets : --- letting this default to the peers assigned to the channel
            chaincodeId: 'fabcar',
            fcn: fcn,
            args: args
        };

        // send the query proposal to the peer
        var query_responses = await channel.queryByChaincode(request);

        console.log("Query has completed, checking results");
        // query_responses could have more than one  results if there multiple peers were used as targets
        if (query_responses && query_responses.length == 1) {
            if (query_responses[0] instanceof Error) {
                return ("error from query = ", query_responses[0]);
            } else {
                return("Response is ", query_responses[0].toString());
            }
        } else {
            return("No payloads were returned from query");
        }
    }catch (err){
        return('Failed to query successfully :: ' + err);
    }
};

app.get('/:fcn/:fcn1',async (req, res) =>{

    console.log(req.params.fcn);
    console.log(req.params.fcn1);
    var result = await query(req.params.fcn,new Array(req.params.fcn1));
    res.send('Hello World!'+ result);

});

app.listen(80, () => console.log('Example app listening on port 80!'))


概念回顾

hyperledger fabirc的三个重要角色

  • client
    客户端,用来发起transaction propose(提案), 可以是cli, node sdk或者java sdk
  • peers
    最常见的节点,维护了ledger的副本. 记录,验证,同步数据.
  • orderer
    接收背书后的请求,排序,生成区块,最后交给peer节点.

共识的达成

fabirc的共识达成通过三个步骤

  1. 客户端发起提案,每个peer节点模拟执行,进行背书
  2. orderer节点进行排序
  3. orderer节点验证后生成区块交给peer节点去apply

三个步骤保证了区块链数据的一致性和正确性

Transaction 流程(一)


endorsing peer实际上是一些特殊的peer. 因为不是每个节点都会参与背书. 根据背书策略指定.

Transaction流程(二)

在智能合约实例化的时候,我们指定了背书策略, 每个peer节点模拟执行的结果会反馈给sdk, sdk收集到节点模拟执行的读写集后,根据背书策略来决定是否是合法请求.
不同的channel可以有不同chaincode,不同的chaincode有不同的背书策略.

Transaction流程(三)

client提交背书后的读写集(RW Sets) 给orderer节点. 注意: 在同一时间可能会有不同的client提交背书后的读写集, 这个提交操作是并行的.

Transaction流程(四)

orderer节点要验证读写集,排序,生成区块,最终把区块交给所有的peer节点,让他们更新ledger数据

orderer节点

区块链需要解决双花问题(double speding), 解决双花就是要把并行的事情,最终变成线性, 把可能引发不一致的并行操作进行串行化. 以卖火车票为例, 同一张票同一个座位有可能会被两个不同的代售点同时卖出. 解决思路有多种, 卖票前先打电话询问其他的售票点,确认不冲突才可以卖,这就是同步锁的方式, 或者约定了第一家售票点只能在8点-9点卖票,第二家售票点只能在9点-10点卖票.这是通过令牌方式解决, 另一种方式就是所有出票操作交给一个中心的机构进行出票, 中心出票之前会检查是否还有票,没有票了就出票失败...
hyperledger fabirc的 orderer节点就是采用了类似中心机构出票的方式. 所以他效率很高, 没有挖矿的概念.

orderer的排序机制

  • solo 单一orderer节点用的玩具级别的排序服务,单一orderer服务器,采用solo方式
  • kafka 阿帕奇的开源流式消息处理服务平台. 提供非拜占庭错误(故障错误)的容错性.
  • SBFT 简单拜占庭容错, 容忍集群中的orderer节点有不超过1/3的错误. 目前还在实现中...

在开发者的角度,orderer采用什么排序方法,对开发人员来讲是透明的. 代码都是一样的.只是修改一个配置.

Transaction流程(五)


committing peer验证读写集跟当前的世界状态是否一致. 一致的话就会更新ledger, 世界状态会跟随变化, 如果不一致不会更新ledger,但transaction还是会被记录.世界状态不会发生变化.

Transaction流程(六)


最后,committing peers会异步的通知client, transaction是成功还是失败. 我们监听这个回调,就可以知道数据是否被写入成功.

以上流程需要理解,记忆! 面试需要能说出来.

channels

channel相当于hyperledger fabirc的子网络, 不同的channel里面的内容彼此独立,完全隔离.
通过channel可以保证区块链参与者的隐私和数据隔离.
不同的channel,拥有不同的application, 不同的ledger,不同的peers

  • 一个peer可以属于多个channel
  • 一个channel可以拥有多个peer
  • 不同chanel里面的数据彼此隔离
  • orderer可以看到所有channel的数据

state db, 状态数据库

世界状态被存储在状态数据库里面
chaincode执行后stub.putState(key, Buffer.from(value)),
这些信息都是被key,value的形式存放到状态数据库中
通过stub.getState(key)的方式读出来

hyperledger fabric 支持两种模式的状态数据库

  • levelDB 文件形式存储, 不易查看管理.
  • couchDB 支持福查询,独立的容器数据库

智能合约(链码)

hyperledger fabric的智能合约叫chaincode链码, 可以用nodejs或者是go语言编写,
chaincode就是我们的business logic, 任何更新ledger的操作都只能通过智能合约来完成.

MSP

hyperledger fabric是一个授权网络, 成员管理服务提供者是一个组件,用于定义身份,验证身份和允许访问网络的规则。 MSP使用CA来颁发证书,MSP的默认接口是Fabric-CA API

Fabirc- CA

颁发或者召回 用户的证书, 用户只有持有证书才可以加入fabirc网络,进行转账操作.

项目实战

开发环境搭建

  1. docker
  2. docker-compose
  3. git
  4. nodejs
#安装docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
#安装docker-compose
curl -L https://github.com/docker/compose/releases/download/1.20.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
#安装git
apt-get update
apt-get install git
#安装nodejs
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs

修复阿里云超时bug
/etc/resolv.conf 注释掉 options timeout:2 attempts:3 rotate single-request-reopen

下载二进制脚本和安装docker镜像
curl -sSL https://raw.githubusercontent.com/itheima1/BlockChain/master/tools/bootstrap.sh | bash -s 1.1.0

chaincode实战

chaincode就是智能合约, 通过编写纯函数的代码,更新ledger的状态.
https://fabric-shim.github.io/ChaincodeInterface.html

<async> Init(stub)
Called during chaincode instantiate and upgrade. This is where you will initialize any application state.
智能合约被初始化或者升级的时候调用, 一般用来初始化应用程序的状态
<async> Invoke(stub)
called throughout the life time of the chaincode to carry out business transaction logic and effect the asset states
商业逻辑, 修改资产的状态.

智能合约结构

const Chaincode = class {
    async Init(stub) {  // 初始化方法
         await stub.putState(key, Buffer.from(aStringValue)); //可以初始化一些世界状态
         return shim.success(Buffer.from('Initialized Successfully!'));
    }
 
    async Invoke(stub) {
       let ret = stub.getFunctionAndParameters(); //获取函数名和参数
       console.info(ret);
       let method = this[ret.fcn]; //函数
       let payload = await method(stub, ret.params); //调用函数
       return shim.success(payload);
    }

    async xxx(stub, args) {//示例函数
        return "xxx";
    }
};
shim.start(new Chaincode());

项目实战 --- 区块链项目实战

区块链不是万金油, 任何技术都要评估风险和收益. 区块链的特点是数据的可信和不可篡改, 企业做业务应该是使用区块链技术来提升自身业务的健壮程度和抗风险能力. 不应该为了区块链而区块链, 如果为了区块链而修改自身业务这种做法是不可取的.

企业项目对区块链的态度应该是,上链了有好处, 不想上链了,可是随时下来.

区块链技术不应该跟业务绑定在一起, 数据和业务分析是我们企业级开发的最佳实战. 是目前主流的做法和思想.

区块链的作用是,在联盟中记录大家都认可,有价值的数据. 通过智能合约去添加和更新数据.

区块链项目分析(一)

雄安新区区块链租房项目
目标:房主租的安心、租客住的放心
租房业务核心数据上链

  1. 租房合同信息上链
    当房主与租客签订完合同后,合同图片信息会上传到服务器,服务器会对图片进行SHA256处理,处理结果会记录
    到区块中。当产生纠纷时按照合同约定,一旦一方的合同篡改了,其SHA256数据是无法与区块链中记录的数据相
    匹配的。
  2. 交易信息上链
    针对租金、押金、违约金等交易信息做记录,项目中记录:订单、交易双方、交易金额、起止时间、备注信息。

以后可以根据区块链交易信息查询房东或者租客的信用程度,进行信用评级.

  var rentHouse = {
      docHash: 'bf27373a381224a19c3a2886cd896d550ad440dddbfc49f09b3c025b50c56107',
      orderid: '0001',
      houseowner: '周扒皮',
      renter: '杨白劳',
      money: 30000,
      beginDate:'2018-01-01',
      endDate:'2018-12-31',
      note:'年付'
    };

    await stub.putState(args[0], Buffer.from(JSON.stringify(rentHouse)));

区块链项目分析(二)

区块链二手车交易项目
目标:安心的登记,安心的二手车交易
二手车交易核心数据上链
车辆交易信息上链
针对汽车类型,制造商,型号,颜色,所有人等交易信息做记录。

以后可以追踪汽车交易的整个历史.

  var car = {
     docType: 'car',
      make: '保时捷',
      model: '911',
      color: '冰川白',
      owner: '李天一'
    };

    await stub.putState(args[0], Buffer.from(JSON.stringify(car)));

区块链项目分析(三)

区块链p2p金融借款反欺诈系统
传统p2p小贷公司, 会对客户做背景调查,调研客户的还款能力,收入水平, 但这种报告往往具有很大的局限性.
区块链技术共享用户的资产和借贷报告,联盟链各机构之间的沟通成本就降得很低,并且这个项目可以作为当前小贷公司的一个增强的外围功能使用,不会对企业当前的核心业务进行入侵式的影响. 利用区块链的零知识证明等特性, 可以极大程度的保证用户的隐私.

例如用户王思聪以买房资金周转的名义从建设银行借款1个亿. 经过建设银行评估, 认为王思聪在贷款期限内有1亿的还款能力,
可是与此同时,王思聪以同样的理由在工商银行和农业银行贷款1个亿. 工商银行和农业银行也都对王思聪的还款能力进行了评估. 由于银行系统放款信息同步不及时,不对称. 王思聪很可能同时从三个银行分别贷出来1个亿. 那么这三家银行很可能其中一家或者两家会出现同时逾贷的情况.如果有大量借款人出现上诉情况, 甚至会导致一家银行会倒闭破产.

hyperledger fabric 存储所有人的借贷信息.

 uid: 'bf27373a381224a19c3a2886cd896d550ad440dddbfc49f09b3c025b50c56107',
  var loan = {
     timestamp: '1504054225', 
      loanmoney: '1000000',
      applydate: '2018-08-14',
      startdate: '2018-08-15',
      enddate:'2019-08-15',
      realenddate:'0000-00-00'
    };

    await stub.putState(uid, Buffer.from(JSON.stringify(loan)));

注意:以上区块链系统存储的数据是脱敏后的数据, 用户的uid是根据用户的身份证号码,姓名和公安部提供的指纹特征码生成的sha256字符串, 只有得到借款当事人授权,才可以拿到这些信息生成uid. 普通机构即使看到这些数据, 也不知道这些数据对应的真实人的身份是什么.

区块链项目分析(四)

小黄鱼项目(案例背景, 大家自行补充)

var fish = {
 Vessel: "奋进号38A", 
 Location: "67.0006, -70.5476", 
 Timestamp: "1504054225",
 Holder: "王大壮"
};
 await stub.putState(id, Buffer.from(JSON.stringify(fish)));

区块链项目分析(五)

航空公司企业积分通用项目

var credits = {
 userid: "bf27373a381224a19c3a2886cd896d550ad440dddbfc49f09b3c025b50c56107", 
 shop: "南航", 
 Timestamp: "1504054225",
 credits: 1668
};
 await stub.putState(id, Buffer.from(JSON.stringify(fish)));

区块链项目分析(六)

物流, 冷链溯源, 一带一路, 电子发票, 跨境支付,数字资产,信息共享,签证证明...

工作职责

  1. 负责hyperledger chaincode代码编写;
  2. 负责协助前端跑通业务逻辑, 业务代码的修改;
  3. 部分后端nodejs中间件业务代码的编写;
  4. 参与与其业务相关的需求变更评审;
  5. 负责hyperledger fabirc技术的研发和内部培训;
  6. 参与区块链相关业务原型设计;
  7. 负责汇报区块链技术demo方案,给客户原理讲解,效果演示.

技能描述

熟悉区块链技术,熟悉Hyperledger Fabric V1.1超级账本的实现原理、基本架构分析,链码智能合约实现,共享账本如何存储,coachdb, 共识机制如何实现和安全隐私等相关加密算法。熟悉从架构师角度分析设计系统,及程序角度实现系统。
1)了解Docker容器虚拟化技术使用;
2)熟悉nodejs语言编写Chaincode,熟悉Nodejs编写 SDK。
3)熟悉Liunx系统,熟练Fabric V1.1环境搭建,cli容器调试等。
4)熟悉多主机Peer系统部署,熟悉单主机开发调试,如4+1+2(4个Peer + 1 Orderer+2CA);
5)熟悉分布式超媒体分发协议-IPFS在区块链中应用。
6)已经实现将业务封装为服务,只要调用封装的服务,就可以实现所有业务。
7)整理了大量文档和部署脚本。

农牧厅渔业管理系统实战

定义资产的json格式

var fish = {
 vessel: "奋进号38A", 
 location: "67.0006, -70.5476", 
 timestamp: "1504054225",
 holder: "王大壮"
};

来到chaincode文件夹 创建fishcc, 智能合约

'use strict';
const shim = require('fabric-shim');
const util = require('util');

let Chaincode = class {
 //初始化智能合约的方法
  async Init(stub) {
    console.info('=========== Instantiated fabcar chaincode ===========');
    return shim.success();
  }
shim.start(new Chaincode());

invode函数设计

init函数

  async Invoke(stub) {
    let ret = stub.getFunctionAndParameters(); //获取函数和参数
    console.info(ret);

    let method = this[ret.fcn];
    if (!method) {
      console.error('找不到要调用的函数,函数名:' + ret.fcn);
      throw new Error('找不到要调用的函数,函数名:' + ret.fcn);
    }
    try {
      let payload = await method(stub, ret.params); //直接调用函数,获取返回值
      return shim.success(payload);
    } catch (err) {
      console.log(err);
      return shim.error(err);
    }
  }

queryFish函数

 async queryFish(stub, args) {
    if (args.length != 1) {
      throw new Error('错误的调用参数. 实例: FISH01');
    }
    let fishNumber = args[0];

    let fishAsBytes = await stub.getState(fishNumber); //从账本中获取fish的信息,账本是二进制存储的
    if (!fishAsBytes || fishAsBytes.toString().length <= 0) {
      throw new Error(fishAsBytes + ' 不存在: ');
    }
    console.log(fishAsBytes.toString());
    return fishAsBytes;
  }

initLedger方法

// 根据官方建议,最好的初始化区块链账本的方法是单独编写一个intLedger的方法.

async initLedger(stub, args) {
    console.info('============= 开始 : 初始化账本 ===========');
    let fishes = [];
    fishes.push({
       vessel: "奋进号38A", 
       location: "67.0006, -70.5476", 
       timestamp: "1504054225",
       holder: "王大壮"
    });
    fishes.push({
       vessel: "光明号66B", 
       location: "57.9006, -78.3478", 
       timestamp: "1504054666",
       holder: "高大壮"
    });
    fishes.push({
       vessel: "钓鱼岛58B", 
       location: "77.9034, -75.3455", 
       timestamp: "1504054888",
       holder: "刘胡兰"
    });
    
    for (let i = 0; i < fishes.length; i++) {
      await stub.putState('FISH' + i, Buffer.from(JSON.stringify(fishes[i])));
      console.info('Added <--> ',fishes[i]);
    }
    console.info('============= 结束 :初始化账本 ===========');
  }

recordFish方法

 async recordFish(stub, args) {
    console.info('============= START : record fish ===========');
    if (args.length != 5) {
      throw new Error('需要5个参数,第0个参数是id,后面的4个参数,   vessel, location,  timestamp, holder');
    }

    var fish = {
      vessel: args[1],
      location: args[2],
      timestamp: args[3],
      holder: args[4]
    };

    await stub.putState(args[0], Buffer.from(JSON.stringify(fish)));
    console.info('============= END : record fish ===========');
  }

queryAllFish方法

 async queryAllFish(stub, args) {

    let startKey = 'FISH0';
    let endKey = 'FISH999';

    let iterator = await stub.getStateByRange(startKey, endKey);

    let allResults = [];
    while (true) {
      let res = await iterator.next();

      if (res.value && res.value.value.toString()) {
        let jsonRes = {};
        console.log(res.value.value.toString('utf8'));

        jsonRes.Key = res.value.key;
        try {
          jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
        } catch (err) {
          console.log(err);
          jsonRes.Record = res.value.value.toString('utf8');
        }
        allResults.push(jsonRes);
      }
      if (res.done) {
        console.log('end of data');
        await iterator.close();
        console.info(allResults);
        return Buffer.from(JSON.stringify(allResults));
      }
    }
  }

changeFishHolder方法

更改小黄鱼的归属人

 async changeFishHolder(stub, args) {
    console.info('============= START : changeFishHolder ===========');
    if (args.length != 2) {
      throw new Error('参数数量错误,需要两个参数');
    }

    let fishAsBytes = await stub.getState(args[0]);
    let fish = JSON.parse(fishAsBytes);
    fish.holder = args[1];

    await stub.putState(args[0], Buffer.from(JSON.stringify(fish)));
    console.info('============= END : changeFishHolder ===========');
  }

总结

农牧厅渔业管理的智能合约我们写完了, 大家要能够举一反三, 其他业务需求基本上也是类似的模版代码.
作业: 大家自己实现上面分析的其他需求.编写智能合约.

搭建环境,开发区块链App

配置msp信息crypto-config.yaml

OrdererOrgs:

  - Name: Orderer
    Domain: example.com

    Specs:
      - Hostname: orderer

PeerOrgs:

  - Name: Org1
    Domain: org1.example.com

    Template:
      Count: 1

    Users:
      Count: 1

配置组织和排序节点 configtx.yaml

Organizations:
    - &OrdererOrg
        Name: OrdererOrg
        ID: OrdererMSP
        MSPDir: crypto-config/ordererOrganizations/example.com/msp
    - &Org1
        Name: Org1MSP
        ID: Org1MSP
        MSPDir: crypto-config/peerOrganizations/org1.example.com/msp

Application: &ApplicationDefaults
    Organizations:

Orderer: &OrdererDefaults
    OrdererType: solo
    Addresses:
        - orderer.example.com:7050
    BatchTimeout: 2s
    BatchSize:
        MaxMessageCount: 10
        AbsoluteMaxBytes: 99 MB
        PreferredMaxBytes: 512 KB
    Organizations:
Profiles:
    OneOrgOrdererGenesis:
        Orderer:
            <<: *OrdererDefaults
            Organizations:
                - *OrdererOrg
        Consortiums:
            SampleConsortium:
                Organizations:
                    - *Org1
    OneOrgChannel:
        Consortium: SampleConsortium
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1

编写docker-compose.yml配置文件

注意ca的默认密码是adminpw

version: '2'

networks:
  basic:

services:
  ca.example.com:
    image: hyperledger/fabric-ca
    environment:
      - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
      - FABRIC_CA_SERVER_CA_NAME=ca.example.com
      - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem
      - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/4239aa0dcd76daeeb8ba0cda701851d14504d31aad1b2ddddbac6a57365e497c_sk
    ports:
      - "7054:7054"
    command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
    volumes:
      - ./crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/fabric-ca-server-config
    container_name: ca.example.com
    networks:
      - basic

  orderer.example.com:
    container_name: orderer.example.com
    image: hyperledger/fabric-orderer
    environment:
      - ORDERER_GENERAL_LOGLEVEL=debug
      - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
      - ORDERER_GENERAL_GENESISMETHOD=file
      - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/configtx/genesis.block
      - ORDERER_GENERAL_LOCALMSPID=OrdererMSP
      - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp/orderer/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/orderer
    command: orderer
    ports:
      - 7050:7050
    volumes:
        - ./config/:/etc/hyperledger/configtx
        - ./crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/:/etc/hyperledger/msp/orderer
        - ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/:/etc/hyperledger/msp/peerOrg1
    networks:
      - basic

  peer0.org1.example.com:
    container_name: peer0.org1.example.com
    image: hyperledger/fabric-peer
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_LOGGING_PEER=debug
      - CORE_CHAINCODE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      # # the following setting starts chaincode containers on the same
      # # bridge network as the peers
      # # https://docs.docker.com/compose/networking/
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric
    command: peer node start
    # command: peer node start --peer-chaincodedev=true
    ports:
      - 7051:7051
      - 7053:7053
    volumes:
        - /var/run/:/host/var/run/
        - ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/msp/peer
        - ./crypto-config/peerOrganizations/org1.example.com/users:/etc/hyperledger/msp/users
        - ./config:/etc/hyperledger/configtx
    depends_on:
      - orderer.example.com
    networks:
      - basic


  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.example.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
      - CORE_CHAINCODE_KEEPALIVE=10
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash
    volumes:
        - /var/run/:/host/var/run/
        - ./../chaincode/:/opt/gopath/src/github.com/
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
    networks:
        - basic

编写启动脚本 start.sh

#!/bin/bash

set -ev

# don't rewrite paths for Windows Git Bash users
export MSYS_NO_PATHCONV=1

docker-compose -f docker-compose.yml down

docker-compose -f docker-compose.yml up -d ca.example.com orderer.example.com peer0.org1.example.com 

export FABRIC_START_TIMEOUT=30
sleep ${FABRIC_START_TIMEOUT}

# Create the channel
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp" peer0.org1.example.com peer channel create -o orderer.example.com:7050 -c mychannel -f /etc/hyperledger/configtx/channel.tx
# Join peer0.org1.example.com to the channel.
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp" peer0.org1.example.com peer channel join -b mychannel.block

编写startFabirc.sh脚本

#!/bin/bash
set -e
# don't rewrite paths for Windows Git Bash users
export MSYS_NO_PATHCONV=1
starttime=$(date +%s)
LANGUAGE=node
CC_SRC_PATH=/opt/gopath/src/github.com/fabcar/node
# clean the keystore
rm -rf ./hfc-key-store
# launch network; create channel and join peer to channel
cd ../basic-network
./start.sh
# Now launch the CLI container in order to install, instantiate chaincode
docker-compose -f ./docker-compose.yml up -d cli
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n fishcc -v 1.0 -p "$CC_SRC_PATH" -l node
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fishcc -l node -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
sleep 20
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fishcc -c '{"function":"initLedger","Args":[""]}'
printf "\n脚本启动时间 : $(($(date +%s) - starttime)) 秒 ...\n\n\n"
printf "执行'npm install' 安装依赖文件\n"
printf "执行 'node enrollAdmin.js', 创建管理员, 然后执行 'node registerUser'创建用户\n\n"
printf "执行 'node invoke.js' 调用函数\n"
printf "执行 'node query.js' 查询记录\n\n"

初始化nodejs项目

创建文件夹fabircfish
npm install --save fabric-ca-client
npm install --save fabirc-client
npm install --save grpc

使用node sdk编写注册admin的逻辑.获取admin的证书信息

'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
 * Enroll the admin user
 */

var Fabric_Client = require('fabric-client');
var Fabric_CA_Client = require('fabric-ca-client');

var path = require('path');
var util = require('util');
var os = require('os');

//
var fabric_client = new Fabric_Client();
var fabric_ca_client = null;
var admin_user = null;
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log(' Store path:'+store_path);

// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
    // assign the store to the fabric client
    fabric_client.setStateStore(state_store);
    var crypto_suite = Fabric_Client.newCryptoSuite();
    // use the same location for the state store (where the users' certificate are kept)
    // and the crypto store (where the users' keys are kept)
    var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
    crypto_suite.setCryptoKeyStore(crypto_store);
    fabric_client.setCryptoSuite(crypto_suite);
    var	tlsOptions = {
    	trustedRoots: [],
    	verify: false
    };
    // be sure to change the http to https when the CA is running TLS enabled
    fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', tlsOptions , 'ca.example.com', crypto_suite);

    // first check to see if the admin is already enrolled
    return fabric_client.getUserContext('admin', true);
}).then((user_from_store) => {
    if (user_from_store && user_from_store.isEnrolled()) {
        console.log('Successfully loaded admin from persistence');
        admin_user = user_from_store;
        return null;
    } else {
        // need to enroll it with CA server
        return fabric_ca_client.enroll({
          enrollmentID: 'admin',
          enrollmentSecret: 'adminpw'
        }).then((enrollment) => {
          console.log('Successfully enrolled admin user "admin"');
          return fabric_client.createUser(
              {username: 'admin',
                  mspid: 'Org1MSP',
                  cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate }
              });
        }).then((user) => {
          admin_user = user;
          return fabric_client.setUserContext(admin_user);
        }).catch((err) => {
          console.error('Failed to enroll and persist admin. Error: ' + err.stack ? err.stack : err);
          throw new Error('Failed to enroll admin');
        });
    }
}).then(() => {
    console.log('Assigned the admin user to the fabric client ::' + admin_user.toString());
}).catch((err) => {
    console.error('Failed to enroll admin: ' + err);
});

使用node sdk编写注册user1的逻辑.获取user1的证书信息

'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
 * Register and Enroll a user
 */

var Fabric_Client = require('fabric-client');
var Fabric_CA_Client = require('fabric-ca-client');

var path = require('path');
var util = require('util');
var os = require('os');

//
var fabric_client = new Fabric_Client();
var fabric_ca_client = null;
var admin_user = null;
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log(' Store path:'+store_path);

// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
    // assign the store to the fabric client
    fabric_client.setStateStore(state_store);
    var crypto_suite = Fabric_Client.newCryptoSuite();
    // use the same location for the state store (where the users' certificate are kept)
    // and the crypto store (where the users' keys are kept)
    var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
    crypto_suite.setCryptoKeyStore(crypto_store);
    fabric_client.setCryptoSuite(crypto_suite);
    var	tlsOptions = {
    	trustedRoots: [],
    	verify: false
    };
    // be sure to change the http to https when the CA is running TLS enabled
    fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', null , '', crypto_suite);

    // first check to see if the admin is already enrolled
    return fabric_client.getUserContext('admin', true);
}).then((user_from_store) => {
    if (user_from_store && user_from_store.isEnrolled()) {
        console.log('Successfully loaded admin from persistence');
        admin_user = user_from_store;
    } else {
        throw new Error('Failed to get admin.... run enrollAdmin.js');
    }

    // at this point we should have the admin user
    // first need to register the user with the CA server
    return fabric_ca_client.register({enrollmentID: 'user1', affiliation: 'org1.department1',role: 'client'}, admin_user);
}).then((secret) => {
    // next we need to enroll the user with CA server
    console.log('Successfully registered user1 - secret:'+ secret);

    return fabric_ca_client.enroll({enrollmentID: 'user1', enrollmentSecret: secret});
}).then((enrollment) => {
  console.log('Successfully enrolled member user "user1" ');
  return fabric_client.createUser(
     {username: 'user1',
     mspid: 'Org1MSP',
     cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate }
     });
}).then((user) => {
     member_user = user;

     return fabric_client.setUserContext(member_user);
}).then(()=>{
     console.log('User1 was successfully registered and enrolled and is ready to interact with the fabric network');

}).catch((err) => {
    console.error('Failed to register: ' + err);
	if(err.toString().indexOf('Authorization') > -1) {
		console.error('Authorization failures may be caused by having admin credentials from a previous CA instance.\n' +
		'Try again after deleting the contents of the store directory '+store_path);
	}
});

query hyperledger fabirc的信息

'use strict';


var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');


var fabric_client = new Fabric_Client();

// 设置fabric网络
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://localhost:7051');
channel.addPeer(peer);

//
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;

var query =async (fcn,args)=>{
    try {
        // create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
        var state_store = await Fabric_Client.newDefaultKeyValueStore({path: store_path});
        // assign the store to the fabric client
        fabric_client.setStateStore(state_store);
        var crypto_suite = Fabric_Client.newCryptoSuite();
        // use the same location for the state store (where the users' certificate are kept)
        // and the crypto store (where the users' keys are kept)
        var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
        crypto_suite.setCryptoKeyStore(crypto_store);
        fabric_client.setCryptoSuite(crypto_suite);

        // get the enrolled user from persistence, this user will sign all requests
        var user_from_store = await fabric_client.getUserContext('user1', true);

        if (user_from_store && user_from_store.isEnrolled()) {
            console.log('Successfully loaded user1 from persistence');
            member_user = user_from_store;
        } else {
            throw new Error('Failed to get user1.... run registerUser.js');
        }

        // queryCar chaincode function - requires 1 argument, ex: args: ['FISH0'],
        // queryAllCars chaincode function - requires no arguments , ex: args: [''],
        const request = {
            //targets : --- letting this default to the peers assigned to the channel
            chaincodeId: 'fishcc',
            fcn: fcn,
            args: args
        };

        // send the query proposal to the peer
        var query_responses = await channel.queryByChaincode(request);

        console.log("Query has completed, checking results");
        // query_responses could have more than one  results if there multiple peers were used as targets
        if (query_responses && query_responses.length == 1) {
            if (query_responses[0] instanceof Error) {
                console.error("error from query = ", query_responses[0]);
            } else {
                console.log("Response is ", query_responses[0].toString());
            }
        } else {
            console.log("No payloads were returned from query");
        }
    }catch (err){
        console.error('Failed to query successfully :: ' + err);
    }
};


console.log(process.argv[2]);
console.log(process.argv[3]);
var args = new Array(process.argv[3]);
query(process.argv[2],args);

前端获取数据

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
    state = {items: []};
    async componentDidMount() {
        var result = await axios.get('http://47.254.69.107/queryAllFish/a');
        console.log(result.data);
        this.setState({
           items:result.data
        });
    }
  render() {
    var htmlbody=[];
    this.state.items.forEach((value)=>{
        htmlbody.push(<h3>{value.Key}, {value.Record.holder}</h3>)
    });
    return (
      <div >
          {htmlbody}
      </div>
    );
  }

添加捕鱼记录

'use strict';

var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');

//
var fabric_client = new Fabric_Client();

// 设置 fabric 网络
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://localhost:7051');
channel.addPeer(peer);
var order = fabric_client.newOrderer('grpc://localhost:7050');
channel.addOrderer(order);

//
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;

Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
	// assign the store to the fabric client
	fabric_client.setStateStore(state_store);
	var crypto_suite = Fabric_Client.newCryptoSuite();
	// use the same location for the state store (where the users' certificate are kept)
	// and the crypto store (where the users' keys are kept)
	var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
	crypto_suite.setCryptoKeyStore(crypto_store);
	fabric_client.setCryptoSuite(crypto_suite);

	// get the enrolled user from persistence, this user will sign all requests
	return fabric_client.getUserContext('user1', true);
}).then((user_from_store) => {
	if (user_from_store && user_from_store.isEnrolled()) {
		console.log('Successfully loaded user1 from persistence');
		member_user = user_from_store;
	} else {
		throw new Error('Failed to get user1.... run registerUser.js');
	}

	// get a transaction id object based on the current user assigned to fabric client
	tx_id = fabric_client.newTransactionID();
	console.log("Assigning transaction_id: ", tx_id._transaction_id);

	// recordFish chaincode function - requires 5 args, ex: args: ['FISH5',  "奋进号38A", "67.0006, -70.5476", "1504054225", "王大壮"],
	// changeFishHolder chaincode function - requires 2 args , ex: args: ['FISH5', 'Dave'],
	// must send the proposal to endorsing peers
	var request = {
		//targets: let default to the peer assigned to the client
		chaincodeId: 'fishcc',
		fcn: '',
		args: [''],
		chainId: 'mychannel',
		txId: tx_id
	};

	//1. 发送背书请求到所有的节点
	return channel.sendTransactionProposal(request);
}).then((results) => {
	var proposalResponses = results[0];
	var proposal = results[1];
	let isProposalGood = false;
	if (proposalResponses && proposalResponses[0].response &&
		proposalResponses[0].response.status === 200) {
			isProposalGood = true;
			console.log('Transaction proposal was good');
		} else {
			console.error('Transaction proposal was bad');
		}
	if (isProposalGood) {
		console.log(util.format(
			'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s"',
			proposalResponses[0].response.status, proposalResponses[0].response.message));

		// 根据背书请求创建request对象
		var request = {
			proposalResponses: proposalResponses,
			proposal: proposal
		};

		// 设置30秒的监听器, 看request请求是否完成
		var transaction_id_string = tx_id.getTransactionID(); //Get the transaction ID string to be used by the event processing
		var promises = [];

		var sendPromise = channel.sendTransaction(request);
		promises.push(sendPromise); //we want the send transaction first, so that we know where to check status

		// get an eventhub once the fabric client has a user assigned. The user
		// is required bacause the event registration must be signed
		let event_hub = channel.newChannelEventHub(peer);

		// using resolve the promise so that result status may be processed
		// under the then clause rather than having the catch clause process
		// the status
		let txPromise = new Promise((resolve, reject) => {
			let handle = setTimeout(() => {
				event_hub.unregisterTxEvent(transaction_id_string);
				event_hub.disconnect();
				resolve({event_status : 'TIMEOUT'}); //we could use reject(new Error('Trnasaction did not complete within 30 seconds'));
			}, 3000);
			event_hub.registerTxEvent(transaction_id_string, (tx, code) => {
				// this is the callback for transaction event status
				// first some clean up of event listener
				clearTimeout(handle);

				// now let the application know what happened
				var return_status = {event_status : code, tx_id : transaction_id_string};
				if (code !== 'VALID') {
					console.error('The transaction was invalid, code = ' + code);
					resolve(return_status); // we could use reject(new Error('Problem with the tranaction, event status ::'+code));
				} else {
					console.log('The transaction has been committed on peer ' + event_hub.getPeerAddr());
					resolve(return_status);
				}
			}, (err) => {
				//this is the callback if something goes wrong with the event registration or processing
				reject(new Error('There was a problem with the eventhub ::'+err));
			},
				{disconnect: true} //disconnect when complete
			);
			event_hub.connect();

		});
		promises.push(txPromise);

		return Promise.all(promises);
	} else {
		console.error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...');
		throw new Error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...');
	}
}).then((results) => {
	console.log('Send transaction promise and event listener promise have completed');
	// check the results in the order the promises were added to the promise all list
	if (results && results[0] && results[0].status === 'SUCCESS') {
		console.log('Successfully sent transaction to the orderer.');
	} else {
		console.error('Failed to order the transaction. Error code: ' + results[0].status);
	}

	if(results && results[1] && results[1].event_status === 'VALID') {
		console.log('Successfully committed the change to the ledger by the peer');
	} else {
		console.log('Transaction failed to be committed to the ledger due to ::'+results[1].event_status);
	}
}).catch((err) => {
	console.error('Failed to invoke successfully :: ' + err);
});