当前位置:首页 » 区块链精品文章 » 正文

12.4 票据背书实现

1989 人参与  2018年10月10日 12:52  分类 : 区块链精品文章  评论

票据背书的实现分为两个部分,即基于Hyperledger Fabric Node.js SDK的应用程序和链码功能的实现。本章所有的代码托管到Github上:https://github.com/ChainNova/trainingProjects/tree/master/billEndorse。后面只介绍部分业务逻辑的实现。

12.4.1 应用程序实现

应用程序分为Web应用前端和后端服务。这里只介绍后端服务的实现,Web应用前端部分请参考Github上的实 现。特别说明一下,本示例中的代码只用来演示和说明如何开发基于Hyperledger Fabric 1.0的区块链应用程序,接口的设计和代码实现都不严格,在实际的项目中需要做优化。

1.后端服务提供的接口定义

后端服务给Web应用提供的是RESTful的接口,全部的请求都是POST请求,Content-Type是“application/json”。

接口主要分为用户登录接口、票据发布接口、查询本人持有票据接口、票据背书请求接口、查询待签收票据接口、查询票 据信息接口、票据背书回复接口等。下面逐一以例子形式展示接口的使用,测试可以采用wget、curl等支持RESTful接口的工具,也可以采用 Postman等可视化工具,也可以编程实现。

(1)用户登录接口

所有的操作都需要先登录并获取token,作为下一次操作的凭证。用户登录接口URL是http://ip:port/login,其中,ip和port是Web应用的地址,这些参数都需要根据实际的部署做修改。

输入的Body信息如下:


{
   "username": "alice",
   "orgName": "org1",
   "password": "123456"
}


返回的信息如下:


   {
   "success": true,
   "secret": "BGjQXLFbHgGJ",
   "message": "alice enrolled Successfully",
   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIxNDM0MzAsInVzZ
   XJuYW1lIjoiYWxpY2UiLCJvcmdOYW1lIjoib3JnMSIsInBhc3N3b3JkIjoiMTIzNDU2IiwiaWF0IjoxNTEyMTA3NDMwfQ.QnIyaxuq8G4JlolBNq3DMKYfs6q8zjLUYwRjxS1GdxU",
   "user": {
       "username": "alice",
       "name": "A公司",
       "passwd": "123456",
       "cmId": "ACMID",
       "Acct": "A公司"
   }
}


其中,token是基于JSON Web Token实现的,详细的介绍参考https://jwt.io

(2)票据发布接口

新票据需要先通过发布接口发布到区块链上,发布成功后,初始状态为“新发布”,标记成000001。若区块链上已经有该票据,输出为错误,提示为“票据重复发布”。

票据操作接口的URL都是相同的:http://ip:port/channels/mychannel/chaincodes/mycc/invoke,其中,ip和port是Web应用的地址,mychannel是通道名称,mycc是链码的名称,这些参数都需要根据实际的部署做修改。

票据操作调用的接口通过Body信息来区分:


{
       "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIxNDM0MzAsI
       nVzZXJuYW1lIjoiYWxpY2UiLCJvcmdOYW1lIjoib3JnMSIsInBhc3N3b3JkIjoiMTIzNDU2IiwiaWF0IjoxNTEyMTA3NDMwfQ.QnIyaxuq8G4JlolBNq3DMKYfs6q8zjLUYwRjxS1GdxU",
       "peers": ["peer1"],
       "fcn":"issue",
       "args":["{\"BillInfoID\":\"POC10000998\",\"BillInfoAmt\":\"222\",\"BillInfoType\":\"111\",\"BillInfoIsseDate\":\"20170910\",\"BillInfoDueDate\":\"20171112\",\"DrwrCmID\":\"111\",\"DrwrAcct\":\"111\",\"AccptrCmID\":\"111\",\"AccptrAcct\":\"111\",\"PyeeCmID\":\"111\",\"PyeeAcct\":\"111\",\"HodrCmID\":\"ACMID\",\"HodrAcct\":\"A公司\"}"]
}


其中,票据操作接口是通用的结构,各参数的含义如表12-3所示。

表12-3 后端服务的接口参数定义

image.png

返回信息如下:


{
   "success": true,
   "message": "9a1525ef5a388530c1757c9c1c565bf52422e9a775a03d20e9aa2273b008aa31"
}


(3)票据背书接口

票据背书接口输入的Body信息如下:


{
       "token":      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIxNDM0MzAsInVzZXJuYW1lIjoiYWxpY2UiLCJvcmdOYW1lIjoib3JnMSIsInBhc3N3b3JkIjoiMTIzNDU2IiwiaWF0IjoxNTEyMTA3NDMwfQ.QnIyaxuq8G4JlolBNq3DMKYfs6q8zjLUYwRjxS1GdxU",
       "peers": ["peer1"],
       "fcn":"endorse",
       "args":["POC10000998","BCMID","B公司"]
}


其中,票据背书的fcn是endorse,args参数的信息参考12.4.2节。

返回的信息如下:


{
   "success": true,
   "message": "ae87c2e1d51f22125e9c16375420aee68acd0bb3dbcb5950a787e4a5cba3b080"
}


(4)票据背书签收接口

票据背书签收接口输入的Body信息如下:


{
       "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIxNDM4MDMsI
       nVzZXJuYW1lIjoiYm9iIiwib3JnTmFtZSI6Im9yZzEiLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlhdCI6MTUxMjEwNzgwM30.RMxkdTOP2e6K03hD_6GpkHV3mcZpeqjcxfdqshb7gKk",
       "peers": ["peer1"],
       "fcn":"accept",
       "args":["POC10000998","BCMID","B公司"]
}


其中,票据背书签收的用户bob需要先登录并获取token,票据背书签收的fcn是accept,args参数的信息参考12.4.2节。

返回的信息如下:


{
   "success": true,
   "message": "3710f07807521218f4ccadcdb06c7ffba21f44fc2f70a773d3bc707fe48d7f99"
}


(5)票据背书拒收接口

票据背书拒收接口输入的Body信息如下:


{
       "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIwODkwMjcsI
       nVzZXJuYW1lIjoiYWxpY2UiLCJvcmdOYW1lIjoib3JnMSIsInBhc3N3b3JkIjoiMTIzNDU2IiwiaWF0IjoxNTEyMDUzMDI3fQ.oqDRAAhuD8KSgFFuW9wCxlYpGMXXxGHV18SLMyVBAMo",
       "peers": ["peer1"],
       "fcn":"reject",
       "args":["POC10000998","BCMID","B公司"]
}


其中,票据背书拒收的fcn是reject,args参数的信息参考12.4.2节。

返回信息如下:


