票据背书的实现分为两个部分,即基于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 后端服务的接口参数定义

返回信息如下:


{
   "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 票据发布接口参数

(2)票据背书接口

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

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

'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646208", container: s }); })(); '); (window.slotbydup = window.slotbydup || []).push({ id: "u3646147", container: s }); })();

12.4 票据背书实现相关推荐

  1. 12.6 票据背书展示

    按照http://ip:4000/ng/src即可访问,我们下面看下实现的效果. 12.6.1 系统登录 系统登录页面如图12-4所示. 图12-4 系统登录效果图 默认提供的用户名和密码如表12-1 ...

  2. 12.2 票据背书需求分析

    本章旨在通过案例讲述如何基于超级账本开发一个简单的区块链应用,票据背书的应用开发实例会对票据的应用场景进行简化,我们实现的业务逻辑包括票据发布.票据背书.票据签收.票据拒收.票据查询等操作,实际的票据 ...

  3. 票据背书以及票据背书的好处

    今天带领大家认识下票据背书的过程,以银票(电票)背书为例,我们首先来看下实例,下面两张分别是银票的正面和背面. 图1 票据的正面 图2 票据的背面 也许有些人会问,为什么叫票据背书,其实可以简单的从字 ...

  4. 12.5 票据背书快速部署

    在Github上提供了快速启动区块链网络和初始化的脚本.启动区块链网络和前端服务的脚本如下: ./setupFabricNetwork.sh & 创建通道及安装实例化链码的脚本如下:加 入 会 ...

  5. 票据 计算机 英语,银行常用英语:汇款汇票汇单用语

    汇款用语 汇款||寄钱 to remit||to send money 寄票供取款||支票支付 to send a cheque for payment 寄款人 a remitter 收款人 a re ...

  6. 国际结算业务--国际结算中的票据

    国际结算业务--国际结算中的票据 第一节 票据概述票据有广义和狭义之分.国际结算中的票据是指狭义的票据,它代替现金起流通和支付作用,从而抵消和清偿国际间债权债务,因而是国际结算中的重要工具.狭义的票据 ...

  7. 票据交易系统-票据交易快人一步

    一.系统介绍 票据交易系统为持票人.票据机构.企业.银行提供建立发布及交易撮合一站式服务平台,全方位为广大有票据流转交易需求的用户提供便捷.安全.全面的电子票据交易渠道,致力于为每个平台会员打造一个& ...

  8. 【渝粤题库】陕西师范大学209011商业银行信贷管理Ⅱ 作业(专升本)

    <商业银行信贷管理Ⅱ>作业 一.单项选择题 1.我国改革开放以来信贷资金来源的最主要支柱是( ). A.企业存款 B.财政存款 C.储蓄存款 D.外汇存款 2.汇票的承兑人明确表示按照票据 ...

  9. 区块链要去中心化么?

    本文摘自< 深度探索区块链:Hyperledger技术与应用 >,原文发布在华章计算机. 区块链(Blockchain)技术自身仍然在飞速发展中,目前还缺乏统一的规范和标准.Wikiped ...

最新文章

  1. 预示敏捷方法走偏的15个标志——第1部分
  2. Spring servlet
  3. 数据结构算法集---C++语言实现
  4. 转载:如何优雅的实现INotifyPropertyChanged接口
  5. JavaScript中数组的增删改查以及应用方式
  6. 2017.8.12 联考题
  7. 【攻防世界003】re-for-50-plz-50
  8. Linux下远程连接断开后如何让程序继续运行
  9. 《x86汇编语言:从实模式到保护模式》读书笔记之后记
  10. 迭代器模式coding
  11. zencart 后台 tool-define page editor define define_main_page.php
  12. netbeans7.4_NetBeans 7.4 Beta提示警告无效的异常处理
  13. python爬取商城数据_Python爬取新版CRMEB小程序商城后台订单数据,保存为excel
  14. android 快捷方式代码片段随记
  15. JS处理支付宝H5支付
  16. 北京业内网友见面会,及其他
  17. iPhone、iPad分辨率和显示屏规格
  18. 读论文:Fine-grained Image Classification via Combining Vision and Language
  19. 飞行堡垒9win键解锁
  20. idea有时不进断点的原因

热门文章

  1. VI高级命令集锦及VIM应用实例
  2. 如何与病毒搏斗?这部BBC“史诗级大片”告诉你答案
  3. 北京内推 | 科大讯飞智慧医疗研究院招聘NLP算法工程师(可实习)
  4. 吹牛的资本之Hibernate框架,五分钟搞定Hibernate...
  5. 哄女孩子:代码写的好不好不重要,重要怎么哄女生
  6. ubuntu14.04开启wifi热点
  7. Outlook开机自启+关闭时最小化
  8. 这11位作家,要用AI写科幻小说了
  9. [z]JMS简明学习教程
  10. 连续四年发布科技趋势预测,他们在探索中国科技的“主干道”