作者: AlexTan
CSDN: http://blog.csdn.net/alextan_
Github: https://github.com/AlexTan-b-z
e-mail: alextanbz@gmail.com
操作系统:Ubuntu18.04
接着上一篇:Fabric1.4 Raft单机部署详解(一)
四、编写链码
创建chaincode文件夹:
cd ..
mkdir chaincode
cd chaincode
5.1 声明结构体(即链上存储的内容)
创建文件scoreStruct.go
:
/*
* @Author: AlexTan
* @GIthub: https://github.com/AlexTan-b-z
* @Date: 2020-08-04 17:44:15
* @LastEditors: AlexTan
* @LastEditTime: 2020-08-12 15:10:41
*/
package main
// 根据实际需求,字段自行增删,以下只供参考
type Score struct {
ObjectType string `json:"docType"`
Name string `json:"Name"` // 姓名
Gender string `json:"Gender"` // 性别
StuID string `json:"StuID"` // 学生学号
Grade string `json:"Grade"` // 年级
Result string `json:"Result"` // 成绩
Time string `json:"Time"` // 插入该数据的时间
Historys []HistoryItem // 当前Score的历史记录
}
type HistoryItem struct { // 交易hash,也是标志ID
TxId string
Score Score
}
5.2 编写链码
创建文件main.go
:
/*
* @Author: AlexTan
* @GIthub: https://github.com/AlexTan-b-z
* @Date: 2020-08-04 20:35:47
* @LastEditors: AlexTan
* @LastEditTime: 2020-08-12 22:18:49
*/
package main
import (
"github.com/hyperledger/fabric/core/chaincode/shim"
"fmt"
"github.com/hyperledger/fabric/protos/peer"
)
/*
func stringToByte(args string) []byte{
var as []byte
for _, a := range args {
as = append(as, []byte(a)...)
}
return as
}*/
type ScoreChaincode struct {
}
func (t *ScoreChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{
return shim.Success(nil)
}
func (t *ScoreChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{
// 获取用户意图
fun, args := stub.GetFunctionAndParameters()
if fun == "addScore"{
stub.SetEvent("addScore", []byte{})
return t.addScore(stub, args) // 添加成绩
}else if fun == "queryScoreByNameAndGrade" {
stub.SetEvent("queryScoreByNameAndGrade", []byte{})
return t.queryScoreByNameAndGrade(stub, args) // 根据姓名及年级查询成绩,假设不同年级存在同名
}else if fun == "queryScoreDetailByStuID" {
stub.SetEvent("queryScoreDetailByStuID", []byte{})
return t.queryScoreDetailByStuID(stub, args) // 根据学号查询成绩详细信息(包括历史信息)
}else if fun == "updateScore" {
stub.SetEvent("updateScore", []byte{})
return t.updateScore(stub, args) // 根据证书编号更新信息
}else if fun == "delScore"{
stub.SetEvent("delScore", []byte{})
return t.delScore(stub, args) // 根据证书编号删除信息
}
return shim.Error("指定的函数名称错误")
}
func main(){
err := shim.Start(new(ScoreChaincode))
if err != nil{
fmt.Printf("启动ScoreChaincode时发生错误: %s", err)
}
}
创建文件scoreCC.go
:
package main
import (
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
"encoding/json"
"fmt"
"bytes"
)
const DOC_TYPE = "ScoreObj"
// 保存sco
// args: Score
func PutScore(stub shim.ChaincodeStubInterface, sco Score) ([]byte, bool) {
sco.ObjectType = DOC_TYPE
b, err := json.Marshal(sco)
if err != nil {
return nil, false
}
// 保存sco状态
err = stub.PutState(sco.StuID, b)
if err != nil {
return nil, false
}
return b, true
}
// 根据学号查询信息
// args: stuID
func GetScoreInfo(stub shim.ChaincodeStubInterface, stuID string) (Score, bool) {
var sco Score
// 根据学号查询信息状态
b, err := stub.GetState(stuID)
if err != nil {
return sco, false
}
if b == nil {
return sco, false
}
// 对查询到的状态进行反序列化
err = json.Unmarshal(b, &sco)
if err != nil {
return sco, false
}
// 返回结果
return sco, true
}
// 根据指定的查询字符串实现富查询
func getEduByQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) {
resultsIterator, err := stub.GetQueryResult(queryString)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
// buffer is a JSON array containing QueryRecords
var buffer bytes.Buffer
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
bArrayMemberAlreadyWritten = true
}
fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())
return buffer.Bytes(), nil
}
// 添加信息
// args: scoreObject
// 学生号为 key, Score 为 value
func (t *ScoreChaincode) addScore(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1{
return shim.Error("给定的参数个数不符合要求")
}
var sco Score
err := json.Unmarshal([]byte(args[0]), &sco)
if err != nil {
return shim.Error("反序列化信息时发生错误")
}
// 查重: 身份证号码必须唯一
_, exist := GetScoreInfo(stub, sco.StuID)
if exist {
return shim.Error("要添加的学生号已存在")
}
_, bl := PutScore(stub, sco)
if !bl {
return shim.Error("保存信息时发生错误")
}
return shim.Success([]byte("信息添加成功"))
}
// 根据姓名及年级查询信息
// args: Name, Grade
func (t *ScoreChaincode) queryScoreByNameAndGrade(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("给定的参数个数不符合要求")
}
name := args[0]
grade := args[1]
// 拼装CouchDB所需要的查询字符串(是标准的一个JSON串)
// queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"eduObj\", \"CertNo\":\"%s\"}}", CertNo)
queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"%s\", \"Name\":\"%s\", \"Grade\":\"%s\"}}", DOC_TYPE, name, grade)
// 查询数据
result, err := getEduByQueryString(stub, queryString)
if err != nil {
return shim.Error("根据证书姓名及年级查询信息时发生错误")
}
if result == nil {
return shim.Error("根据指定的姓名及年级没有查询到相关的信息")
}
return shim.Success(result)
}
// 根据学号查询详情(溯源)
// args: StuID
func (t *ScoreChaincode) queryScoreDetailByStuID(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("给定的参数个数不符合要求")
}
// 根据学号查询sco状态
b, err := stub.GetState(args[0])
if err != nil {
return shim.Error("根据学号查询信息失败")
}
if b == nil {
return shim.Error("根据学号没有查询到相关的信息")
}
// 对查询到的状态进行反序列化
var sco Score
err = json.Unmarshal(b, &sco)
if err != nil {
return shim.Error("反序列化sco信息失败")
}
// 获取历史变更数据
iterator, err := stub.GetHistoryForKey(sco.StuID)
if err != nil {
return shim.Error("根据指定的学号查询对应的历史变更数据失败")
}
defer iterator.Close()
// 迭代处理
var historys []HistoryItem
var hisSco Score
for iterator.HasNext() {
hisData, err := iterator.Next()
if err != nil {
return shim.Error("获取sco的历史变更数据失败")
}
var historyItem HistoryItem
historyItem.TxId = hisData.TxId
json.Unmarshal(hisData.Value, &hisSco)
if hisData.Value == nil {
var empty Score
historyItem.Score = empty
}else {
historyItem.Score = hisSco
}
historys = append(historys, historyItem)
}
sco.Historys = historys
// 返回
result, err := json.Marshal(sco)
if err != nil {
return shim.Error("序列化sco信息时发生错误")
}
return shim.Success(result)
}
// 根据学号更新信息
// args: Score Object
func (t *ScoreChaincode) updateScore(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1{
return shim.Error("给定的参数个数不符合要求")
}
var info Score
err := json.Unmarshal([]byte(args[0]), &info)
if err != nil {
return shim.Error("反序列化edu信息失败")
}
// 根据学号查询信息
result, bl := GetScoreInfo(stub, info.StuID)
if !bl{
return shim.Error("根据学号查询信息时发生错误")
}
result.Name = info.Name
result.Gender = info.Gender
result.Grade = info.Grade
result.Result = info.Result
result.Time = info.Time
_, bl = PutScore(stub, result)
if !bl {
return shim.Error("保存信息信息时发生错误")
}
return shim.Success([]byte("信息更新成功"))
}
// 根据学号删除信息(暂不对外提供)
// args: StuID
func (t *ScoreChaincode) delScore(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1{
return shim.Error("给定的参数个数不符合要求")
}
/*var edu Education
result, bl := GetEduInfo(stub, info.EntityID)
err := json.Unmarshal(result, &edu)
if err != nil {
return shim.Error("反序列化信息时发生错误")
}*/
err := stub.DelState(args[0])
if err != nil {
return shim.Error("删除信息时发生错误")
}
return shim.Success([]byte("信息删除成功"))
}
1. 在这里可能大家会疑惑区块链为什么还存在删除操作?
答:Fabric区块中存的是动作:“谁在什么时间对某个数据执行了什么操作”,例如:“张三在2020年8月10日插入了key为xxx,value为xx的数据”。而LevelDB或CouchDB存的是状态数据,及当前时间点某个key的状态(值)是什么。
2. 为什么要用CouchDB?
**答:简单点讲就是只有用CouchDB才支持富查询等操作,LevelDB是不支持的。 **
5.3 创建CouchDB索引
mkdir META-INF/statedb/couchdb/indexes
cd META-INF/statedb/couchdb/indexes
创建indexNameGrade.json
文件:
{"index":{"fields":["Name","Grade"]},"ddoc":"indexNameGrade", "name":"indexNameGrade","type":"json"}
因为链码中有通过Name
和Grade
两个字段来进行查询的函数。
五、SDK配置
创建配置目录:
cd .. # 回到主目录
mkdir sdkConfig
创建org1的配置文件org1_config.yaml
:
name: "org1-config"
#
# Copyright SecureKey Technologies Inc. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
#
# The network connection profile provides client applications the information about the target
# blockchain network that are necessary for the applications to interact with it. These are all
# knowledge that must be acquired from out-of-band sources. This file provides such a source.
#
# copied from fabric-sdk-go/test/fixtures/config/config_e2e_pkcs11.yaml
#
# Schema version of the content. Used by the SDK to apply the corresponding parsing rules.
#
version: 1.0.0
#
# The client section used by GO SDK.
#
client:
# Which organization does this application instance belong to? The value must be the name of an org
# defined under "organizations"
organization: Org1
logging:
# Develope can using debug to get more information
# level: debug
level: info
cryptoconfig:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config
# Some SDKs support pluggable KV stores, the properties under "credentialStore"
# are implementation specific
credentialStore:
# [Optional]. Used by user store. Not needed if all credentials are embedded in configuration
# and enrollments are performed elswhere.
path: "/tmp/examplestore"
# [Optional] BCCSP config for the client. Used by GO SDK.
BCCSP:
security:
enabled: true
default:
provider: "SW"
hashAlgorithm: "SHA2"
softVerify: true
level: 256
tlsCerts:
# [Optional]. Use system certificate pool when connecting to peers, orderers (for negotiating TLS) Default: false
systemCertPool: true
# [Optional]. Client key and cert for TLS handshake with peers and orderers
client:
# 使用User1@org1的证书
keyfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.key
certfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.cert
################################## General part ##################################
#
# [Optional]. But most apps would have this section so that channel objects can be constructed
# based on the content below. If an app is creating channels, then it likely will not need this
# section.
#
channels:
# name of the channel
mychannel:
# Required. list of orderers designated by the application to use for transactions on this
# channel. This list can be a result of access control ("org1" can only access "ordererA"), or
# operational decisions to share loads from applications among the orderers. The values must
# be "names" of orgs defined under "organizations/peers"
# deprecated: not recommended, to override any orderer configuration items, entity matchers should be used.
# orderers:
# - orderer.example.com
# 不要缺少当前channel的orderer节点
orderers:
- orderer0.example.com
- orderer1.example.com
- orderer2.example.com
# Required. list of peers from participating orgs
peers:
peer0.org1.example.com:
# [Optional]. will this peer be sent transaction proposals for endorsement? The peer must
# have the chaincode installed. The app can also use this property to decide which peers
# to send the chaincode install request. Default: true
endorsingPeer: true
# [Optional]. will this peer be sent query proposals? The peer must have the chaincode
# installed. The app can also use this property to decide which peers to send the
# chaincode install request. Default: true
chaincodeQuery: true
# [Optional]. will this peer be sent query proposals that do not require chaincodes, like
# queryBlock(), queryTransaction(), etc. Default: true
ledgerQuery: true
# [Optional]. will this peer be the target of the SDK's listener registration? All peers can
# produce events but the app typically only needs to connect to one to listen to events.
# Default: true
eventSource: true
# Add other peers in mychannel for byfn
peer1.org1.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
peer0.org2.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
peer1.org2.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
# [Optional]. The application can use these options to perform channel operations like retrieving channel
# config etc.
policies:
#[Optional] options for retrieving channel configuration blocks
queryChannelConfig:
#[Optional] min number of success responses (from targets/peers)
minResponses: 1
#[Optional] channel config will be retrieved for these number of random targets
maxTargets: 1
#[Optional] retry options for query config block
retryOpts:
#[Optional] number of retry attempts
attempts: 5
#[Optional] the back off interval for the first retry attempt
initialBackoff: 500ms
#[Optional] the maximum back off interval for any retry attempt
maxBackoff: 5s
#[Optional] he factor by which the initial back off period is exponentially incremented
backoffFactor: 2.0
#
# list of participating organizations in this network
#
organizations:
Org1:
mspid: Org1MSP
# set msp files path
cryptoPath: peerOrganizations/org1.example.com/users/{username}@org1.example.com/msp
# Add peers for org1
peers:
- peer0.org1.example.com
- peer1.org1.example.com
# [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based
# network. Typically certificates provisioning is done in a separate process outside of the
# runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for
# dynamic certificate management (enroll, revoke, re-enroll). The following section is only for
# Fabric-CA servers.
certificateAuthorities:
- ca.org1.example.com
#users:
# Admin:
# cert:
# pem: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/tlsca/tlsca.org1.example.com-cert.pem
# the profile will contain public information about organizations other than the one it belongs to.
# These are necessary information to make transaction lifecycles work, including MSP IDs and
# peers with a public URL to send transaction proposals. The file will not contain private
# information reserved for members of the organization, such as admin key and certificate,
# fabric-ca registrar enroll ID and secret, etc.
Org2:
mspid: Org2MSP
cryptoPath: peerOrganizations/org2.example.com/users/{username}@org2.example.com/msp
# Add peers for org2
peers:
- peer0.org2.example.com
- peer1.org2.example.com
certificateAuthorities:
- ca.org2.example.com
# Orderer Org name
ordererorg:
# Membership Service Provider ID for this organization
mspID: OrdererMSP
cryptoPath: ordererOrganizations/example.com/users/{username}@example.com/msp
orderers:
- orderer0.example.com
- orderer1.example.com
- orderer2.example.com
#
# List of orderers to send transaction and channel create/update requests to. For the time
# being only one orderer is needed. If more than one is defined, which one get used by the
# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers.
#
orderers:
orderer0.example.com:
# [Optional] Default: Infer from hostname
url: grpcs://localhost:7050
# these are standard properties defined by the gRPC library
# they will be passed in as-is to gRPC client constructor
grpcOptions:
ssl-target-name-override: orderer0.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
# Certificate location absolute path
# Replace to orderer cert path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
orderer1.example.com:
url: grpcs://localhost:8050
grpcOptions:
ssl-target-name-override: orderer1.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
orderer2.example.com:
url: grpcs://localhost:9050
grpcOptions:
ssl-target-name-override: orderer2.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
#
# List of peers to send various requests to, including endorsement, query
# and event listener registration.
#
peers:
peer0.org1.example.com:
# this URL is used to send endorsement and query requests
# [Optional] Default: Infer from hostname
# 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接
# url: grpcs://peer0.org1.example.com:7051
url: grpcs://localhost:7051
grpcOptions:
ssl-target-name-override: peer0.org1.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
peer1.org1.example.com:
# this URL is used to send endorsement and query requests
# [Optional] Default: Infer from hostname
# 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接
# url: grpcs://peer0.org1.example.com:7051
url: grpcs://localhost:8051
grpcOptions:
ssl-target-name-override: peer1.org1.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
peer0.org2.example.com:
# Replace the port
url: grpcs://localhost:9051
grpcOptions:
ssl-target-name-override: peer0.org2.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem
peer1.org2.example.com:
# Replace the port
url: grpcs://localhost:10051
grpcOptions:
ssl-target-name-override: peer1.org2.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem
# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows
# certificate management to be done via REST APIs. Application may choose to use a standard
# Certificate Authority instead of Fabric-CA, in which case this section would not be specified.
#
certificateAuthorities:
ca.org1.example.com:
url: http://localhost:7054
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
# Client key and cert for SSL handshake with Fabric CA
#client:
# key:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
# cert:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
# Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
# needed to enroll and invoke new users.
registrar:
enrollId: admin
enrollSecret: adminpw
# [Optional] The optional name of the CA.
caName: ca.org1.example.com
ca.org2.example.com:
url: http://localhost:8054
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem
# Client key and cert for SSL handshake with Fabric CA
#client:
# key:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
# cert:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
# Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
# needed to enroll and invoke new users.
registrar:
enrollId: admin
enrollSecret: adminpw
# [Optional] The optional name of the CA.
caName: ca.org2.example.com
entitymatchers:
peer:
- pattern: (\w*)peer0.org1.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:7051
ssltargetoverrideurlsubstitutionexp: peer0.org1.example.com
mappedhost: peer0.org1.example.com
- pattern: (\w*)peer1.org1.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:8051
ssltargetoverrideurlsubstitutionexp: peer1.org1.example.com
mappedhost: peer1.org1.example.com
- pattern: (\w*)peer0.org2.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:9051
ssltargetoverrideurlsubstitutionexp: peer0.org2.example.com
mappedhost: peer0.org2.example.com
- pattern: (\w*)peer1.org2.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:10051
ssltargetoverrideurlsubstitutionexp: peer1.org2.example.com
mappedhost: peer1.org2.example.com
orderer:
- pattern: (\w*)orderer0.example.com(\w*)
urlsubstitutionexp: localhost:7050
ssltargetoverrideurlsubstitutionexp: orderer0.example.com
mappedhost: orderer0.example.com
- pattern: (\w*)orderer1.example.com(\w*)
urlsubstitutionexp: localhost:8050
ssltargetoverrideurlsubstitutionexp: orderer1.example.com
mappedhost: orderer1.example.com
- pattern: (\w*)orderer2.example.com(\w*)
urlsubstitutionexp: localhost:9050
ssltargetoverrideurlsubstitutionexp: orderer2.example.com
mappedhost: orderer2.example.com
certificateAuthorities:
- pattern: (\w*)ca.org1.example.com(\w*)
urlSubstitutionExp: http://localhost:7054
mappedHost: ca.org1.example.com
- pattern: (\w*)ca.org2.example.com(\w*)
urlSubstitutionExp: http://localhost:8054
mappedHost: ca.org2.example.com
特别注意其中的目录配置,在organizations
下的cryptoPath
里一定要是相对目录,从crypto-config
的下一级目录开始,因为在文件的开始就已经配置了crypto-config
目录的,不然在创建resmgmt Client
的时候会提示user not found
。
创建org2_config.yaml
的文件:
name: "org1-config"
#
# Copyright SecureKey Technologies Inc. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
#
# The network connection profile provides client applications the information about the target
# blockchain network that are necessary for the applications to interact with it. These are all
# knowledge that must be acquired from out-of-band sources. This file provides such a source.
#
# copied from fabric-sdk-go/test/fixtures/config/config_e2e_pkcs11.yaml
#
# Schema version of the content. Used by the SDK to apply the corresponding parsing rules.
#
version: 1.0.0
#
# The client section used by GO SDK.
#
client:
# Which organization does this application instance belong to? The value must be the name of an org
# defined under "organizations"
organization: Org2
logging:
# Develope can using debug to get more information
# level: debug
level: info
cryptoconfig:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config
# Some SDKs support pluggable KV stores, the properties under "credentialStore"
# are implementation specific
credentialStore:
# [Optional]. Used by user store. Not needed if all credentials are embedded in configuration
# and enrollments are performed elswhere.
path: "/tmp/examplestore"
# [Optional] BCCSP config for the client. Used by GO SDK.
BCCSP:
security:
enabled: true
default:
provider: "SW"
hashAlgorithm: "SHA2"
softVerify: true
level: 256
tlsCerts:
# [Optional]. Use system certificate pool when connecting to peers, orderers (for negotiating TLS) Default: false
systemCertPool: true
# [Optional]. Client key and cert for TLS handshake with peers and orderers
client:
# 使用User1@org2的证书
keyfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/tls/client.key
certfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/tls/client.cert
################################## General part ##################################
#
# [Optional]. But most apps would have this section so that channel objects can be constructed
# based on the content below. If an app is creating channels, then it likely will not need this
# section.
#
channels:
# name of the channel
mychannel:
# Required. list of orderers designated by the application to use for transactions on this
# channel. This list can be a result of access control ("org1" can only access "ordererA"), or
# operational decisions to share loads from applications among the orderers. The values must
# be "names" of orgs defined under "organizations/peers"
# deprecated: not recommended, to override any orderer configuration items, entity matchers should be used.
# orderers:
# - orderer.example.com
# 不要缺少当前channel的orderer节点
orderers:
- orderer0.example.com
- orderer1.example.com
- orderer2.example.com
# Required. list of peers from participating orgs
peers:
peer0.org1.example.com:
# [Optional]. will this peer be sent transaction proposals for endorsement? The peer must
# have the chaincode installed. The app can also use this property to decide which peers
# to send the chaincode install request. Default: true
endorsingPeer: true
# [Optional]. will this peer be sent query proposals? The peer must have the chaincode
# installed. The app can also use this property to decide which peers to send the
# chaincode install request. Default: true
chaincodeQuery: true
# [Optional]. will this peer be sent query proposals that do not require chaincodes, like
# queryBlock(), queryTransaction(), etc. Default: true
ledgerQuery: true
# [Optional]. will this peer be the target of the SDK's listener registration? All peers can
# produce events but the app typically only needs to connect to one to listen to events.
# Default: true
eventSource: true
# Add other peers in mychannel for byfn
peer1.org1.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
peer0.org2.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
peer1.org2.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
# [Optional]. The application can use these options to perform channel operations like retrieving channel
# config etc.
policies:
#[Optional] options for retrieving channel configuration blocks
queryChannelConfig:
#[Optional] min number of success responses (from targets/peers)
minResponses: 1
#[Optional] channel config will be retrieved for these number of random targets
maxTargets: 1
#[Optional] retry options for query config block
retryOpts:
#[Optional] number of retry attempts
attempts: 5
#[Optional] the back off interval for the first retry attempt
initialBackoff: 500ms
#[Optional] the maximum back off interval for any retry attempt
maxBackoff: 5s
#[Optional] he factor by which the initial back off period is exponentially incremented
backoffFactor: 2.0
#
# list of participating organizations in this network
#
organizations:
Org1:
mspid: Org1MSP
# set msp files path
cryptoPath: peerOrganizations/org1.example.com/users/{username}@org1.example.com/msp
# Add peers for org1
peers:
- peer0.org1.example.com
- peer1.org1.example.com
# [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based
# network. Typically certificates provisioning is done in a separate process outside of the
# runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for
# dynamic certificate management (enroll, revoke, re-enroll). The following section is only for
# Fabric-CA servers.
certificateAuthorities:
- ca.org1.example.com
#users:
# Admin:
# cert:
# pem: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/tlsca/tlsca.org1.example.com-cert.pem
# the profile will contain public information about organizations other than the one it belongs to.
# These are necessary information to make transaction lifecycles work, including MSP IDs and
# peers with a public URL to send transaction proposals. The file will not contain private
# information reserved for members of the organization, such as admin key and certificate,
# fabric-ca registrar enroll ID and secret, etc.
Org2:
mspid: Org2MSP
cryptoPath: peerOrganizations/org2.example.com/users/{username}@org2.example.com/msp
# Add peers for org2
peers:
- peer0.org2.example.com
- peer1.org2.example.com
certificateAuthorities:
- ca.org2.example.com
# Orderer Org name
ordererorg:
# Membership Service Provider ID for this organization
mspID: OrdererMSP
cryptoPath: ordererOrganizations/example.com/users/{username}@example.com/msp
orderers:
- orderer0.example.com
- orderer1.example.com
- orderer2.example.com
#
# List of orderers to send transaction and channel create/update requests to. For the time
# being only one orderer is needed. If more than one is defined, which one get used by the
# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers.
#
orderers:
orderer0.example.com:
# [Optional] Default: Infer from hostname
url: grpcs://localhost:7050
# these are standard properties defined by the gRPC library
# they will be passed in as-is to gRPC client constructor
grpcOptions:
ssl-target-name-override: orderer0.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
# Certificate location absolute path
# Replace to orderer cert path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
orderer1.example.com:
url: grpcs://localhost:8050
grpcOptions:
ssl-target-name-override: orderer1.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
orderer2.example.com:
url: grpcs://localhost:9050
grpcOptions:
ssl-target-name-override: orderer2.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
#
# List of peers to send various requests to, including endorsement, query
# and event listener registration.
#
peers:
peer0.org1.example.com:
# this URL is used to send endorsement and query requests
# [Optional] Default: Infer from hostname
# 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接
# url: grpcs://peer0.org1.example.com:7051
url: grpcs://localhost:7051
grpcOptions:
ssl-target-name-override: peer0.org1.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
peer1.org1.example.com:
# this URL is used to send endorsement and query requests
# [Optional] Default: Infer from hostname
# 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接
# url: grpcs://peer0.org1.example.com:7051
url: grpcs://localhost:8051
grpcOptions:
ssl-target-name-override: peer1.org1.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
peer0.org2.example.com:
# Replace the port
url: grpcs://localhost:9051
grpcOptions:
ssl-target-name-override: peer0.org2.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem
peer1.org2.example.com:
# Replace the port
url: grpcs://localhost:10051
grpcOptions:
ssl-target-name-override: peer1.org2.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
#will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
allow-insecure: false
tlsCACerts:
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem
# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows
# certificate management to be done via REST APIs. Application may choose to use a standard
# Certificate Authority instead of Fabric-CA, in which case this section would not be specified.
#
certificateAuthorities:
ca.org1.example.com:
url: http://localhost:7054
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
# Client key and cert for SSL handshake with Fabric CA
#client:
# key:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
# cert:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
# Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
# needed to enroll and invoke new users.
registrar:
enrollId: admin
enrollSecret: adminpw
# [Optional] The optional name of the CA.
caName: ca.org1.example.com
ca.org2.example.com:
url: http://localhost:8054
tlsCACerts:
# Certificate location absolute path
path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem
# Client key and cert for SSL handshake with Fabric CA
#client:
# key:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
# cert:
# path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
# Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
# needed to enroll and invoke new users.
registrar:
enrollId: admin
enrollSecret: adminpw
# [Optional] The optional name of the CA.
caName: ca.org2.example.com
entitymatchers:
peer:
- pattern: (\w*)peer0.org1.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:7051
ssltargetoverrideurlsubstitutionexp: peer0.org1.example.com
mappedhost: peer0.org1.example.com
- pattern: (\w*)peer1.org1.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:8051
ssltargetoverrideurlsubstitutionexp: peer1.org1.example.com
mappedhost: peer1.org1.example.com
- pattern: (\w*)peer0.org2.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:9051
ssltargetoverrideurlsubstitutionexp: peer0.org2.example.com
mappedhost: peer0.org2.example.com
- pattern: (\w*)peer1.org2.example.com(\w*)
urlsubstitutionexp: grpcs://localhost:10051
ssltargetoverrideurlsubstitutionexp: peer1.org2.example.com
mappedhost: peer1.org2.example.com
orderer:
- pattern: (\w*)orderer0.example.com(\w*)
urlsubstitutionexp: localhost:7050
ssltargetoverrideurlsubstitutionexp: orderer0.example.com
mappedhost: orderer0.example.com
- pattern: (\w*)orderer1.example.com(\w*)
urlsubstitutionexp: localhost:8050
ssltargetoverrideurlsubstitutionexp: orderer1.example.com
mappedhost: orderer1.example.com
- pattern: (\w*)orderer2.example.com(\w*)
urlsubstitutionexp: localhost:9050
ssltargetoverrideurlsubstitutionexp: orderer2.example.com
mappedhost: orderer2.example.com
certificateAuthorities:
- pattern: (\w*)ca.org1.example.com(\w*)
urlSubstitutionExp: http://localhost:7054
mappedHost: ca.org1.example.com
- pattern: (\w*)ca.org2.example.com(\w*)
urlSubstitutionExp: http://localhost:8054
mappedHost: ca.org2.example.com
六、SDK实现
链码写好后,我们使用Fabric-SDK-Go提供的API来实现对链码的安装及实例化操作。
6.1 创建SDK
创建目录cli
:
mkdir cli
cd cli
创建init.go
文件,用于创建channel,加入channel:
/*
* @Author: AlexTan
* @GIthub: https://github.com/AlexTan-b-z
* @Date: 2020-08-12 21:13:19
* @LastEditors: AlexTan
* @LastEditTime: 2020-08-12 22:07:26
*/
package cli
import (
"log"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
)
const (
org1CfgPath = "../../sdkConfig/org1_config.yaml"
org2CfgPath = "../../sdkConfig/org2_config.yaml"
channelConfig = "../../fixtures/channel-artifacts/channel.tx"
ordererID = "orderer0.example.com"
ordererOrgName = "ordererorg"
org1Name = "Org1"
org2Name = "Org2"
orgAdmin = "Admin"
channelID = "mychannel"
)
func CreateChannel(){
sdk1, err := fabsdk.New(config.FromFile(org1CfgPath))
if err != nil {
log.Panicf("failed to create fabric sdk1: %s", err)
}
sdk2, err := fabsdk.New(config.FromFile(org2CfgPath))
if err != nil {
log.Panicf("failed to create fabric sdk2: %s", err)
}
clientContext := sdk1.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(ordererOrgName))
resMgmtClient, err := resmgmt.New(clientContext)
if err != nil {
log.Panicf("failed to create resMgmtClient in createChannel: %s", err)
}
mspClient, err := mspclient.New(sdk1.Context(), mspclient.WithOrg(org1Name))
if err != nil {
log.Panicf("failed to create msp client: %s", err)
}
adminIdentity, err := mspClient.GetSigningIdentity(orgAdmin)
if err != nil {
log.Panicf("failed to GetSigningIdentity: %s", err)
}
req := resmgmt.SaveChannelRequest{ChannelID: channelID,
ChannelConfigPath: channelConfig,
SigningIdentities: []msp.SigningIdentity{adminIdentity}}
_, err = resMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(ordererID))
if err != nil {
log.Panicf("failed to GetSigningIdentity: %s", err)
}
log.Println("created fabric channel")
// join Channel
org1Context := sdk1.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(org1Name))
org2Context := sdk2.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(org2Name))
org1ResMgmt, err := resmgmt.New(org1Context)
if err != nil {
log.Panicf("failed to create org1ResMgmt: %s", err)
}
org2ResMgmt, err := resmgmt.New(org2Context)
if err != nil {
log.Panicf("failed to create org2ResMgmt: %s", err)
}
if err = org1ResMgmt.JoinChannel(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(ordererID)); err != nil {
log.Panicf("Org1 peers failed to JoinChannel: %s", err)
}
log.Println("org1 joined channel")
if err = org2ResMgmt.JoinChannel(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(ordererID)); err != nil {
log.Panicf("Org2 peers failed to JoinChannel: %s", err)
}
log.Println("org2 joined channel")
}
创建client.go
,用于创建客户端:
/*
* @Author: AlexTan
* @GIthub: https://github.com/AlexTan-b-z
* @Date: 2020-08-08 17:00:23
* @LastEditors: AlexTan
* @LastEditTime: 2020-08-12 21:57:18
*/
package cli
import (
"log"
"os"
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
)
type Client struct {
// Fabric network information
ConfigPath string
OrgName string
OrgAdmin string
OrgUser string
// sdk clients
SDK *fabsdk.FabricSDK
rc *resmgmt.Client
cc *channel.Client
// for create channel
// ChannelConfig string
// OrdererID string
// Same for each peer
ChannelID string
CCID string // chaincode ID, eq name
CCPath string // chaincode source path, 是GOPATH下的某个目录
CCGoPath string // GOPATH used for chaincode
}
func New(cfg, org, admin, user string) *Client {
c := &Client{
ConfigPath: cfg,
OrgName: org,
OrgAdmin: admin,
OrgUser: user,
// ChannelConfig: "../fixtures/channel-artifacts/channel.tx",
// OrdererID: "orderer0.example.com",
CCID: "mycc",
CCPath: "chaincode/", // 相对路径是从GOPAHT/src开始的
CCGoPath: os.Getenv("GOPATH"),
ChannelID: "mychannel",
}
// create sdk
sdk, err := fabsdk.New(config.FromFile(c.ConfigPath))
if err != nil {
log.Panicf("failed to create fabric sdk: %s", err)
}
c.SDK = sdk
log.Println("Initialized fabric sdk")
c.rc, c.cc = NewSdkClient(sdk, c.ChannelID, c.OrgName, c.OrgAdmin, c.OrgUser)
return c
}
// create channel
// func CreateChannel(sdk *fabsdk.FabricSDK, orgName, orgAdmin, channelID, ChannelConfig)
// NewSdkClient create resource client and channel client
func NewSdkClient(sdk *fabsdk.FabricSDK, channelID, orgName, orgAdmin, OrgUser string) (rc *resmgmt.Client, cc *channel.Client) {
var err error
// create rc
rcp := sdk.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(orgName))
rc, err = resmgmt.New(rcp)
if err != nil {
log.Panicf("failed to create resource client: %s", err)
}
log.Println("Initialized resource client")
// create cc
ccp := sdk.ChannelContext(channelID, fabsdk.WithUser(OrgUser))
cc, err = channel.New(ccp)
if err != nil {
log.Panicf("failed to create channel client: %s", err)
}
log.Println("Initialized channel client")
return rc, cc
}
// RegisterChaincodeEvent more easy than event client to registering chaincode event.
func (c *Client) RegisterChaincodeEvent(ccid, eventName string) (fab.Registration, <-chan *fab.CCEvent, error) {
return c.cc.RegisterChaincodeEvent(ccid, eventName)
}
创建chaincode.go
,用于链码安装,实例化链码操作:
package cli
import (
"log"
"net/http"
"strings"
"encoding/json"
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/gopackager"
"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric-protos-go/common"
"github.com/pkg/errors"
"scoreDapp/model"
)
// InstallCC install chaincode for target peer
func (c *Client) InstallCC(v string, peer string) error {
targetPeer := resmgmt.WithTargetEndpoints(peer)
// pack the chaincode
ccPkg, err := gopackager.NewCCPackage(c.CCPath, c.CCGoPath)
if err != nil {
return errors.WithMessage(err, "pack chaincode error")
}
// new request of installing chaincode
req := resmgmt.InstallCCRequest{
Name: c.CCID,
Path: c.CCPath,
Version: v,
Package: ccPkg,
}
resps, err := c.rc.InstallCC(req, targetPeer)
if err != nil {
return errors.WithMessage(err, "installCC error")
}
// check other errors
var errs []error
for _, resp := range resps {
log.Printf("Install response status: %v", resp.Status)
if resp.Status != http.StatusOK {
errs = append(errs, errors.New(resp.Info))
}
if resp.Info == "already installed" {
log.Printf("Chaincode %s already installed on peer: %s.\n",
c.CCID+"-"+v, resp.Target)
return nil
}
}
if len(errs) > 0 {
log.Printf("InstallCC errors: %v", errs)
return errors.WithMessage(errs[0], "installCC first error")
}
return nil
}
func (c *Client) InstantiateCC(v string, peer string) (fab.TransactionID,
error) {
// endorser policy
org1OrOrg2 := "OR('Org1MSP.member','Org2MSP.member')"
ccPolicy, err := c.genPolicy(org1OrOrg2)
if err != nil {
return "", errors.WithMessage(err, "gen policy from string error")
}
// new request
// Attention: args should include `init` for Request not
// have a method term to call init
args := packArgs([]string{"init"})
req := resmgmt.InstantiateCCRequest{
Name: c.CCID,
Path: c.CCPath,
Version: v,
Args: args,
Policy: ccPolicy,
}
// send request and handle response
reqPeers := resmgmt.WithTargetEndpoints(peer)
resp, err := c.rc.InstantiateCC(c.ChannelID, req, reqPeers)
if err != nil {
if strings.Contains(err.Error(), "already exists") {
return "", nil
}
return "", errors.WithMessage(err, "instantiate chaincode error")
}
log.Printf("Instantitate chaincode tx: %s", resp.TransactionID)
return resp.TransactionID, nil
}
func (c *Client) genPolicy(p string) (*common.SignaturePolicyEnvelope, error) {
// TODO bug, this any leads to endorser invalid
if p == "ANY" {
return cauthdsl.SignedByAnyMember([]string{c.OrgName}), nil
}
return cauthdsl.FromString(p)
}
func (c *Client) InvokeCCadd(peers []string, sco model.Score) (fab.TransactionID, error) {
//将sco序列化成字节数组
sco_b, err := json.Marshal(sco)
if err != nil {
log.Printf("指定的Score对象序列化时发生错误: %v", err)
return "", err
}
// new channel request for invoke
req := channel.Request{
ChaincodeID: "mycc",
Fcn: "addScore",
Args: [][]byte{sco_b},
}
// send request and handle response
// peers is needed
reqPeers := channel.WithTargetEndpoints(peers...)
resp, err := c.cc.Execute(req, reqPeers)
log.Printf("Invoke chaincode response:\n"+
"id: %v\nvalidate: %v\nchaincode status: %v\n\n",
resp.TransactionID,
resp.TxValidationCode,
resp.ChaincodeStatus)
if err != nil {
return "", errors.WithMessage(err, "invoke chaincode error")
}
return resp.TransactionID, nil
}
func (c *Client) QueryCCByNameAndGrade(peer, name string, grade string) error {
// new channel request for query
req := channel.Request{
ChaincodeID: "mycc",
Fcn: "queryScoreByNameAndGrade",
Args: packArgs([]string{name, grade}),
}
// send request and handle response
reqPeers := channel.WithTargetEndpoints(peer)
resp, err := c.cc.Query(req, reqPeers)
if err != nil {
return errors.WithMessage(err, "query chaincode error")
}
log.Printf("Query chaincode tx response:\ntx: %s\nresult: %v\n\n",
resp.TransactionID,
string(resp.Payload))
return nil
}
func (c *Client) QueryCCByID(peer, StuID string) error {
// new channel request for query
req := channel.Request{
ChaincodeID: "mycc",
Fcn: "queryScoreDetailByStuID",
Args: packArgs([]string{StuID}),
}
// send request and handle response
reqPeers := channel.WithTargetEndpoints(peer)
resp, err := c.cc.Query(req, reqPeers)
if err != nil {
return errors.WithMessage(err, "query chaincode error")
}
log.Printf("Query chaincode tx response:\ntx: %s\nresult: %v\n\n",
resp.TransactionID,
string(resp.Payload))
return nil
}
func (c *Client) UpdateCCScore(peers []string, sco model.Score) (fab.TransactionID, error) {
//将sco序列化成字节数组
sco_b, err := json.Marshal(sco)
if err != nil {
log.Printf("指定的Score对象序列化时发生错误: %v", err)
return "", err
}
// new channel request for invoke
req := channel.Request{
ChaincodeID: "mycc",
Fcn: "updateScore",
Args: [][]byte{sco_b},
}
// send request and handle response
// peers is needed
reqPeers := channel.WithTargetEndpoints(peers...)
resp, err := c.cc.Execute(req, reqPeers)
log.Printf("Invoke chaincode response:\n"+
"id: %v\nvalidate: %v\nchaincode status: %v\n\n",
resp.TransactionID,
resp.TxValidationCode,
resp.ChaincodeStatus)
if err != nil {
return "", errors.WithMessage(err, "invoke chaincode error")
}
return resp.TransactionID, nil
}
func (c *Client) InvokeCCDelete(peers []string, stuID string) (fab.TransactionID, error) {
log.Println("Invoke delete")
// new channel request for invoke
req := channel.Request{
ChaincodeID: c.CCID,
Fcn: "delScore",
Args: packArgs([]string{stuID}),
}
// send request and handle response
// peers is needed
reqPeers := channel.WithTargetEndpoints(peers...)
resp, err := c.cc.Execute(req, reqPeers)
log.Printf("Invoke chaincode delete response:\n"+
"id: %v\nvalidate: %v\nchaincode status: %v\n\n",
resp.TransactionID,
resp.TxValidationCode,
resp.ChaincodeStatus)
if err != nil {
return "", errors.WithMessage(err, "invoke chaincode error")
}
return resp.TransactionID, nil
}
func (c *Client) UpgradeCC(v string, peer string) error {
// endorser policy
org1AndOrg2 := "AND('Org1MSP.member','Org2MSP.member')"
ccPolicy, err := c.genPolicy(org1AndOrg2)
if err != nil {
return errors.WithMessage(err, "gen policy from string error")
}
// new request
// Attention: args should include `init` for Request not
// have a method term to call init
// Reset a b's value to test the upgrade
args := packArgs([]string{"init"})
req := resmgmt.UpgradeCCRequest{
Name: c.CCID,
Path: c.CCPath,
Version: v,
Args: args,
Policy: ccPolicy,
}
// send request and handle response
reqPeers := resmgmt.WithTargetEndpoints(peer)
resp, err := c.rc.UpgradeCC(c.ChannelID, req, reqPeers)
if err != nil {
return errors.WithMessage(err, "instantiate chaincode error")
}
log.Printf("Instantitate chaincode tx: %s", resp.TransactionID)
return nil
}
func (c *Client) QueryCCInfo(v string, peer string) {
}
func (c *Client) Close() {
c.SDK.Close()
}
func packArgs(paras []string) [][]byte {
var args [][]byte
for _, k := range paras {
args = append(args, []byte(k))
}
return args
}
6.2 创建执行链码相关操作的文件
回到项目根目录
cd ..
mkdir samples
cd samples
mkdir chaincode
在samples/chaincode
目录下创建文件main.go
,用于运行链码相关操作:
/*
* @Author: test1Tan
* @GIthub: https://github.com/test1Tan-b-z
* @Date: 2020-08-11 21:03:54
* @LastEditors: AlexTan
* @LastEditTime: 2020-08-19 20:20:44
*/
package main
import (
"log"
"time"
"github.com/AlexTan-b-z/fabric-sdk-go-demo/cli"
"github.com/AlexTan-b-z/fabric-sdk-go-demo/model"
)
const (
org1CfgPath = "../../sdkConfig/org1_config.yaml"
org2CfgPath = "../../sdkConfig/org2_config.yaml"
)
var (
peer0Org1 = "peer0.org1.example.com"
peer0Org2 = "peer0.org2.example.com"
)
func main() {
// init
// 第一次运行后记得注释掉
cli.CreateChannel()
org1Client := cli.New(org1CfgPath, "Org1", "Admin", "User1")
org2Client := cli.New(org2CfgPath, "Org2", "Admin", "User1")
defer org1Client.Close()
defer org2Client.Close()
// Install, instantiate, invoke, query
Phase1(org1Client, org2Client)
// Install, upgrade, invoke, query
Phase2(org1Client, org2Client)
}
func Phase1(cli1, cli2 *cli.Client) {
log.Println("=================== Phase 1 begin ===================")
defer log.Println("=================== Phase 1 end ===================")
if err := cli1.InstallCC("v1", peer0Org1); err != nil {
log.Panicf("Intall chaincode error: %v", err)
}
log.Println("Chaincode has been installed on org1's peers")
if err := cli2.InstallCC("v1", peer0Org2); err != nil {
log.Panicf("Intall chaincode error: %v", err)
}
log.Println("Chaincode has been installed on org2's peers")
// InstantiateCC chaincode only need once for each channel
if _, err := cli1.InstantiateCC("v1", peer0Org1); err != nil {
log.Panicf("Instantiated chaincode error: %v", err)
}
log.Println("Chaincode has been instantiated")
sco1 := model.Score{
Name: "test1",
Gender: "男",
StuID: "123",
Grade: "2015",
Result: "100",
Time: time.Now().Format("2006-01-02 15:04:05"),
}
sco2 := model.Score{
Name: "test2",
Gender: "女",
StuID: "1234",
Grade: "2017",
Result: "100",
Time: time.Now().Format("2006-01-02 15:04:05"),
}
if _, err := cli1.InvokeCCadd([]string{peer0Org1}, sco1); err != nil {
log.Panicf("InvokeCCadd test1 chaincode error: %v", err)
}
log.Println("InvokeCCadd test1 chaincode success 1")
if _, err := cli1.InvokeCCadd([]string{peer0Org1}, sco2); err != nil {
log.Panicf("InvokeCCadd test2 chaincode error: %v", err)
}
log.Println("InvokeCCadd test2 chaincode success 2")
if err := cli1.QueryCCByNameAndGrade("peer0.org1.example.com", "test1", "2015"); err != nil {
log.Panicf("QueryCCByNameAndGrade chaincode error: %v", err)
}
log.Println("QueryCCByNameAndGrade chaincode success on peer0.org1")
if err := cli1.QueryCCByID("peer0.org1.example.com", "1234"); err != nil {
log.Panicf("QueryCCByID chaincode error: %v", err)
}
log.Println("QueryCCByID chaincode success on peer0.org1")
new_sco := model.Score{
Name: "test1",
Gender: "男",
StuID: "123",
Grade: "2015",
Result: "99",
Time: time.Now().Format("2006-01-02 15:04:05"),
}
if _, err := cli1.UpdateCCScore([]string{peer0Org1}, new_sco); err != nil {
log.Panicf("updateCCScore chaincode error: %v", err)
}
log.Println("updateCCScore chaincode success 1")
if err := cli1.QueryCCByID("peer0.org1.example.com", "123"); err != nil {
log.Panicf("QueryCCByID chaincode error: %v", err)
}
log.Println("QueryCCByID chaincode success on peer0.org1")
if _, err := cli1.InvokeCCDelete([]string{"peer0.org1.example.com"}, "123"); err != nil {
log.Panicf("InvokeCCDelete chaincode error: %v", err)
}
log.Println("InvokeCCDelete chaincode success on peer0.org1")
}
func Phase2(cli1, cli2 *cli.Client) {
log.Println("=================== Phase 2 begin ===================")
defer log.Println("=================== Phase 2 end ===================")
v := "v2"
// Install new version chaincode
if err := cli1.InstallCC(v, peer0Org1); err != nil {
log.Panicf("Intall chaincode error: %v", err)
}
log.Println("Chaincode has been installed on org1's peers")
if err := cli2.InstallCC(v, peer0Org2); err != nil {
log.Panicf("Intall chaincode error: %v", err)
}
log.Println("Chaincode has been installed on org2's peers")
// Upgrade chaincode only need once for each channel
if err := cli1.UpgradeCC(v, peer0Org1); err != nil {
log.Panicf("Upgrade chaincode error: %v", err)
}
log.Println("Upgrade chaincode success for channel")
sco1 := model.Score{
Name: "test3",
Gender: "男",
StuID: "12345",
Grade: "2015",
Result: "100",
Time: time.Now().Format("2006-01-02 15:04:05"),
}
sco2 := model.Score{
Name: "test4",
Gender: "女",
StuID: "123456",
Grade: "2017",
Result: "100",
Time: time.Now().Format("2006-01-02 15:04:05"),
}
if _, err := cli1.InvokeCCadd([]string{"peer0.org1.example.com", "peer0.org2.example.com"}, sco1); err != nil {
log.Panicf("InvokeCCadd test3 chaincode error: %v", err)
}
log.Println("InvokeCCadd test3 chaincode success 1")
if _, err := cli1.InvokeCCadd([]string{peer0Org1, "peer0.org2.example.com"}, sco2); err != nil {
log.Panicf("InvokeCCadd test4 chaincode error: %v", err)
}
log.Println("InvokeCCadd test4 chaincode success 2")
if err := cli1.QueryCCByNameAndGrade("peer0.org2.example.com", "test3", "2015"); err != nil {
log.Panicf("QueryCCByNameAndGrade chaincode error: %v", err)
}
log.Println("QueryCCByNameAndGrade chaincode success on peer0.org2")
if err := cli1.QueryCCByID("peer0.org2.example.com", "12345"); err != nil {
log.Panicf("QueryCCByID chaincode error: %v", err)
}
log.Println("QueryCCByID chaincode success on peer0.org2")
new_sco := model.Score{
Name: "test3",
Gender: "男",
StuID: "12345",
Grade: "2015",
Result: "99",
Time: time.Now().Format("2006-01-02 15:04:05"),
}
if _, err := cli1.UpdateCCScore([]string{peer0Org1, "peer0.org2.example.com"}, new_sco); err != nil {
log.Panicf("updateCCScore chaincode error: %v", err)
}
log.Println("updateCCScore chaincode success 1")
if err := cli1.QueryCCByID("peer0.org2.example.com", "12345"); err != nil {
log.Panicf("QueryCCByID chaincode error: %v", err)
}
log.Println("QueryCCByID chaincode success on peer0.org2")
if _, err := cli1.InvokeCCDelete([]string{"peer0.org2.example.com", peer0Org1}, "12345"); err != nil {
log.Panicf("InvokeCCDelete chaincode error: %v", err)
}
log.Println("InvokeCCDelete chaincode success on peer0.org2")
}
6.3 创建事件监听程序
回到前一个目录,创建event
文件夹
cd ..
mkdir event
cd event
创建main.go
,用于测试监听链码事件:
package main
import (
"encoding/hex"
"log"
"time"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/events/deliverclient/seek"
"github.com/hyperledger/fabric-sdk-go/pkg/client/event"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
"github.com/AlexTan-b-z/fabric-sdk-go-demo/cli"
"github.com/AlexTan-b-z/fabric-sdk-go-demo/model"
)
const (
org1CfgPath = "../../sdkConfig/org1_config.yaml"
org2CfgPath = "../../sdkConfig/org2_config.yaml"
)
var (
peer0Org1 = "peer0.org1.example.com"
peer0Org2 = "peer0.org2.example.com"
)
func main() {
org1Client := cli.New(org1CfgPath, "Org1", "Admin", "User1")
org2Client := cli.New(org2CfgPath, "Org2", "Admin", "User1")
defer org1Client.Close()
defer org2Client.Close()
// New event client
cp := org1Client.SDK.ChannelContext(org1Client.ChannelID, fabsdk.WithUser(org1Client.OrgUser))
ec, err := event.New(
cp,
event.WithBlockEvents(), // 如果没有,会是filtered
// event.WithBlockNum(1), // 从指定区块获取,需要此参数
event.WithSeekType(seek.Newest))
if err != nil {
log.Printf("Create event client error: %v", err)
}
// block event listen
defer ec.Unregister(blockListener(ec))
defer ec.Unregister(filteredBlockListener(ec))
// tx listen
txIDCh := make(chan string, 100)
go txListener(ec, txIDCh)
// chaincode event listen
defer ec.Unregister(chainCodeEventListener(nil, ec))
DoChainCode(org1Client, txIDCh)
close(txIDCh)
time.Sleep(time.Second * 10)
}
func blockListener(ec *event.Client) fab.Registration {
// Register monitor block event
beReg, beCh, err := ec.RegisterBlockEvent()
if err != nil {
log.Printf("Register block event error: %v", err)
}
log.Println("Registered block event")
// Receive block event
go func() {
for e := range beCh {
log.Printf("Receive block event:\nSourceURL: %v\nNumber: %v\nHash"+
": %v\nPreviousHash: %v\n\n",
e.SourceURL,
e.Block.Header.Number,
hex.EncodeToString(e.Block.Header.DataHash),
hex.EncodeToString(e.Block.Header.PreviousHash))
}
}()
return beReg
}
func filteredBlockListener(ec *event.Client) fab.Registration {
// Register monitor filtered block event
fbeReg, fbeCh, err := ec.RegisterFilteredBlockEvent()
if err != nil {
log.Printf("Register filtered block event error: %v", err)
}
log.Println("Registered filtered block event")
// Receive filtered block event
go func() {
for e := range fbeCh {
log.Printf("Receive filterd block event:\nNumber: %v\nlen("+
"transactions): %v\nSourceURL: %v",
e.FilteredBlock.Number, len(e.FilteredBlock.
FilteredTransactions), e.SourceURL)
for i, tx := range e.FilteredBlock.FilteredTransactions {
log.Printf("tx index %d: type: %v, txid: %v, "+
"validation code: %v", i,
tx.Type, tx.Txid,
tx.TxValidationCode)
}
log.Println() // Just go print empty log for easy to read
}
}()
return fbeReg
}
func txListener(ec *event.Client, txIDCh chan string) {
log.Println("Transaction listener start")
defer log.Println("Transaction listener exit")
for id := range txIDCh {
// Register monitor transaction event
log.Printf("Register transaction event for: %v", id)
txReg, txCh, err := ec.RegisterTxStatusEvent(id)
if err != nil {
log.Printf("Register transaction event error: %v", err)
continue
}
defer ec.Unregister(txReg)
// Receive transaction event
go func() {
for e := range txCh {
log.Printf("Receive transaction event: txid: %v, "+
"validation code: %v, block number: %v",
e.TxID,
e.TxValidationCode,
e.BlockNumber)
}
}()
}
}
func chainCodeEventListener(c *cli.Client, ec *event.Client) fab.Registration {
eventName := ".*"
log.Printf("Listen chaincode event: %v", eventName)
var (
ccReg fab.Registration
eventCh <-chan *fab.CCEvent
err error
)
if c != nil {
log.Println("Using client to register chaincode event")
ccReg, eventCh, err = c.RegisterChaincodeEvent("mycc", eventName)
} else {
log.Println("Using event client to register chaincode event")
ccReg, eventCh, err = ec.RegisterChaincodeEvent("mycc", eventName)
}
if err != nil {
log.Printf("Register chaincode event error: %v", err.Error())
return nil
}
// consume event
go func() {
for e := range eventCh {
log.Printf("Receive cc event, ccid: %v \neventName: %v\n"+
"payload: %v \ntxid: %v \nblock: %v \nsourceURL: %v\n",
e.ChaincodeID, e.EventName, string(e.Payload), e.TxID, e.BlockNumber, e.SourceURL)
}
}()
return ccReg
}
// Install、Deploy、Invoke、Query、Upgrade
func DoChainCode(cli1 *cli.Client, txCh chan<- string) {
var (
txid fab.TransactionID
err error
)
// ccVersion := "v1"
// if err := cli1.InstallCC(ccVersion, peer0Org1); err != nil {
// log.Panicf("Intall chaincode error: %v", err)
// }
// log.Println("Chaincode has been installed on org1's peers")
//
// // InstantiateCC chaincode only need once for each channel
// if txid, err = cli1.InstantiateCC(ccVersion, peer0Org1); err != nil {
// log.Panicf("Instantiated chaincode error: %v", err)
// }
// if txid != "" {
// txCh <- string(txid)
// }
// log.Println("Chaincode has been instantiated")
sco3 := model.Score{
Name: "eventTest",
Gender: "男",
StuID: "888888",
Grade: "2018",
Result: "100",
Time: time.Now().Format("2006-01-02 15:04:05"),
}
if txid, err = cli1.InvokeCCadd([]string{peer0Org1, "peer0.org2.example.com"}, sco3); err != nil {
log.Panicf("InvokeCCadd chaincode error: %v", err)
}
log.Println("InvokeCCadd chaincode success 1")
if txid != "" {
txCh <- string(txid)
}
log.Println("Invoke chaincode success")
if err = cli1.QueryCCByID("peer0.org1.example.com", "888888"); err != nil {
log.Panicf("Query chaincode error: %v", err)
}
log.Println("Query chaincode success on peer0.org1")
if txid, err = cli1.InvokeCCDelete([]string{"peer0.org1.example.com", "peer0.org2.example.com"}, "888888"); err != nil {
log.Panicf("InvokeCCDelete chaincode error: %v", err)
}
}
七、测试运行
确保已经启动好容器环境后,分别运行samples/chaincode/main.go
和samples/event/main.go
即可完成测试。
其中包括以下操作:
- 创建通道
- 把
org1
、org2
(所有节点)加入通道 - 在指定节点安装链码
- 实例化链码(配置背书策略)
- 调用链码
- 更新链码背书策略
- 调用新的链码(新的背书策略)
清理环境
要清理运行环境,使用docker-compose将容器停掉并移除即可:
docker-compose -f docker-compose-local.yaml down -v
完整文件已发github: fabric-sdk-go-demo, 欢迎Star