{
   "success": true,
   "message": "183b9ea86804f1fbf1cdd172210b612c89514e9266b082d54b5acda5be4b2f69"
}


(6)查询持票人的票据列表接口

查询持票人的票据列表接口输入的Body信息如下:


{
       "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIxNDM4MDMsI
       nVzZXJuYW1lIjoiYm9iIiwib3JnTmFtZSI6Im9yZzEiLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlhdCI6MTUxMjEwNzgwM30.RMxkdTOP2e6K03hD_6GpkHV3mcZpeqjcxfdqshb7gKk",
       "peers": ["peer1"],
       "fcn":"queryMyBill",
       "args":["BCMID"]
}


其中,查询持票人的票据列表的fcn是queryMyBill,args参数的信息参考12.4.2节。

返回的信息如下:


   {
   "success": true,
   "message": "[
       {
           'BillInfoID': 'POC10000998',
           'BillInfoAmt': '222',
           'BillInfoType': '111',
           'BillInfoIsseDate': '111',
           'BillInfoDueDate': '111',
           'DrwrCmID': '111',
           'DrwrAcct': '111',
           'AccptrCmID': '111',
           'AccptrAcct': '111',
           'PyeeCmID': '111',
           'PyeeAcct': '111',
           'HodrCmID': 'BCMID',
           'HodrAcct': 'B公司',
           'WaitEndorserCmID': '',
           'WaitEndorserAcct': '',
           'RejectEndorserCmID': '',
           'RejectEndorserAcct': '',
           'State': 'EndrSigned',
           'History': null
       }
   ]"
}


说明一下,为了方便阅读,上面的返回信息对结果做了格式化处理,把原始的结果中双引号的转义“\"”替换成了单引号“'”,后面的展示结果也做了相同的处理。

(7)查询待签收票据列表接口

查询待签收票据列表接口输入的Body信息如下:


{
       "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIxNDM4MDMsI
       nVzZXJuYW1lIjoiYm9iIiwib3JnTmFtZSI6Im9yZzEiLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlhdCI6MTUxMjEwNzgwM30.RMxkdTOP2e6K03hD_6GpkHV3mcZpeqjcxfdqshb7gKk",
       "peers": ["peer1"],
       "fcn":"queryMyWaitBill",
       "args":["BCMID"]
}


其中,查询待签收票据列表的fcn是queryMyWaitBill,args参数的信息参考12.4.2节。

返回的信息如下:


{
   "success": true,
   "message": "[
       {
           'BillInfoID': 'POC10000999',
           'BillInfoAmt': '222',
           'BillInfoType': '111',
           'BillInfoIsseDate': '111',
           'BillInfoDueDate': '111',
           'DrwrCmID': '111',
           'DrwrAcct': '111',
           'AccptrCmID': '111',
           'AccptrAcct': '111',
           'PyeeCmID': '111',
           'PyeeAcct': '111',
           'HodrCmID': 'ACMID',
           'HodrAcct': 'A公司',
           'WaitEndorserCmID': 'BCMID',
           'WaitEndorserAcct': 'B公司',
           'RejectEndorserCmID': '',
           'RejectEndorserAcct': '',
           'State': 'EndrWaitSign',
           'History': null
       }
   ]"
}


(8)根据票据号码查询票据信息接口

根据票据号码查询票据信息接口输入的Body信息如下:


{
       "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIxNDM4MDMsI
       nVzZXJuYW1lIjoiYm9iIiwib3JnTmFtZSI6Im9yZzEiLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlhdCI6MTUxMjEwNzgwM30.RMxkdTOP2e6K03hD_6GpkHV3mcZpeqjcxfdqshb7gKk",
       "peers": ["peer1"],
       "fcn":"queryByBillNo",
       "args":["POC10000998"]
}


其中,根据票据号码查询票据信息的fcn是queryByBillNo,args参数的信息参考12.4.2节。

返回的信息如下:


{
   "success": true,
   "message": "{
       'BillInfoID': 'POC10000998',
       'BillInfoAmt': '222',
       'BillInfoType': '111',
       'BillInfoIsseDate': '111',
       'BillInfoDueDate': '111',
       'DrwrCmID': '111',
       'DrwrAcct': '111',
       'AccptrCmID': '111',
       'AccptrAcct': '111',
       'PyeeCmID': '111',
       'PyeeAcct': '111',
       'HodrCmID': 'BCMID',
       'HodrAcct': 'B公司',
       'WaitEndorserCmID': '',
       'WaitEndorserAcct': '',
       'RejectEndorserCmID': '',
       'RejectEndorserAcct': '',
       'State': 'EndrSigned',
       'History': [
           {
               'txId': '9a1525ef5a388530c1757c9c1c565bf52422e9a775a03d20e9aa2273
                b008aa31',
               'bill': {
                   'BillInfoID': 'POC10000998',
                   'BillInfoAmt': '222',
                   'BillInfoType': '111',
                   'BillInfoIsseDate': '111',
                   'BillInfoDueDate': '111',
                   'DrwrCmID': '111',
                   'DrwrAcct': '111',
                   'AccptrCmID': '111',
                   'AccptrAcct': '111',
                   'PyeeCmID': '111',
                   'PyeeAcct': '111',
                   'HodrCmID': 'ACMID',
                   'HodrAcct': 'A公司',
                   'WaitEndorserCmID': '',
                   'WaitEndorserAcct': '',
                   'RejectEndorserCmID': '',
                   'RejectEndorserAcct': '',
                   'State': 'NewPublish',
                   'History': null
               }
           },
           {
               'txId': 'ae87c2e1d51f22125e9c16375420aee68acd0bb3dbcb5950a787e4a5
                cba3b080',
               'bill': {
                   'BillInfoID': 'POC10000998',
                   'BillInfoAmt': '222',
                   'BillInfoType': '111',
                   'BillInfoIsseDate': '111',
                   'BillInfoDueDate': '111',
                   'DrwrCmID': '111',
                   'DrwrAcct': '111',
                   'AccptrCmID': '111',
                   'AccptrAcct': '111',
                   'PyeeCmID': '111',
                   'PyeeAcct': '111',
                   'HodrCmID': 'ACMID',
                   'HodrAcct': 'A公司',
                   'WaitEndorserCmID': 'BCMID',
                   'WaitEndorserAcct': 'B公司',
                   'RejectEndorserCmID': '',
                   'RejectEndorserAcct': '',
                   'State': 'EndrWaitSign',
                   'History': null
               }
           },
           {
               'txId': '3710f07807521218f4ccadcdb06c7ffba21f44fc2f70a773d3bc707f
                e48d7f99',
               'bill': {
                   'BillInfoID': 'POC10000998',
                   'BillInfoAmt': '222',
                   'BillInfoType': '111',
                   'BillInfoIsseDate': '111',
                   'BillInfoDueDate': '111',
                   'DrwrCmID': '111',
                   'DrwrAcct': '111',
                   'AccptrCmID': '111',
                   'AccptrAcct': '111',
                   'PyeeCmID': '111',
                   'PyeeAcct': '111',
                   'HodrCmID': 'BCMID',
                   'HodrAcct': 'B公司',
                   'WaitEndorserCmID': '',
                   'WaitEndorserAcct': '',
                   'RejectEndorserCmID': '',
                   'RejectEndorserAcct': '',
                   'State': 'EndrSigned',
                   'History': null
               }
           }
       ]
   }"
}


