很早就听说了区块链,却一直没有深入接触学习,直到动手做区块链相关的项目时才真正对区块链、Hyperledger Fabric等技术有了些许了解。这篇博客主要记录的是基于Fabric开发一个简易的Web项目,如果想要对Fabric有更深入的了解,可以阅读者官方文档或以下文章:
时间仓促,很多东西只能等空下来之后再详细补充。
我选择的fabric版本是Hyperledger Fabric1.4,运行环境为Ubuntu18.04,主要参考以下两个链接进行环境搭建:
还有一些搭建环境可能出现的问题以及解决方法,等以后补充。
后端项目的根目录下共有三个子文件夹,分别为basic-network、chaincode和Scripts。
basic-network文件夹存放网络配置文件。
在首次执行basic-network下的start.sh的过程中,所有网络节点已经被初始化,其中节点的配置基于basic-network下的docker-compose.yml文件。想要了解初始化的详细流程可参看startFabric.sh源码。
对这些配置文件具体作用不需要非常清晰的了解,因为这并不影响我们写项目。
chaincode文件夹存放智能合约代码。
chaincode(智能合约)可以理解为接口,通过接口实现具体的业务操作。
Fabric的chaincode接口并不复杂,只需实现Init和Invoke方法,具体的业务实现则通过在Invoke中对输入方法名做判断分发。
package main
import (
"encoding/json"
"fmt"
//"strconv"
// "strings"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
type bMRPChainCode struct{
}
type Patient struct{
PatientName string `json:PatientName` //患者姓名
PatientGender int `json:PatientGender` //患者性别
PatientAge string `json:PatientAge` //患者年龄
PatientNationality string `json:PatientNationality` //国籍
PatientIDType string `json:PatientIDType` //证件类型
PatientIDNumber string `json:PatientIDNumber` //证件编号
PatientTelephone string `json:PatientTelephone` //电话号码
PatientAddress string `json:PatientAddress` //住址
}
func(a *bMRPChainCode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
func(a *bMRPChainCode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
fn,args := stub.GetFunctionAndParameters()
if fn == "AddNewMR"{
return a.AddNewMR(stub,args)
}else if fn=="GetMRByID"{
return a.GetMRByID(stub,args)
}
return shim.Error("Recevied unkown function invocation : "+fn)
}
func(a *bMRPChainCode) AddNewMR(stub shim.ChaincodeStubInterface, args []string) pb.Response{
var err error
var newRecord MedicalRecord
//检查参数个数是否正确
if len(args)!=7{
return shim.Error("Incorrect number of arguments.")
}
newRecord.MRID=args[0]
newRecord.MRAdmissionDate=args[1]
newRecord.MRDischargeDate=args[2]
newRecord.MRPaymentType=args[3]
newRecord.MRPatientID=args[4]
newRecord.MRDoctors =args[5]
newRecord.MRDiagnosis =args[6]
ProInfosJSONasBytes,err := json.Marshal(newRecord)
if err != nil{
return shim.Error(err.Error())
}
err = stub.PutState(newRecord.MRID,ProInfosJSONasBytes)
if err != nil{
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func(a *bMRPChainCode) GetMRByID(stub shim.ChaincodeStubInterface,args []string) pb.Response{
if len(args) != 1{
return shim.Error("Incorrect number of arguments.")
}
MRID := args[0]
resultsIterator,err := stub.GetHistoryForKey(MRID)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()
//var foodProInfo ProInfo
var medicalRecord MedicalRecord
for resultsIterator.HasNext(){
var _medicalRecord MedicalRecord
//var FoodInfos FoodInfo
response,err :=resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
json.Unmarshal(response.Value,&_medicalRecord)
if _medicalRecord.MRID != ""{
medicalRecord = _medicalRecord
continue
}
}
jsonsAsBytes,err := json.Marshal(medicalRecord)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(jsonsAsBytes)
}
func main(){
err := shim.Start(new(bMRPChainCode))
if err != nil {
fmt.Printf("Error starting bMRP chaincode: %s ",err)
}
}
Scripts文件夹存放nodejs代码。
Hyperledger交易验证流程如下图所示,可以看到,在合约编写完毕后,还差应用层调用。
Fabric主要提供了两种方式实现应用层调用,一种是通过cli容器入口,另一种是sdk。Fabric官网支持多种sdk,官方发布的有node,java,除此之外还有Go,Python等sdk,活跃度都很高。本文使用的是node sdk进行应用程序开发。
'use strict';
/*
* Channel Name : mychannel
* chaincodeId : bMRPCC
*/
var Fabric_Client = require('fabric-client');
var Fabric_CA_Client = require('fabric-ca-client');
var path = require('path');
var util = require('util');
function RegisterNewAdmin() {
var fabric_client = new Fabric_Client();
var fabric_ca_client = null;
var admin_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log(' Store path:' + store_path);
// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
Fabric_Client.newDefaultKeyValueStore({
path: store_path
}).then((state_store) => {
// assign the store to the fabric client
fabric_client.setStateStore(state_store);
var crypto_suite = Fabric_Client.newCryptoSuite();
// use the same location for the state store (where the users' certificate are kept)
// and the crypto store (where the users' keys are kept)
var crypto_store = Fabric_Client.newCryptoKeyStore({
path: store_path
});
crypto_suite.setCryptoKeyStore(crypto_store);
fabric_client.setCryptoSuite(crypto_suite);
var tlsOptions = {
trustedRoots: [],
verify: false
};
// be sure to change the http to https when the CA is running TLS enabled
fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', tlsOptions, 'ca.example.com', crypto_suite);
// first check to see if the admin is already enrolled
return fabric_client.getUserContext('admin', true);
}).then((user_from_store) => {
if (user_from_store && user_from_store.isEnrolled()) {
console.log('Successfully loaded admin from persistence');
admin_user = user_from_store;
return null;
} else {
// need to enroll it with CA server
return fabric_ca_client.enroll({
enrollmentID: 'admin',
enrollmentSecret: 'adminpw'
}).then((enrollment) => {
console.log('Successfully enrolled admin user "admin"');
return fabric_client.createUser({
username: 'admin',
mspid: 'Org1MSP',
cryptoContent: {
privateKeyPEM: enrollment.key.toBytes(),
signedCertPEM: enrollment.certificate
}
});
}).then((user) => {
admin_user = user;
return fabric_client.setUserContext(admin_user);
}).catch((err) => {
console.error('Failed to enroll and persist admin. Error: ' + err.stack ? err.stack : err);
throw new Error('Failed to enroll admin');
});
}
}).then(() => {
console.log('Assigned the admin user to the fabric client ::' + admin_user.toString());
}).catch((err) => {
console.error('Failed to enroll admin: ' + err);
});
}
代码有点长,故此处只展示了注册新管理员的函数功能。完成简单封装之后,其他部分就与大多数nodejs应用开发没什么区别啦。
由于做的是一个前后端分离的小demo,前端项目一时找不到了……以后补
(以后补)
项目源码已经放在Github上,供大家学习和参考。