背景描述:
在供应链金融产品中,供应商、核心企业、银行、金融机构等多方并存,共同参与交易完成。由于参与方众多,其中涉及很多清算和结算功能,如果采用传统方案解决会产生很多中间环节,导致效率低下。区块链的出现给供应链金融的实现提供了新的解决方案。
案例描述:
案例实现的是简单的“应收账款融资”场景。
业务流程:
1、核心企业与供应商线下签订合同并发货
2、供应商在链上发起供货交易
3、核心企业和金融机构确认并签名交易
4、金融机构发起放款请求给供应商
以上每一笔交易都需要所有参与方认同。
环境配置
演示为三个组织——核心企业、供应商、金融机构。
使用环境为fabric v1.1
IP | 节点 | 域名 | 组织名称 |
---|---|---|---|
10.254.186.164 | orderer | orderer.gyl.com | 排序节点 |
10.254.186.164 | peer | peer0.org1.gyl.com | 供应商 |
10.254.247.165 | peer | peer0.org2.gyl.com | 金融机构 |
10.254.207.154 | peer | peer0.org3.gyl.com | 核心企业 |
合约部分
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
"time"
"encoding/json"
)
var asset_time = "asset_name_a"
type scfinancechaincode struct {}
/**
系统初始化
*/
// Init callback representing the invocation of a chaincode
func (t *scfinancechaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
fmt.Println("Init success! ")
return shim.Success([]byte("Init success !!!!!"))
}
/**
系统Invoke方法
*/
func (t *scfinancechaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
_, args := stub.GetFunctionAndParameters()
var opttype = args[0] //操作
var assetname = args[1] //货物名
var optcontent = args[2] //内容
fmt.Printf("param is %s %s %s \n",opttype,assetname,optcontent)
if opttype == "putvalue" { //设置
stub.PutState(assetname,[]byte(optcontent))
return shim.Success([]byte("success put " + optcontent))
}else if opttype == "getlastvalue" { //取值
var keyvalue []byte
var err error
keyvalue,err = stub.GetState(assetname)
if err != nil {
return shim.Error("find error!")
}
return shim.Success(keyvalue)
}else if opttype == "gethistory" { //获取交易记录
keyIter, err := stub.GetHistoryForKey(assetname)
if err != nil {
return shim.Error(fmt.Sprintf("GetHistoryForKey failed. Error accessing state: %s", err.Error()))
}
defer keyIter.Close()
var keys []string //存储所有的交易ID
for keyIter.HasNext() {
response, iterErr := keyIter.Next()
if iterErr != nil {
return shim.Error(fmt.Sprintf("GetHistoryForKey operation failed. Error accessing state: %s", iterErr.Error()))
}
//交易编号
txid := response.TxId
//交易的值
txvalue := response.Value
//当前交易状态
txstatus := response.IsDelete
//交易发生的时间戳
txtimestamp := response.Timestamp
tm := time.Unix(txtimestamp.Seconds,0)
datestr := tm.Format("2006-01-02 03:04:05 PM")
fmt.Printf("Tx info - txid : %s value : %s if delete: %t datetime: %s \n",txid,string(txvalue),txstatus,datestr)
keys = append(keys,txid)
}
jsonKeys, err := json.Marshal(keys)
if err != nil {
return shim.Error(fmt.Sprintf("query operation failed. Error marshaling JSON: %s", err.Error()))
}
return shim.Success(jsonKeys)
}else {
return shim.Success([]byte("success invoke but invalid operation"))
}
}
func main() {
err := shim.Start(new(scfinancechaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
client部分
var path = require('path');
var fs = require('fs');
var util = require('util');
var hfc = require('fabric-client');
var Peer = require('fabric-client/lib/Peer.js');
var EventHub = require('fabric-client/lib/EventHub.js');
var User = require('crypto');
var FabricCAService = require('fabric-ca-client');
//var log4js = require('log4js');
//var logger = log4js.getLogger('Helper');
//logger.setLevel('DEBUG');
var tempdir = "/home/dc2-user/kongli/fabric-client-js-kvs";
let client = new hfc();
let tls_cacerts_content_orderer = fs.readFileSync('./orderer/tls/ca.crt');
let opt_orderer = {
pem: Buffer.from(tls_cacerts_content_orderer).toString(),
'ssl-target-name-override':'orderer.gyl.com'
};
//peer1
let tls_cacerts_content_peer1 = fs.readFileSync('./peer1/tls/ca.crt');
let opt_peer1 = {
pem: Buffer.from(tls_cacerts_content_peer1).toString(),
'ssl-target-name-override':'peer0.org1.gyl.com'
};
//peer2
let tls_cacerts_content_peer2 = fs.readFileSync('./peer2/tls/ca.crt');
let opt_peer2 = {
pem: Buffer.from(tls_cacerts_content_peer2).toString(),
'ssl-target-name-override':'peer0.org2.gyl.com'
};
//peer3
let tls_cacerts_content_peer3 = fs.readFileSync('./peer3/tls/ca.crt');
let opt_peer3 = {
pem: Buffer.from(tls_cacerts_content_peer3).toString(),
'ssl-target-name-override':'peer0.org3.gyl.com'
};
var channel = client.newChannel('gylchannel');
var order = client.newOrderer('grpcs://10.254.186.164:7050',opt_orderer);
channel.addOrderer(order);
var peer1 = client.newPeer('grpcs://10.254.186.164:7051',opt_peer1);
var peer2 = client.newPeer('grpcs://10.254.247.165:7051',opt_peer2);
var peer3 = client.newPeer('grpcs://10.254.207.154:7051',opt_peer3);
channel.addPeer(peer1);
channel.addPeer(peer2);
channel.addPeer(peer3);
var event_url = 'grpcs://10.254.186.164:7053';
/**
发起交易
@returns {Promis.}
*/
var sendTransaction = function(chaincodeid, func, chaincode_args, channelId) {
var tx_id = null;
return getOrgUser4Local().then((user)=>{
tx_id = client.newTransactionID();
var request = {
chaincodeId: chaincodeid,
fcn: func,
args: chaincode_args,
chainId: channelId,
txId: tx_id
};
return channel.sendTransactionProposal(request);
},(err)=>{
console.log('error',err);
}).then((chaincodeinvokresult)=>{
var proposalResponses = chaincodeinvokresult[0];
var proposal = chaincodeinvokresult[1];
var header = chaincodeinvokresult[2];
var all_good = true;
for (var i in proposalResponses) {
let one_good = false;
//成功
if (proposalResponses && proposalResponses[0].response && proposalResponses[0].response.status == 200) {
one_good = true;
console.info('transaction proposal was good');
}else {
console.error('transaction proposal was bad');
}
all_good = all_good & one_good;
}
if (all_good) {
console.info(util.format(
'Successfully sent proposal and received proposalResponses: Status - %s, message - "%s", metadate - "%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,
orderer: order,
txId: tx_id,
header:header
};
var transactionID = tx_id.getTransactionID();
var eventPromises = [];
let eh = client.newEventHub();
//接下来设置EventHub,用于监听Transaction是否成功写入,这里也是启用了TLS
eh.setPeerAddr(event_url,opt_peer1);
eh.connect();
let txPromise = new Promise((resolve, reject) => {
let handle = setTimeout(() => {
eh.disconnect();
reject();
}, 30000);
//向EventHub注册事件的处理办法
eh.registerTxEvent(transactionID, (tx, code) => {
clearTimeout(handle);
eh.unregisterTxEvent(transactionID);
eh.disconnect();
if (code !== 'VALID') {
console.error(
'The transaction was invalid, code = ' + code);
reject();
} else {
console.log(
'The transaction has been committed on peer ' +
eh._ep._endpoint.addr);
resolve();
}
});
});
eventPromises.push(txPromise);
//把背书后的结果发到orderer排序
var sendPromise = channel.sendTransaction(request);
return Promise.all([sendPromise].concat(eventPromises)).then((results) => {
console.log(' event promise all complete and testing complete');
return results[0]; // the first returned value is from the 'sendPromise' which is from the 'sendTransaction()' call
}).catch((err) => {
console.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 {
console.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)=>{
console.log('error',err);
}).then((response)=>{
if (response.status === 'SUCCESS') {
console.log('Successfully sent transaction to the orderer.');
return tx_id.getTransactionID();
} else {
console.error('Failed to order the transaction. Error code: ' + response.status);
return 'Failed to order the transaction. Error code: ' + response.status;
}
},(err)=>{
console.log('error',err);
});
}
/**
根据cryptogen模块生成的账号通过Fabric接口进行相关操作
@returns {Promise.}
*/
function getOrgUser4Local(){
var keyPath = "./users/keystore";
var keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString();
var certPath = "./users/signcerts";
var certPEM = readAllFiles(certPath)[0].toString();
return hfc.newDefaultKeyValueStore({
path: tempdir
}).then((store)=>{
client.setStateStore(store);
return client.createUser({
username: 'user87',
mspid: 'GylOrg1MSP',
cryptoContent: {
privateKeyPEM: keyPEM,
signedCertPEM: certPEM
}
});
});
};
function readAllFiles(dir) {
var files = fs.readdirSync(dir);
var certs = [];
files.forEach((file_name)=>{
let file_path = path.join(dir,file_name);
let data = fs.readFileSync(file_path);
certs.push(data);
});
return certs;
}
/**
获取channel的区块信息
@returns {Promise.}
*/
var getBlockChainInfo = function() {
return getOrgUser4Local().then((user)=>{
return channel.queryInfo(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
根据区块链的编号获取详细信息
@param blocknum
@returns {Promise.}
*/
var getblockInfobyNum = function(blocknum) {
return getOrgUser4Local().then((user)=>{
return channel.queryBlock(blocknum,peer1,null);
},(err)=>{
console.log('error',err);
})
}
/**
根据区块链的哈希值获取区块详细信息
@param blockhash
@returns {Promise.}
*/
var getblockInfobyHash = function(blockHash) {
return getOrgUser4Local().then((user)=>{
return channel.queryBlockByHash(new Buffer(blockHash,"hex"),peer1);
},(err)=>{
console.log('error',err);
})
}
/**
获取当前节点加入的通道信息
@returns {Promise.}
*/
var getPeerChannel = function() {
return getOrgUser4Local().then((user)=>{
return client.queryChannels(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查询指定peer节点已经install的chaincode
@returns {Promise.}
*/
var getPeerInstallCc = function() {
return getOrgUser4Local().then((user)=>{
return client.queryInstalledChaincodes(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查询指定channel中已实例化的chaincode
@returns {Promise.}
*/
var getPeerInstantiatedCc = function() {
return getOrgUser4Local().then((user)=>{
return channel.queryInstantiatedChaincodes(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查询指定交易所在区块信息
@param txId
@returns {Promis.}
*/
var getBlockByTxID = function(TxID) {
return getOrgUser4Local().then((user)=>{
return channel.queryBlockByTxID(TxID,peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查询指定交易所在区块信息
@param txId
@returns {Promis.}
*/
var getTransaction = function(TxID) {
return getOrgUser4Local().then((user)=>{
return channel.queryTransaction(TxID,peer1);
},(err)=>{
console.log('error',err);
})
}
exports.sendTransaction = sendTransaction;
exports.getBlockChainInfo = getBlockChainInfo;
exports.getblockInfobyNum = getblockInfobyNum;
exports.getblockInfobyHash = getblockInfobyHash;
exports.getPeerChannel = getPeerChannel;
exports.getPeerInstantiatedCc = getPeerInstantiatedCc;
exports.getPeerInstallCc = getPeerInstallCc;
exports.getBlockByTxID = getBlockByTxID;
exports.getTransaction = getTransaction;
浏览器部分
var co = require('co');
var fabricservice = require('./fabricservice.js');
var express = require('express');
var app = express();
var channelid = "gylchannel";
var chaincodeid = "gyl";
//供应商发起供货交易
app.get('/sendTransaction1',function(req,res){
co(function * () {
var k = req.query.k;
var v = req.query.v;
var blockinfo = yield fabricservice.sendTransaction(chaincodeid,"invoke",["putvalue",k,v],channelid);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
});
//核心企业发起确认
app.get('/sendTransaction2',function(req,res){
co(function * () {
var k = req.query.k;
var v = req.query.v;
var blockinfo = yield fabricservice.sendTransaction(chaincodeid,"invoke",["putvalue",k,v],channelid);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//金融机构审核并放款
app.get('/sendTransaction3',function(req,res){
co(function * () {
var k = req.query.k;
var v = req.query.v;
var blockinfo = yield fabricservice.sendTransaction(chaincodeid,"invoke",["putvalue",k,v],channelid);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//查询交易记录
app.get('/queryhistory',function(req,res){
co(function * () {
var k = req.query.k;
var blockinfo = yield fabricservice.sendTransaction(chaincodeid,"invoke",["gethistory",k,"-1"],channelid);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//查询最新结果
app.get('/getlastvalue',function(req,res){
co(function * () {
var k = req.query.k;
var blockinfo = yield fabricservice.sendTransaction(chaincodeid,"invoke",["getlastvalue",k,"-1"],channelid);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//获取当前通道块儿高度
app.get('/getchannelheight',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getBlockChainInfo();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//根据区块编号获取区块信息
app.get('/getblockInfobyNum',function(req,res){
co(function * () {
var param = parseInt(req.query.params);
var blockinfo = yield fabricservice.getblockInfobyNum(param);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//根据区块Hash值获取区块信息
app.get('/getblockInfobyHash',function(req,res){
co(function * () {
var param = req.query.params;
var blockinfo = yield fabricservice.getblockInfobyHash(param);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//获取指定peer节点加入的通道数
app.get('/getPeerChannel',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getPeerChannel();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//获取channel已经安装的链码
app.get('/getPeerInstallCc',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getPeerInstallCc();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//获取指定channel已经实例化的链码
app.get('/getPeerInstantiatedCc',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getPeerInstantiatedCc();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//通过交易ID获取区块信息
app.get('/getBlockByTxID',function(req,res){
co(function * () {
var param = req.query.TxID;
var blockinfo = yield fabricservice.getBlockByTxID(param);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//通过交易ID获取交易信息
app.get('/getTransaction',function(req,res){
co(function * () {
var param = req.query.TxID;
var blockinfo = yield fabricservice.getTransaction(param);
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//启动http服务
var server = app.listen(3000,function(){
var host = server.address().address;
var port = server.address().port;
console.log('Example app listen at http://%s:%s',host,port);
});
//注册异常处理器
process.on('unhandleRejection',function(err){
console.error(err.stack);
});
process.on('uncaughtException',console.error);
流程
1、供应商发起供货交易调用,如:
http://116.85.10.181:3000/sendTransaction1?k=food&v=1
2、核心企业发起确认,如:
http://116.85.10.181:3000/sendTransaction2?k=food&v=2
3、银行审核后确认放款,如:
http://116.85.10.181:3000/sendTransaction3?k=food&v=3
目前链码写的比较简单,最好每个组织有自己的链码,组织间约定好操作内容。即使采用同样的链码,由于账本中会记录调用者的身份信息(组织以及签名等),所以假如供应商直接调用http://116.85.10.181:3000/sendTransaction3?k=food&v=3 虽然通过但是账本会记录调用者不是银行机构,可以认定无效。
注意:
1、需要准备npm环境,fabric-client 需要在v.1.10 grpc为v.1.10.1
可以使用npm list xxx 来查看版本信息。
2、使用浏览器访问需要开通端口访问权限。