2.HFC Node.js SDK的使用

HFC Node.js SDK的使用包括创建通道、加入通道、安装链码、实例化链码、调用链码等。

(1)创建通道

首先介绍getOrgAdmin函数,其目的是根据传入的orgName将client实例设置为对应该组织,针对后面的操作进行组织的配置,它作为util函数会在后面多次用到,具体实现内容如下:

1)将client实例的CryptoSuit切换为传入组织的CryptoSuit;

2)将client实例的StateStore切换为传入组织的StateStore;

3)返回传入组织的admin用户实例。


var getOrgAdmin = function(userOrg) {
   var admin = ORGS[userOrg].admin;
   var keyPath = path.join(__dirname, admin.key);
   var keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString();
   var certPath = path.join(__dirname, admin.cert);
   var certPEM = readAllFiles(certPath)[0].toString();

   var client = getClientForOrg(userOrg);
   var cryptoSuite = hfc.newCryptoSuite();
   if (userOrg) {
       cryptoSuite.setCryptoKeyStore(hfc.newCryptoKeyStore({path: getKeyStoreFo
   rOrg(getOrgName(userOrg))}));
       client.setCryptoSuite(cryptoSuite);
   }

   return hfc.newDefaultKeyValueStore({
       path: getKeyStoreForOrg(getOrgName(userOrg))
   }).then((store) => {
       client.setStateStore(store);

       return client.createUser({
           username: 'peer'+userOrg+'Admin',
           mspid: getMspID(userOrg),
           cryptoContent: {
               privateKeyPEM: keyPEM,
               signedCertPEM: certPEM
           }
       });
   });
}
 


下面介绍创建通道的步骤:

1)首先根据传入的channelConfigPath,获取通道配置文件,提取为字节;

2)把client实例切换到传入组织;

3)使用传入组织的加密材料对通道配置字节签名;

4)构建request,向orderer发送创建通道请求;

5)创建成功则返回成功的结构对象,失败则抛出异常。


var createChannel = function(channelName, channelConfigPath, username, orgName) {
       logger.debug('\n====== Creating Channel \'' + channelName + '\' ======\n');
       var client = helper.getClientForOrg(orgName);
       var channel = helper.getChannelForOrg(orgName);

       // 取到通道配置文件
       var envelope = fs.readFileSync(path.join(__dirname, channelConfigPath));
       // 提取通道配置文件字节
       var channelConfig = client.extractChannelConfig(envelope);

       return helper.getOrgAdmin(orgName).then((admin) => {
               logger.debug(util.format('Successfully acquired admin user for
               the organization "%s"', orgName));
               // 对通道配置字节签名为"背书",这是由orderer的通道创建策略所要求的
               let signature = client.signChannelConfig(channelConfig);

               let request = {
                       config: channelConfig,
                       signatures: [signature],
                       name: channelName,
                       orderer: channel.getOrderers()[0],
                       txId: client.newTransactionID()
               };

               // 将创建通道请求发送给orderer
               return client.createChannel(request);
       }, (err) => {
               logger.error('Failed to enroll user \''+username+'\'. Error: ' +
               err);
               throw new Error('Failed to enroll user \''+username+'\'' + err);
       }).then((response) => {
               logger.debug(' response ::%j', response);
               if (response && response.status === 'SUCCESS') {
                       logger.debug('Successfully created the channel.');
                       let response = {
                               success: true,
                               message: 'Channel \'' + channelName + '\' created
                               Successfully'
                       };
                   return response;
               } else {
                       logger.error('\n!!!!!!!!! Failed to create the channel \''
                       + channelName +
                               '\' !!!!!!!!!\n\n');
                       throw new Error('Failed to create the channel \'' +
                       channelName + '\'');
               }
       }, (err) => {
               logger.error('Failed to initialize the channel: ' + err.stack ?
               err.stack :
                       err);
               throw new Error('Failed to initialize the channel: ' + err.stack ?
               err.stack : err);
       });
};

exports.createChannel = createChannel;


(2)加入通道

加入通道包括如下步骤:

1)client实例切换到传入组织;

2)基于之前创建的通道,获取该通道的创世区块;

3)向要加入通道的peers发送加入通道的请求;

4)在向peers发送加入通道请求的同时,为各个peer分别注册block eventhub来监听区块产生的过程是否正常;

5)通过校验第一个peer的response status是否为200来判断加入通道的结果,成功则返回成功的结构对象,失败则抛出异常。


