Hyperledger Fabric實踐:供應(yīng)鏈金融案例

背景描述:

在供應(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)限。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容