背景描述:
在供應(yīng)鏈金融產(chǎn)品中,供應(yīng)商、核心企業(yè)、銀行、金融機(jī)構(gòu)等多方并存,共同參與交易完成。由于參與方眾多,其中涉及很多清算和結(jié)算功能,如果采用傳統(tǒng)方案解決會產(chǎn)生很多中間環(huán)節(jié),導(dǎo)致效率低下。區(qū)塊鏈的出現(xiàn)給供應(yīng)鏈金融的實現(xiàn)提供了新的解決方案。
案例描述:
案例實現(xiàn)的是簡單的“應(yīng)收賬款融資”場景。
業(yè)務(wù)流程:
1、核心企業(yè)與供應(yīng)商線下簽訂合同并發(fā)貨
2、供應(yīng)商在鏈上發(fā)起供貨交易
3、核心企業(yè)和金融機(jī)構(gòu)確認(rèn)并簽名交易
4、金融機(jī)構(gòu)發(fā)起放款請求給供應(yīng)商
以上每一筆交易都需要所有參與方認(rèn)同。
環(huán)境配置
演示為三個組織——核心企業(yè)、供應(yīng)商、金融機(jī)構(gòu)。
使用環(huán)境為fabric v1.1
| IP | 節(jié)點(diǎn) | 域名 | 組織名稱 |
|---|---|---|---|
| 10.254.186.164 | orderer | orderer.gyl.com | 排序節(jié)點(diǎn) |
| 10.254.186.164 | peer | peer0.org1.gyl.com | 供應(yīng)商 |
| 10.254.247.165 | peer | peer0.org2.gyl.com | 金融機(jī)構(gòu) |
| 10.254.207.154 | peer | peer0.org3.gyl.com | 核心企業(yè) |
合約部分
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 {}
/**
系統(tǒng)初始化
*/
// 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 !!!!!"))
}
/**
系統(tǒng)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] //內(nèi)容
fmt.Printf("param is %s %s %s \n",opttype,assetname,optcontent)
if opttype == "putvalue" { //設(shè)置
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
//當(dāng)前交易狀態(tài)
txstatus := response.IsDelete
//交易發(fā)生的時間戳
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';
/**
發(fā)起交易
@returns {Promis.<TResult>}
*/
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();
//接下來設(shè)置EventHub,用于監(jiān)聽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);
//把背書后的結(jié)果發(fā)到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);
});
}
/**
根據(jù)cryptogen模塊生成的賬號通過Fabric接口進(jìn)行相關(guān)操作
@returns {Promise.<TResult>}
*/
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的區(qū)塊信息
@returns {Promise.<TResult>}
*/
var getBlockChainInfo = function() {
return getOrgUser4Local().then((user)=>{
return channel.queryInfo(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
根據(jù)區(qū)塊鏈的編號獲取詳細(xì)信息
@param blocknum
@returns {Promise.<TResult>}
*/
var getblockInfobyNum = function(blocknum) {
return getOrgUser4Local().then((user)=>{
return channel.queryBlock(blocknum,peer1,null);
},(err)=>{
console.log('error',err);
})
}
/**
根據(jù)區(qū)塊鏈的哈希值獲取區(qū)塊詳細(xì)信息
@param blockhash
@returns {Promise.<TResult>}
*/
var getblockInfobyHash = function(blockHash) {
return getOrgUser4Local().then((user)=>{
return channel.queryBlockByHash(new Buffer(blockHash,"hex"),peer1);
},(err)=>{
console.log('error',err);
})
}
/**
獲取當(dāng)前節(jié)點(diǎn)加入的通道信息
@returns {Promise.<TResult>}
*/
var getPeerChannel = function() {
return getOrgUser4Local().then((user)=>{
return client.queryChannels(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查詢指定peer節(jié)點(diǎn)已經(jīng)install的chaincode
@returns {Promise.<TResult>}
*/
var getPeerInstallCc = function() {
return getOrgUser4Local().then((user)=>{
return client.queryInstalledChaincodes(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查詢指定channel中已實例化的chaincode
@returns {Promise.<TResult>}
*/
var getPeerInstantiatedCc = function() {
return getOrgUser4Local().then((user)=>{
return channel.queryInstantiatedChaincodes(peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查詢指定交易所在區(qū)塊信息
@param txId
@returns {Promis.<TResult>}
*/
var getBlockByTxID = function(TxID) {
return getOrgUser4Local().then((user)=>{
return channel.queryBlockByTxID(TxID,peer1);
},(err)=>{
console.log('error',err);
})
}
/**
查詢指定交易所在區(qū)塊信息
@param txId
@returns {Promis.<TResult>}
*/
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";
//供應(yīng)商發(fā)起供貨交易
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);
})
});
//核心企業(yè)發(fā)起確認(rèn)
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);
})
})
//金融機(jī)構(gòu)審核并放款
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);
})
})
//查詢最新結(jié)果
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);
})
})
//獲取當(dāng)前通道塊兒高度
app.get('/getchannelheight',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getBlockChainInfo();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//根據(jù)區(qū)塊編號獲取區(qū)塊信息
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);
})
})
//根據(jù)區(qū)塊Hash值獲取區(qū)塊信息
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節(jié)點(diǎn)加入的通道數(shù)
app.get('/getPeerChannel',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getPeerChannel();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//獲取channel已經(jīng)安裝的鏈碼
app.get('/getPeerInstallCc',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getPeerInstallCc();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//獲取指定channel已經(jīng)實例化的鏈碼
app.get('/getPeerInstantiatedCc',function(req,res){
co(function * () {
var blockinfo = yield fabricservice.getPeerInstantiatedCc();
res.send(JSON.stringify(blockinfo));
}).catch((err)=>{
res.send(err);
})
})
//通過交易ID獲取區(qū)塊信息
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服務(wù)
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、供應(yīng)商發(fā)起供貨交易調(diào)用,如:
http://116.85.10.181:3000/sendTransaction1?k=food&v=1
2、核心企業(yè)發(fā)起確認(rèn),如:
http://116.85.10.181:3000/sendTransaction2?k=food&v=2
3、銀行審核后確認(rèn)放款,如:
http://116.85.10.181:3000/sendTransaction3?k=food&v=3
目前鏈碼寫的比較簡單,最好每個組織有自己的鏈碼,組織間約定好操作內(nèi)容。即使采用同樣的鏈碼,由于賬本中會記錄調(diào)用者的身份信息(組織以及簽名等),所以假如供應(yīng)商直接調(diào)用http://116.85.10.181:3000/sendTransaction3?k=food&v=3 雖然通過但是賬本會記錄調(diào)用者不是銀行機(jī)構(gòu),可以認(rèn)定無效。
注意:
1、需要準(zhǔn)備npm環(huán)境,fabric-client 需要在v.1.10 grpc為v.1.10.1
可以使用npm list xxx 來查看版本信息。
2、使用瀏覽器訪問需要開通端口訪問權(quán)限。