var joinChannel = function(channelName, peers, username, org) {
       // 断开与event hub连接的函数
       var closeConnections = function(isSuccess) {
               if (isSuccess) {
                       logger.debug('\n============ Join Channel is SUCCESS ====
                       ========\n');
               } else {
                       logger.debug('\n!!!!!!!! ERROR: Join Channel FAILED !!!!!
                       !!!\n');
               }
               logger.debug('');
               for (var key in allEventhubs) {
                       var eventhub = allEventhubs[key];
                       if (eventhub && eventhub.isconnected()) {
                               //logger.debug('Disconnecting the event hub');
                               eventhub.disconnect();
                       }
               }
       };
       //logger.debug('\n============ Join Channel ============\n')
       logger.info(util.format(
               'Calling peers in organization "%s" to join the channel', org));

       var client = helper.getClientForOrg(org);
       var channel = helper.getChannelForOrg(org);
       var eventhubs = [];

       return helper.getOrgAdmin(org).then((admin) => {
               logger.info(util.format('received member object for admin of the
               organization "%s": ', org));
               tx_id = client.newTransactionID();
               let request = {
                       txId : tx_id
               };

               return channel.getGenesisBlock(request);
       }).then((genesis_block) => {
               tx_id = client.newTransactionID();
               var request = {
                       targets: helper.newPeers(peers, org),
                       txId: tx_id,
                       block: genesis_block
               };

               eventhubs = helper.newEventHubs(peers, org);
               for (let key in eventhubs) {
                       let eh = eventhubs[key];
                       eh.connect();
                       allEventhubs.push(eh);
               }

               var eventPromises = [];
               eventhubs.forEach((eh) => {
                       let txPromise = new Promise((resolve, reject) => {
                               let handle = setTimeout(reject, parseInt(config.
                               eventWaitTime));
                               eh.registerBlockEvent((block) => {
                                       clearTimeout(handle);
   // 一个peer可能属于多个通道,所以必须检查这个配置block是否来自于我们请求加入的通道
                                       if (block.data.data.length === 1) {
                                               // 配置block只包括一个交易
                                               var channel_header = block.data.
                                               data[0].payload.header.channel_header;
                                               if (channel_header.channel_id
                                               === channelName) {
                                                       resolve();
                                               }
                                               else {
                                                       reject();
                                               }
                                       }
                               });
                       });
                       eventPromises.push(txPromise);
               });
               let sendPromise = channel.joinChannel(request);
               return Promise.all([sendPromise].concat(eventPromises));
       }, (err) => {
               logger.error('Failed to enroll user \'' + username + '\' due to
               error: ' +
                       err.stack ? err.stack : err);
               throw new Error('Failed to enroll user \'' + username +
                       '\' due to error: ' + err.stack ? err.stack : err);
       }).then((results) => {
               logger.debug(util.format('Join Channel R E S P O N S E : %j',
               results));
               if (results[0] && results[0][0] && results[0][0].response &&
               results[0][0]
                       .response.status == 200) {
                       logger.info(util.format(
                               'Successfully joined peers in organization %s to
                               the channel \'%s\'',
                               org, channelName));
                       closeConnections(true);
                       let response = {
                               success: true,
                               message: util.format(
                                       'Successfully joined peers in organization
                                       %s to the channel \'%s\'',
                                       org, channelName)
                       };
                       return response;
               } else {
                       logger.error(' Failed to join channel');
                       closeConnections();
                       throw new Error('Failed to join channel');
               }
       }, (err) => {
               logger.error('Failed to join channel due to error: ' + err.stack ?
               err.stack :
                       err);
               closeConnections();
               throw new Error('Failed to join channel due to error: ' + err.
               stack ? err.stack :
                       err);
       });
};
exports.joinChannel = joinChannel;


(3)安装链码

安装链码包括如下步骤:

1)client实例切换到传入组织;

2)client实例发出安装链码请求,请求中包括目标peers、链码路径、链码名称和链码版本;

3)对目标peers返回的proposalResponses结果依次校验,所有peers都成功则返回成功的结构对象,有peer失败则抛出异常。


var installChaincode = function(peers, chaincodeName, chaincodePath,
       chaincodeVersion, username, org) {
       logger.debug(
               '\n============ Install chaincode on organizations ============
                \n');
       helper.setupChaincodeDeploy();
       var channel = helper.getChannelForOrg(org);
       var client = helper.getClientForOrg(org);

       return helper.getOrgAdmin(org).then((user) => {
               var request = {
                       targets: helper.newPeers(peers, org),
                       chaincodePath: chaincodePath,
                       chaincodeId: chaincodeName,
                       chaincodeVersion: chaincodeVersion
               };
               return client.installChaincode(request);
       }, (err) => {
               logger.error('Failed to enroll user \'' + username + '\'. ' + err);
               throw new Error('Failed to enroll user \'' + username + '\'. ' + err);
       }).then((results) => {
               var proposalResponses = results[0];
               var proposal = results[1];
               var all_good = true;
               for (var i in proposalResponses) {
                       let one_good = false;
                       if (proposalResponses && proposalResponses[i].response &&
                               proposalResponses[i].response.status === 200) {
                               one_good = true;
                               logger.info('install proposal was good');
                       } else {
                               logger.error('install proposal was bad');
                       }
                       all_good = all_good & one_good;
               }
               if (all_good) {
                       logger.info(util.format(
                               'Successfully sent install Proposal and received
                               ProposalResponse: Status - %s',
                               proposalResponses[0].response.status));
                       logger.debug('\nSuccessfully Installed chaincode on
                       organization ' + org +
                               '\n');
                       return 'Successfully Installed chaincode on organization '
                       + org;
               } else {
                       logger.error(
                               'Failed to send install Proposal or receive valid
                               response. Response null or status is not 200. exiting...'
                       );
                       return 'Failed to send install Proposal or receive valid
                       response. Response null or status is not 200. exiting...';
               }
       }, (err) => {
               logger.error('Failed to send install proposal due to error: ' +
               err.stack ?
                       err.stack : err);
               throw new Error('Failed to send install proposal due to error: ' +
               err.stack ?
                       err.stack : err);
       });
};
exports.installChaincode = installChaincode;


(4)实例化链码

实例化链码包括如下步骤:

1)client实例切换到传入组织;

2)channel调用initialize(),该方法会使用对应组织的MSPs实例化channel对象;

3)发送背书proposal给endorsers(args里面指定的背书节点);

4)对目标endorsers返回的proposalResponses结果依次校验,所有endorsers都背书成功才进入下一步,有endorsers背书失败则抛出异常;

5)endorsers背书成功后,应用端将背书proposalResponses和之前的proposal打 包成request,调用sendTransaction发给orderer,这时因为orderer经过order后再通知peers进行实例化的操作 是异步的,需要注册transaction event来监听实例化的最终结果;

6)在sendTransaction和transaction event都成功返回的情况下,才说明实例化链码成功,此时返回成功的结构对象,若transaction event监听到失败则抛出异常。


var instantiateChaincode = function(channelName, chaincodeName, chaincodeVersion,
functionName, args, username, org) {
       logger.debug('\n============ Instantiate chaincode on organization ' + org +
               ' ============\n');

       var channel = helper.getChannelForOrg(org);
       var client = helper.getClientForOrg(org);

       return helper.getOrgAdmin(org).then((user) => {

       // channel实例从orderer读取该通道的配置区块,并基于所加入的组织实例化验证MSPs
               return channel.initialize();
       }, (err) => {
               logger.error('Failed to enroll user \'' + username + '\'. ' + err);
               throw new Error('Failed to enroll user \'' + username + '\'. ' + err);
       }).then((success) => {
               tx_id = client.newTransactionID();
               // 发送背书proposal给endorser
               var request = {
                       chaincodeId: chaincodeName,
                       chaincodeVersion: chaincodeVersion,
                       args: args,
                       txId: tx_id
               };

               if (functionName)
                       request.fcn = functionName;

               return channel.sendInstantiateProposal(request);
       }, (err) => {
               logger.error('Failed to initialize the channel');
               throw new Error('Failed to initialize the channel');
       }).then((results) => {
               var proposalResponses = results[0];
               var proposal = results[1];
               var all_good = true;
               for (var i in proposalResponses) {
                       let one_good = false;
                       if (proposalResponses && proposalResponses[i].response &&
                               proposalResponses[i].response.status === 200) {
                               one_good = true;
                               logger.info('instantiate proposal was good');
                       } else {
                               logger.error('instantiate proposal was bad');
                       }
                       all_good = all_good & one_good;
               }
               if (all_good) {
                       logger.info(util.format(
                               'Successfully sent Proposal and received
                               ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
                               proposalResponses[0].response.status,
                               proposalResponses[0].response.message,
                               proposalResponses[0].response.payload,
                               proposalResponses[0].endorsement
                               .signature));
                       var request = {
                               proposalResponses: proposalResponses,
                               proposal: proposal
                       };

           // 设置一个transaction listener并且设置30秒timeout
           // 如果在timeout的时限内,transaction没有被有效提交则返回错误
                       var deployId = tx_id.getTransactionID();

                       eh = client.newEventHub();
                       let data = fs.readFileSync(path.join(__dirname, ORGS[org].
                                peers['peer1'][
                               'tls_cacerts'
                       ]));

                       eh.setPeerAddr(ORGS[org].peers['peer1']['events'], {
                               pem: Buffer.from(data).toString(),
                               'ssl-target-name-override': ORGS[org].peers['peer1']
                                ['server-hostname']
                       });
                       eh.connect();

                       let txPromise = new Promise((resolve, reject) => {
                               let handle = setTimeout(() => {
                                       eh.disconnect();
                                       reject();
                               }, 30000);

                               eh.registerTxEvent(deployId, (tx, code) => {
                                       logger.info(
                                               'The chaincode instantiate
                                               transaction has been committed on peer ' +
                       eh._ep._endpoint.addr);
                                       clearTimeout(handle);
                                       eh.unregisterTxEvent(deployId);
                                       eh.disconnect();

                                       if (code !== 'VALID') {
                                               logger.error('The chaincode
                                               instantiate transaction was invalid, code = ' + code);
                                               reject();
                                       } else {
                                               logger.info('The chaincode
                                               instantiate transaction was valid.');
                                               resolve();
                                       }
                               });
                       });

                       var sendPromise = channel.sendTransaction(request);
                       return Promise.all([sendPromise].concat([txPromise])).
                       then((results) => {
                               logger.debug('Event promise all complete and
                               testing complete');
                               return results[0]; // Promise all队列的第一个返回值
                               是'sendTransaction()'的调用结果
                       }).catch((err) => {
                               logger.error(
                                       util.format('Failed to send instantiate
                                       transaction and get notifications within the timeout period. %s', err)
                               );
                               return 'Failed to send instantiate transaction and
                               get notifications within the timeout period.';
                       });
               } else {
                       logger.error(
                               'Failed to send instantiate Proposal or receive
                               valid response. Response null or status is not 200. exiting...'
                       );
                       return 'Failed to send instantiate Proposal or receive
                       valid response. Response null or status is not 200. exiting...';
               }
       }, (err) => {
               logger.error('Failed to send instantiate proposal due to error: '
               + err.stack ?
                       err.stack : err);
               return 'Failed to send instantiate proposal due to error: ' +
               err.stack ?
                       err.stack : err;
       }).then((response) => {
               if (response.status === 'SUCCESS') {
                       logger.info('Successfully sent transaction to the orderer.');
                       return 'Chaincode Instantiation is SUCCESS';
               } else {
                       logger.error('Failed to order the transaction. Error code:
                       ' + response.status);
                       return 'Failed to order the transaction. Error code: ' +
                       response.status;
               }
       }, (err) => {
               logger.error('Failed to send instantiate due to error: ' + err.
               stack ? err
                       .stack : err);
               return 'Failed to send instantiate due to error: ' + err.stack ?
               err.stack :
                       err;
       });
};
exports.instantiateChaincode = instantiateChaincode;


(5)调用链码

调用链码和之前实例化链码步骤类似,也是需要先发出背书,包括如下步骤。

1)client实例切换到传入组织。

2)发送proposal给endorsers(args里面指定的背书节点)。

3)对目标endorsers返回的proposalResponses结果依次校验,所有endorsers都背书成功才进入下一步,有endorsers背书失败则抛出异常。

4)endorsers背书成功后,应用端将背书proposalResponses和之前的proposal打 包成request,调用sendTransaction发给orderer,这时因为orderer经过order后再通知peers进行调用链码的操 作是异步的,需要注册transaction event来监听调用链码的最终结果。

5)在sendTransaction和transaction event都成功返回的情况下,才说明调用链码成功,此时返回成功的结构对象,若transaction event监听到失败则抛出异常。


var invokeChaincode = function(peerNames, channelName, chaincodeName, fcn, args,
username, org) {
       logger.debug(util.format('\n============ invoke transaction on
       organization %s ============\n', org));
       var client = helper.getClientForOrg(org);
       var channel = helper.getChannelForOrg(org);
       var targets = (peerNames) ? helper.newPeers(peerNames, org) : undefined;
       var tx_id = null;

       var txRequest = null;
       return helper.getRegisteredUsers(username, org).then((user) => {
               tx_id = client.newTransactionID();
               logger.debug(util.format('Sending transaction "%j"', tx_id));
               // 发送背书proposal给endorser
               var request = {
                       chaincodeId: chaincodeName,
                       fcn: fcn,
                       args: args,
                       chainId: channelName,
                       txId: tx_id
               };

               if (targets)
                       request.targets = targets;
               var txRequest = channel.sendTransactionProposal(request)
                       return txRequest;
               }, (err) => {
               logger.error('Failed to enroll user \'' + username + '\'. ' + err);
               throw new Error('Failed to enroll user \'' + username + '\'. ' + err);
               }).then((results) => {
               var proposalResponses = results[0];
               var proposal = results[1];
               var all_good = true;
               for (var i in proposalResponses) {
                       let one_good = false;
                       if (proposalResponses && proposalResponses[i].response &&
                               proposalResponses[i].response.status === 200) {
                               one_good = true;
                               logger.info('transaction proposal was good');
                       } else {
                               logger.error(proposalResponses[i]);
                               logger.error('transaction proposal was bad');
                               if (proposalResponses[i].message != null) {
                               return proposalResponses[i].message;
               }
                       }
                       all_good = all_good & one_good;
               }
               if (all_good) {
                       logger.debug(util.format(
                               'Successfully sent Proposal and received
                               ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
                               proposalResponses[0].response.status,
                               proposalResponses[0].response.message,
                               proposalResponses[0].response.payload,
                               proposalResponses[0].endorsement
                               .signature));
                       var request = {
                               proposalResponses: proposalResponses,
                               proposal: proposal
                       };
                       // 设置一个transaction listener并且设置30秒timeout
                       // 如果在timeout的时限内,transaction没有被有效提交则返回错误
                       var transactionID = tx_id.getTransactionID();
                       var eventPromises = [];

                       if (!peerNames) {
                               peerNames = channel.getPeers().map(function(peer) {
                                       return peer.getName();
                               });
                       }

                       var eventhubs = helper.newEventHubs(peerNames, org);
                       for (let key in eventhubs) {
                               let eh = eventhubs[key];
                               eh.connect();

                               let txPromise = new Promise((resolve, reject) => {
                                       let handle = setTimeout(() => {
                                               eh.disconnect();
                                               reject();
                                       }, 30000);

                       eh.registerTxEvent(transactionID, (tx, code) => {

                       clearTimeout(handle);

                       eh.unregisterTxEvent(transactionID);
                                               eh.disconnect();

                                               if (code !== 'VALID') {
                                                       logger.error(

                                                       'The balance transfer transaction was invalid, code = ' + code);
                                                       reject();
                                               } else {
                                                       logger.info(

                                               'The balance transfer transaction has been committed on peer ' +

                                               eh._ep._endpoint.addr);
                                                       resolve();
                                               }
                                       });
                               });
                               eventPromises.push(txPromise);
                       };
                       var sendPromise = channel.sendTransaction(request);
                       return Promise.all([sendPromise].concat(eventPromises)).
                       then((results) => {
                               logger.debug(' event promise all complete and
                               testing complete');
                               return results[0]; // Promise all队列的第一个返回值
                               // 是'sendTransaction()'的调用结果
                       }).catch((err) => {
                               logger.error(
                                       'Failed to send transaction and get
                                       notifications within the timeout period.'
                               );
                               return 'Failed to send transaction and get
                               notifications within the timeout period.';
                       });
               } else {
                       logger.error(
                               'Failed to send Proposal or receive valid response.
                               Response null or status is not 200. exiting...'
                       );
                       return 'Failed to send Proposal or receive valid response.
                       Response null or status is not 200. exiting...';
               }
       }, (err) => {
               logger.error('Failed to send proposal due to error: ' + err.
               stack ? err.stack :
                       err);
               return 'Failed to send proposal due to error: ' + err.stack ?
               err.stack :
                       err;
       }).then((response) => {
               if (response.status === 'SUCCESS') {
                       logger.info('Successfully sent transaction to the
                       orderer.');
                       return tx_id.getTransactionID();
               } else {
                       if (response.status != null) {
                           logger.error('Failed to order the transaction. Error code: ' + response.status);
                           return 'Failed to order the transaction. Error code: ' + response.status;
                       }else {
               return response;
                       }

               }
       }, (err) => {
               logger.error('Failed to send transaction due to error: ' + err.
               stack ? err .stack : err);
               return 'Failed to send transaction due to error: ' + err.stack ?
               err.stack :err;
       });
};

exports.invokeChaincode = invokeChaincode;


12.4.2 链码功能实现

本节我们来看链码对外提供的功能接口和每个功能接口的实现过程。

1.链码接口定义

链码接口由两部分组成,即调用函数名称和调用参数。

(1)票据发布接口

票据发布的函数名称是issue,只有一个参数,是JSON结构的Bill对象:


   {
   "BillInfoID": "POC10000998",
   "BillInfoAmt": "222",
   "BillInfoType": "111",
   "BillInfoIsseDate": "20170910",
   "BillInfoDueDate": "20171112",
   "DrwrCmID": "111",
   "DrwrAcct": "111",
   "AccptrCmID": "111",
   "AccptrAcct": "111",
   "PyeeCmID": "111",
   "PyeeAcct": "111",
   "HodrCmID": "ACMID",
   "HodrAcct": "A公司"
}


各字段参数说明如表12-4所示。

表12-4 票据发布接口参数

image.png

(2)票据背书接口

票据背书的函数名称是endorse,有3个参数按表12-5所示顺序。

image.png

2.链码接口实现

链码初始化默认实现即可:


// chaincode Init 接口
func (a *BillChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
       return shim.Success(nil)
}


链码调用接口包含票据发布、票据背书、票据签收、票据拒收、票据查询等:


// chaincode Invoke 接口
func (a *BillChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
   function,args := stub.GetFunctionAndParameters()

   // invoke
   if function == "issue" {
       return a.issue(stub, args)
   } else if function == "endorse" {
       return a.endorse(stub, args)
   } else if function == "accept" {
       return a.accept(stub, args)
   } else if function == "reject" {
       return a.reject(stub, args)
   }

   // query
   if function == "queryMyBill" {
       return a.queryMyBill(stub, args)
   } else if function == "queryByBillNo" {
       return a.queryByBillNo(stub, args)
   } else if function == "queryMyWaitBill" {
       return a.queryMyWaitBill(stub, args)
   }

   res := getRetString(1,"ChainnovaChaincode Unkown method!")
       chaincodeLogger.Infof("%s",res)

   return shim.Error(res)
}


链码用到的一些通用接口:


// 链码返回结构
type chaincodeRet struct {
   Code int    // 0 成功 1 其他
   Des  string // 描述
}

// 根据返回码和描述返回序列号后的字节数组
func getRetByte(code int,des string) []byte {
   var r chaincodeRet
   r.Code = code
   r.Des = des

   b,err := json.Marshal(r)

   if err!=nil {
       fmt.Println("marshal Ret failed")
       return nil
   }
   return b
}

// 根据返回码和描述返回序列号后的字符串
func getRetString(code int,des string) string {
   var r chaincodeRet
   r.Code = code
   r.Des = des

   b,err := json.Marshal(r)

   if err!=nil {
       fmt.Println("marshal Ret failed")
       return ""
   }
   chaincodeLogger.Infof("%s",string(b[:]))
   return string(b[:])
}

// 根据票号取出票据
func (a *BillChaincode) getBill(stub shim.ChaincodeStubInterface,bill_No string)
(Bill, bool) {
       var bill Bill
       key := Bill_Prefix + bill_No
       b,err := stub.GetState(key)
       if b==nil {
               return bill, false
       }
       err = json.Unmarshal(b,&bill)
       if err!=nil {
               return bill, false
       }
       return bill, true
}

// 保存票据
func (a *BillChaincode) putBill(stub shim.ChaincodeStubInterface, bill Bill) ([]
byte, bool) {

       byte,err := json.Marshal(bill)
       if err!=nil {
               return nil, false
       }

       err = stub.PutState(Bill_Prefix + bill.BillInfoID, byte)
       if err!=nil {
               return nil, false
       }
       return byte, true
}


(1)票据发布

票据发布的实现如下:


// 票据发布
// args: 0 - {Bill Object}
func (a *BillChaincode) issue(stub shim.ChaincodeStubInterface, args []string)
pb.Response {
       if len(args)!=1 {
               res := getRetString(1,"ChainnovaChaincode Invoke issue args!=1")
               return shim.Error(res)
       }

       var bill Bill
       err := json.Unmarshal([]byte(args[0]), &bill)
       if err!=nil {
               res := getRetString(1,"ChainnovaChaincode Invoke issue unmarshal
               failed")
               return shim.Error(res)
       }

       // 根据票号查找是否票号已存在
       _, existbl := a.getBill(stub, bill.BillInfoID)
       if existbl {
               res := getRetString(1,"ChainnovaChaincode Invoke issue failed :
               the billNo has exist ")
               return shim.Error(res)
       }

       if bill.BillInfoID == "" {
               bill.BillInfoID = fmt.Sprintf("%d", time.Now().UnixNano())
       }

       // 更改票据信息和状态并保存票据:票据状态设为新发布
       bill.State = BillInfo_State_NewPublish

       // 保存票据
       _, bl := a.putBill(stub, bill)
       if !bl {
               res := getRetString(1,"ChainnovaChaincode Invoke issue put bill
               failed")
               return shim.Error(res)
       }
       // 以持票人ID和票号构造复合key 向search表中保存 value为空即可 以便持票人批量查询
       holderNameBillNoIndexKey, err := stub.CreateCompositeKey(IndexName, []
       string{bill.HodrCmID, bill.BillInfoID})
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode Invoke issue put search
               table failed")
               return shim.Error(res)
       }
       stub.PutState(holderNameBillNoIndexKey, []byte{0x00})

       res := getRetByte(0,"invoke issue success")
       return shim.Success(res)
}


(2)票据背书

票据背书的实现如下:


// 背书请求
//  args: 0 - Bill_No ; 1 - Endorser CmId ; 2 - Endorser Acct
func (a *BillChaincode) endorse(stub shim.ChaincodeStubInterface, args []string)
pb.Response {
       if len(args)<3 {
               res := getRetString(1,"ChainnovaChaincode Invoke endorse args<3")
               return shim.Error(res)
       }
   // 根据票号取得票据
       bill, bl := a.getBill(stub, args[0])
       if !bl {
               res := getRetString(1,"ChainnovaChaincode Invoke endorse get bill
               error")
               return shim.Error(res)
       }

       if bill.HodrCmID == args[1] {
               res := getRetString(1,"ChainnovaChaincode Invoke endorse failed:
               Endorser should not be same with current Holder")
               return shim.Error(res)
       }
       // 更改票据信息和状态并保存票据: 添加待背书人信息,重置已拒绝背书人,票据状态改为待背书
       bill.WaitEndorserCmID = args[1]
       bill.WaitEndorserAcct = args[2]
       bill.RejectEndorserCmID = ""
       bill.RejectEndorserAcct = ""
       bill.State = BillInfo_State_EndrWaitSign

       // 保存票据
       _, bl = a.putBill(stub, bill)
       if !bl {
               res := getRetString(1,"ChainnovaChaincode Invoke endorse put bill
               failed")
               return shim.Error(res)
       }
       // 以待背书人ID和票号构造复合key 向search表中保存 value为空即可 以便待背书人批量查询
       holderNameBillNoIndexKey, err := stub.CreateCompositeKey(IndexName, []
       string{bill.WaitEndorserCmID, bill.BillInfoID})
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode Invoke endorse put search
               table failed")
               return shim.Error(res)
       }
       stub.PutState(holderNameBillNoIndexKey, []byte{0x00})

       res := getRetByte(0,"invoke endorse success")
       return shim.Success(res)
}


(3)票据背书签收

票据背书签收的实现如下:


// 背书人接受背书
// args: 0 - Bill_No ; 1 - Endorser CmId ; 2 - Endorser Acct
func (a *BillChaincode) accept(stub shim.ChaincodeStubInterface, args []string)
pb.Response {
       if len(args)<3 {
               res := getRetString(1,"ChainnovaChaincode Invoke accept args<3")
               return shim.Error(res)
       }
       // 根据票号取得票据
       bill, bl := a.getBill(stub, args[0])
       if !bl {
               res := getRetString(1,"ChainnovaChaincode Invoke accept get bill
               error")
               return shim.Error(res)
       }

   // 维护search表: 以前手持票人ID和票号构造复合key 从search表中删除该key 以便前手持
   票人无法再查到该票据
       holderNameBillNoIndexKey, err := stub.CreateCompositeKey(IndexName, []
       string{bill.HodrCmID, bill.BillInfoID})
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode Invoke accept put search
               table failed")
               return shim.Error(res)
       }
       stub.DelState(holderNameBillNoIndexKey)

       // 更改票据信息和状态并保存票据: 将前手持票人改为背书人,重置待背书人,票据状态改为背
       书签收
       bill.HodrCmID = args[1]
       bill.HodrAcct = args[2]
       bill.WaitEndorserCmID = ""
       bill.WaitEndorserAcct = ""
       bill.State = BillInfo_State_EndrSigned

   // 保存票据
       _, bl = a.putBill(stub, bill)
       if !bl {
               res := getRetString(1,"ChainnovaChaincode Invoke accept put bill
               failed")
               return shim.Error(res)
       }

       res := getRetByte(0,"invoke accept success")
       return shim.Success(res)
}


(4)票据背书拒收

票据背书拒收的实现如下:


// 背书人拒绝背书
// args: 0 - Bill_No ; 1 - Endorser CmId ; 2 - Endorser Acct
func (a *BillChaincode) reject(stub shim.ChaincodeStubInterface, args []string)
pb.Response {
       if len(args)<3 {
               res := getRetString(1,"ChainnovaChaincode Invoke reject args<3")
               return shim.Error(res)
       }
   // 根据票号取得票据
       bill, bl := a.getBill(stub, args[0])
       if !bl {
               res := getRetString(1,"ChainnovaChaincode Invoke reject get bill
               error")
               return shim.Error(res)
       }

       // 维护search表: 以当前背书人ID和票号构造复合key 从search表中删除该key 以便当
       前背书人无法再查到该票据
       holderNameBillNoIndexKey, err := stub.CreateCompositeKey(IndexName, []
       string{args[1], bill.BillInfoID})
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode Invoke reject put search
               table failed")
               return shim.Error(res)
       }
       stub.DelState(holderNameBillNoIndexKey)

       // 更改票据信息和状态并保存票据:将拒绝背书人改为当前背书人,重置待背书人,票据状态改
       为背书拒绝
       bill.WaitEndorserCmID = ""
       bill.WaitEndorserAcct = ""
       bill.RejectEndorserCmID = args[1]
       bill.RejectEndorserAcct = args[2]
       bill.State = BillInfo_State_EndrReject

       // 保存票据
       _, bl = a.putBill(stub, bill)
       if !bl {
               res := getRetString(1,"ChainnovaChaincode Invoke reject put bill
               failed")
               return shim.Error(res)
       }

       res := getRetByte(0,"invoke accept success")
       return shim.Success(res)
}


(5)票据信息查询

获取自己持有的票据的实现如下:


// 查询我的票据:根据持票人编号 批量查询票据
//  0 - Holder CmId ;
func (a *BillChaincode) queryMyBill(stub shim.ChaincodeStubInterface, args []
string) pb.Response {
       if len(args)!=1 {
               res := getRetString(1,"ChainnovaChaincode queryMyBill args!=1")
               return shim.Error(res)
       }
       // 以持票人ID从search表中批量查询所持有的票号
       billsIterator, err := stub.GetStateByPartialCompositeKey(IndexName, []
       string{args[0]})
       if err != nil {
       res := getRetString(1,"ChainnovaChaincode queryMyBill get bill list error")
               return shim.Error(res)
       }
       defer billsIterator.Close()

       var billList = []Bill{}

       for billsIterator.HasNext() {
               kv, _ := billsIterator.Next()
               // 取得持票人名下的票号
               _, compositeKeyParts, err := stub.SplitCompositeKey(kv.Key)
               if err != nil {
                       res := getRetString(1,"ChainnovaChaincode queryMyBill
                       SplitCompositeKey error")
                       return shim.Error(res)
               }
               // 根据票号取得票据
               bill, bl := a.getBill(stub, compositeKeyParts[1])
               if !bl {
                       res := getRetString(1,"ChainnovaChaincode queryMyBill get
                       bill error")
                       return shim.Error(res)
               }
               billList = append(billList, bill)
       }
       // 取得并返回票据数组
       b, err := json.Marshal(billList)
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode Marshal queryMyBill
               billList error")
               return shim.Error(res)
       }
       return shim.Success(b)
}
 


查询我的待背书票据实现如下:


// 查询我的待背书票据: 根据背书人编号 批量查询票据
//  0 - Endorser CmId ;
func (a *BillChaincode) queryMyWaitBill(stub shim.ChaincodeStubInterface, args []
string) pb.Response {
       if len(args)!=1 {
               res := getRetString(1,"ChainnovaChaincode queryMyWaitBill args!=1")
               return shim.Error(res)
       }
       // 以背书人ID从search表中批量查询所持有的票号
       billsIterator, err := stub.GetStateByPartialCompositeKey(IndexName, []
       string{args[0]})
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode queryMyWaitBill
               GetStateByPartialCompositeKey error")
               return shim.Error(res)
       }
       defer billsIterator.Close()

       var billList = []Bill{}

       for billsIterator.HasNext() {
               kv, _ := billsIterator.Next()
               // 从search表中批量查询与背书人有关的票号
               _, compositeKeyParts, err := stub.SplitCompositeKey(kv.Key)
               if err != nil {
                       res := getRetString(1,"ChainnovaChaincode queryMyWaitBill
                       SplitCompositeKey error")
                       return shim.Error(res)
               }
               // 根据票号取得票据
               bill, bl := a.getBill(stub, compositeKeyParts[1])
               if !bl {
                       res := getRetString(1,"ChainnovaChaincode queryMyWaitBill
                       get bill error")
                       return shim.Error(res)
               }
               // 取得状态为待背书的票据 并且待背书人是当前背书人
               if bill.State == BillInfo_State_EndrWaitSign && bill.
               WaitEndorserCmID == args[0] {
                       billList = append(billList, bill)
               }
       }
       // 取得并返回票据数组
       b, err := json.Marshal(billList)
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode Marshal queryMyWaitBill
               billList error")
               return shim.Error(res)
       }
       return shim.Success(b)
}


根据票据号码查询票据的详细信息实现如下:


// 根据票号取得票据 以及该票据背书历史
//  0 - Bill_No ;
func (a *BillChaincode) queryByBillNo(stub shim.ChaincodeStubInterface, args []string) pb.Response {
       if len(args)!=1 {
               res := getRetString(1,"ChainnovaChaincode queryByBillNo args!=1")
               return shim.Error(res)
       }
       // 取得该票据
       bill, bl := a.getBill(stub, args[0])
       if !bl {
               res := getRetString(1,"ChainnovaChaincode queryByBillNo get bill error")
               return shim.Error(res)
       }

       // 取得背书历史: 通过fabric api取得该票据的变更历史
       resultsIterator, err := stub.GetHistoryForKey(Bill_Prefix+args[0])
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode queryByBillNo GetHistoryForKey error")
               return shim.Error(res)
       }
       defer resultsIterator.Close()

       var history []HistoryItem
       var hisBill Bill
       for resultsIterator.HasNext() {
               historyData, err := resultsIterator.Next()
               if err != nil {
                       res := getRetString(1,"ChainnovaChaincode queryByBillNo
                       resultsIterator.Next() error")
                       return shim.Error(res)
               }

               var hisItem HistoryItem
               hisItem.TxId = historyData.TxId //copy transaction id over
               json.Unmarshal(historyData.Value, &hisBill)
               // un stringify it aka  JSON.parse()
               if historyData.Value == nil {            //bill has been deleted
                       var emptyBill Bill
                       hisItem.Bill = emptyBill //copy nil marble
               } else {
                       json.Unmarshal(historyData.Value, &hisBill)
               // un stringify it aka JSON.parse()
                       hisItem.Bill = hisBill                //copy bill over
               }
               history = append(history, hisItem) //add this tx to the list
       }
       // 将背书历史作为票据的一个属性 一同返回
       bill.History = history

       b, err := json.Marshal(bill)
       if err != nil {
               res := getRetString(1,"ChainnovaChaincode Marshal queryByBillNo
               billList error")
               return shim.Error(res)
       }
       return shim.Success(b)
}



来源:我是码农,转载请保留出处和链接!

本文链接:http://www.54manong.com/?id=1044

区块链是什么  

微信号:qq444848023    QQ号:444848023

加入【我是码农】QQ群:864689844(加群验证:我是码农)

<< 上一篇 下一篇 >>

网站分类

标签列表

最近发表

全站首页 | 数据结构 | 区块链| 大数据 | 机器学习 | 物联网和云计算 | 面试笔试

本站资源大部分来自互联网,版权归原作者所有!