fabric后端接口框架

搭建区块链网络

1、下载 fabric 二进制工具

v1.4.12 版本为例, fabric 二进制工具的下载地址在:https://github.com/hyperledger/fabric/releases/tag/v1.4.12[3]

自行根据你的系统环境下载对应的包。

其中几个主要的工具说明:

  • cryptogen :用来生成 Hyperledger Fabric 密钥材料的工具,这个过程是静态的。cryptogen 工具通过一个包含网络拓扑的 crypto-config.yaml 文件,为所有组织和属于这些组织的组件生成一组证书和秘钥。cryptogen 适合用于测试开发环境,在生产环境建议使用动态的 CA 服务。
  • configtxgen :用于创建和查看排序节点的创世区块、通道配置交易等相关的工具。configtxgen 使用 configtx.yaml 文件来定义网络配置。
  • configtxlator:fabric 中 ProtobufJSON 格式转换的工具,fabric 中任何的使用 Protobuf 定义的类型,都可使用该工具进行转换。
  • peer:peer 命令有 5 个不同的子命令,每个命令都可以让指定的 peer 节点执行特定的一组任务。比如,可以使用子命令 peer channel 让一个 peer 节点加入通道,或者使用 peer chaincode 命令把智能合约链码部署到 peer 节点上。

2、将 fabric 二进制工具添加到环境变量

为了后续方便使用命令,可以将第 1 步下载的工具添加到系统环境变量中

1
export PATH=${PWD}/hyperledger-fabric-linux-amd64-1.4.12/bin:$PATH

3、生成证书和秘钥所需文件

我们将使用 cryptogen 工具生成各种加密材料( x509 证书和签名秘钥)。这些证书是身份的代表,在实体相互通信和交易的时候,可以对其身份进行签名和验证。

首先创建 crypto-config.yaml 文件,定义网络拓扑,为所有组织和属于这些组织的组件(也就是节点)生成一组证书和秘钥,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 排序节点的组织定义
OrdererOrgs:
- Name: ZXY # 名称
Domain: zxy.com # 域名
Specs: # 节点域名:orderer.carunion.com
- Hostname: orderer # 主机名

# peer节点的组织定义
PeerOrgs:
# CHEN-组织
- Name: CHEN # 名称
Domain: chen.com # 域名
Template: # 使用模板定义。Count 指的是该组织下组织节点的个数
Count: 2 # 节点域名:peer0.chen.com 和 peer1.chen.com
Users: # 组织的用户信息。Count 指该组织中除了 Admin 之外的用户的个数
Count: 1 # 用户:Admin 和 User1

# ZHOU-组织
- Name: ZHOU
Domain: zhou.com
Template:
Count: 2 # 节点域名:peer0.zhou.com 和 peer1.zhou.com
Users:
Count: 1 # 用户:Admin 和 User1

4、创建排序通道创世区块所需文件

我们可以使用 configtx.yaml 文件和 configtxgen 工具轻松地创建通道的配置。configtx.yaml 文件可以以易于理解和编辑的 yaml 格式来构建通道配置所需的信息。configtxgen 工具通过读取 configtx.yaml 文件中的信息,将其转成 Fabric 可以读取的 protobuf 格式。

先来创建 configtx.yaml 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 定义组织机构实体
Organizations:
- &ZXY
Name: ZXY # 组织的名称
ID: ZXYMSP # 组织的 MSPID
MSPDir: crypto-config/ordererOrganizations/zxy.com/msp #组织的证书相对位置(生成的crypto-config目录)

- &CHEN
Name: CHEN
ID: CHENMSP
MSPDir: crypto-config/peerOrganizations/chen.com/msp
AnchorPeers: # 组织锚节点的配置
- Host: peer0.chen.com
Port: 7051

- &ZHOU
Name: ZHOU
ID: ZHOUMSP
MSPDir: crypto-config/peerOrganizations/zhou.com/msp
AnchorPeers: # 组织锚节点的配置
- Host: peer0.zhou.com
Port: 7051

# 定义了排序服务的相关参数,这些参数将用于创建创世区块
Orderer: &OrdererDefaults
# 排序节点类型用来指定要启用的排序节点实现,不同的实现对应不同的共识算法
OrdererType: solo # 共识机制
Addresses: # Orderer 的域名(用于连接)
- orderer.zxy.com:7050
BatchTimeout: 2s # 出块时间间隔
BatchSize: # 用于控制每个block的信息量
MaxMessageCount: 10 #每个区块的消息个数
AbsoluteMaxBytes: 99 MB #每个区块最大的信息大小
PreferredMaxBytes: 512 KB #每个区块包含的一条信息最大长度
Organizations:

# 定义Peer组织如何与应用程序通道交互的策略
# 默认策略:所有Peer组织都将能够读取数据并将数据写入账本
Application: &ApplicationDefaults
Organizations:

# 用来定义用于 configtxgen 工具的配置入口
# 将 Profile 参数( TwoOrgsOrdererGenesis 或 TwoOrgsChannel )指定为 configtxgen 工具的参数
Profiles:
# TwoOrgsOrdererGenesis配置文件用于创建系统通道创世块
# 该配置文件创建一个名为SampleConsortium的联盟
# 该联盟在configtx.yaml文件中包含两个Peer组织CHEN和ZHOU
TwoOrgsOrdererGenesis:
Orderer:
<<: *OrdererDefaults
Organizations:
- *ZXY
Consortiums:
SampleConsortium:
Organizations:
- *CHEN
- *ZHOU
# 使用TwoOrgsChannel配置文件创建应用程序通道
TwoOrgsChannel:
Consortium: SampleConsortium
Application:
<<: *ApplicationDefaults
Organizations:
- *CHEN
- *ZHOU

排序区块是排序服务的创世区块,通过以上命令就可以预先生成创世区块的 protobuf 格式的配置文件 ./config/genesis.block 了。这一步也是为后续做准备用的。

6、创建并启动各组织的节点

我们说过:我们假设 ZXY 作为一个运营方,提供了 1 个 Orderer 节点 orderer.zxy.com 来创建联盟链的基础设施, 而 CHENZHOU 则是作为组织成员加入到链中,各自提供 2 个 Peer 节点 peer0.xx.compeer1.xx.com 参与工作。

现在这些组织及其节点所需要的配置已经准备好了。我们接下来就可以使用 Docker Compose 来模拟启动这些节点服务。

由于这些节点之间需要互相通信,所以我们需要将这些节点都放入到一个 Docker 网络中,以 fabric_network 为例。

docker-compose.yaml 的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
version: '2.1'

volumes:
orderer.zxy.com:
peer0.chen.com:
peer1.chen.com:
peer0.zhou.com:
peer1.zhou.com:

networks:
fabric_network:
name: fabric_network

services:
# 排序服务节点
orderer.zxy.com:
container_name: orderer.zxy.com
image: hyperledger/fabric-orderer:1.4.12
environment:
- GODEBUG=netdns=go
- ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
- ORDERER_GENERAL_GENESISMETHOD=file
- ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/config/genesis.block # 注入创世区块
- ORDERER_GENERAL_LOCALMSPID=ZXYMSP
- ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/orderer/msp # 证书相关
command: orderer
ports:
- "7050:7050"
volumes: # 挂载由cryptogen和configtxgen生成的证书文件以及创世区块
- ./config/genesis.block:/etc/hyperledger/config/genesis.block
- ./crypto-config/ordererOrganizations/zxy.com/orderers/orderer.zxy.com/:/etc/hyperledger/orderer
- orderer.zxy.com:/var/hyperledger/production/orderer
networks:
- fabric_network

# CHEN 组织 peer0 节点
peer0.chen.com:
extends:
file: docker-compose-base.yaml
service: peer-base
container_name: peer0.chen.com
environment:
- CORE_PEER_ID=peer0.chen.com
- CORE_PEER_LOCALMSPID=CHENMSP
- CORE_PEER_ADDRESS=peer0.chen.com:7051
- CORE_LEDGER_STATE_STATEDATABASE=CouchDB
- CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb:5984
- CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
- CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=123456
ports:
- "7051:7051" # grpc服务端口
- "7053:7053" # eventhub端口
volumes:
- ./crypto-config/peerOrganizations/chen.com/peers/peer0.chen.com:/etc/hyperledger/peer
- peer0.chen.com:/var/hyperledger/production
depends_on:
- orderer.zxy.com

# CHEN 组织 peer1 节点
peer1.chen.com:
extends:
file: docker-compose-base.yaml
service: peer-base
container_name: peer1.chen.com
environment:
- CORE_PEER_ID=peer1.chen.com
- CORE_PEER_LOCALMSPID=CHENMSP
- CORE_PEER_ADDRESS=peer1.chen.com:7051
- CORE_LEDGER_STATE_STATEDATABASE=CouchDB
- CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb:5984
- CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
- CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=123456
ports:
- "17051:7051"
- "17053:7053"
volumes:
- ./crypto-config/peerOrganizations/chen.com/peers/peer1.chen.com:/etc/hyperledger/peer
- peer1.chen.com:/var/hyperledger/production
depends_on:
- orderer.zxy.com

# ZHOU 组织 peer0 节点
peer0.zhou.com:
extends:
file: docker-compose-base.yaml
service: peer-base
container_name: peer0.zhou.com
environment:
- CORE_PEER_ID=peer0.zhou.com
- CORE_PEER_LOCALMSPID=ZHOUMSP
- CORE_PEER_ADDRESS=peer0.zhou.com:7051
- CORE_LEDGER_STATE_STATEDATABASE=CouchDB
- CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb:5984
- CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
- CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=123456
ports:
- "27051:7051"
- "27053:7053"
volumes:
- ./crypto-config/peerOrganizations/zhou.com/peers/peer0.zhou.com:/etc/hyperledger/peer
- peer0.zhou.com:/var/hyperledger/production
depends_on:
- orderer.zxy.com

# ZHOU 组织 peer1 节点
peer1.zhou.com:
extends:
file: docker-compose-base.yaml
service: peer-base
container_name: peer1.zhou.com
environment:
- CORE_PEER_ID=peer1.zhou.com
- CORE_PEER_LOCALMSPID=ZHOUMSP
- CORE_PEER_ADDRESS=peer1.zhou.com:7051
- CORE_LEDGER_STATE_STATEDATABASE=CouchDB
- CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb:5984
- CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
- CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=123456
ports:
- "37051:7051"
- "37053:7053"
volumes:
- ./crypto-config/peerOrganizations/zhou.com/peers/peer1.zhou.com:/etc/hyperledger/peer
- peer1.zhou.com:/var/hyperledger/production
depends_on:
- orderer.zxy.com

# 客户端节点
cli:
container_name: cli
image: hyperledger/fabric-tools:1.4.12
tty: true
environment:
# go 环境设置
- GO111MODULE=auto
- GOPROXY=https://goproxy.cn
- CORE_PEER_ID=cli
command: /bin/bash
volumes:
- ./config:/etc/hyperledger/config
- ./crypto-config/peerOrganizations/chen.com/:/etc/hyperledger/peer/chen.com
- ./crypto-config/peerOrganizations/zhou.com/:/etc/hyperledger/peer/zhou.com
- ./../chaincode:/opt/gopath/src/chaincode # 链码路径注入
networks:
- fabric_network
depends_on:
- orderer.zxy.com
- peer0.chen.com
- peer1.chen.com
- peer0.zhou.com
- peer1.zhou.com

couchdb:
container_name: couchdb
image: hyperledger/fabric-couchdb:latest
# Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
# for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
environment:
- COUCHDB_USER=admin
- COUCHDB_PASSWORD=123456
# Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service,
# for example map it to utilize Fauxton User Interface in dev environments.
ports:
- "5984:5984"
networks:
- fabric_network

为了方便,这里我还定义了一个 docker-compose-base.yaml 作为 Peer 节点的公共模板,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '2.1'

services:
peer-base: # peer的公共服务
image: hyperledger/fabric-peer:1.4.12
environment:
- GODEBUG=netdns=go
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_LOGGING_PEER=info
- CORE_CHAINCODE_LOGGING_LEVEL=INFO
- CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/msp # msp证书(节点证书)
# - CORE_LEDGER_STATE_STATEDATABASE=CouchDB # 状态数据库的存储引擎(or CouchDB)
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_network # docker 网络
volumes:
- /var/run/docker.sock:/host/var/run/docker.sock
working_dir: /opt/gopath/src/github.com/hyperledger/fabric
command: peer node start
networks:
- fabric_network

编写智能合约

1、编写操作账本的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package utils

import (
"encoding/json"
"errors"
"fmt"

"github.com/hyperledger/fabric/core/chaincode/shim"
)

// WriteLedger 写入账本
func WriteLedger(obj interface{}, stub shim.ChaincodeStubInterface, objectType string, keys []string) error {
//创建复合主键
var key string
if val, err := stub.CreateCompositeKey(objectType, keys); err != nil {
return errors.New(fmt.Sprintf("%s-创建复合主键出错 %s", objectType, err))
} else {
key = val
}
bytes, err := json.Marshal(obj)
if err != nil {
return errors.New(fmt.Sprintf("%s-序列化json数据失败出错: %s", objectType, err))
}
//写入区块链账本
if err := stub.PutState(key, bytes); err != nil {
return errors.New(fmt.Sprintf("%s-写入区块链账本出错: %s", objectType, err))
}
return nil
}

// DelLedger 删除账本
func DelLedger(stub shim.ChaincodeStubInterface, objectType string, keys []string) error {
//创建复合主键
var key string
if val, err := stub.CreateCompositeKey(objectType, keys); err != nil {
return errors.New(fmt.Sprintf("%s-创建复合主键出错 %s", objectType, err))
} else {
key = val
}
//写入区块链账本
if err := stub.DelState(key); err != nil {
return errors.New(fmt.Sprintf("%s-删除区块链账本出错: %s", objectType, err))
}
return nil
}

// GetStateByPartialCompositeKeys 根据复合主键查询数据(适合获取全部,多个,单个数据)
// 将keys拆分查询
func GetStateByPartialCompositeKeys(stub shim.ChaincodeStubInterface, objectType string, keys []string) (results [][]byte, err error) {
if len(keys) == 0 {
// 传入的keys长度为0,则查找并返回所有数据
// 通过主键从区块链查找相关的数据,相当于对主键的模糊查询
resultIterator, err := stub.GetStateByPartialCompositeKey(objectType, keys)
if err != nil {
return nil, errors.New(fmt.Sprintf("%s-获取全部数据出错: %s", objectType, err))
}
defer resultIterator.Close()

//检查返回的数据是否为空,不为空则遍历数据,否则返回空数组
for resultIterator.HasNext() {
val, err := resultIterator.Next()
if err != nil {
return nil, errors.New(fmt.Sprintf("%s-返回的数据出错: %s", objectType, err))
}

results = append(results, val.GetValue())
}
} else {
// 传入的keys长度不为0,查找相应的数据并返回
for _, v := range keys {
// 创建组合键
key, err := stub.CreateCompositeKey(objectType, []string{v})
if err != nil {
return nil, errors.New(fmt.Sprintf("%s-创建组合键出错: %s", objectType, err))
}
// 从账本中获取数据
bytes, err := stub.GetState(key)
if err != nil {
return nil, errors.New(fmt.Sprintf("%s-获取数据出错: %s", objectType, err))
}

if bytes != nil {
results = append(results, bytes)
}
}
}

return results, nil
}

// GetStateByPartialCompositeKeys2 根据复合主键查询数据(适合获取全部或指定的数据)
func GetStateByPartialCompositeKeys2(stub shim.ChaincodeStubInterface, objectType string, keys []string) (results [][]byte, err error) {
// 通过主键从区块链查找相关的数据,相当于对主键的模糊查询
resultIterator, err := stub.GetStateByPartialCompositeKey(objectType, keys)
if err != nil {
return nil, errors.New(fmt.Sprintf("%s-获取全部数据出错: %s", objectType, err))
}
defer resultIterator.Close()

//检查返回的数据是否为空,不为空则遍历数据,否则返回空数组
for resultIterator.HasNext() {
val, err := resultIterator.Next()
if err != nil {
return nil, errors.New(fmt.Sprintf("%s-返回的数据出错: %s", objectType, err))
}

results = append(results, val.GetValue())
}
return results, nil
}

2、定义结构体

1
2
3
4
5
6
7
8
package model

// Account 账户
type Account struct {
AccountId string `json:"accountId"` //账号ID
UserName string `json:"userName"` //账号名
Password float64 `json:"password"` //余额
}

3、编写测试链码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package api

import (
"chaincode/pkg/utils"

"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)

// Hello 测试
func Hello(stub shim.ChaincodeStubInterface, args []string) pb.Response {
err := utils.WriteLedger(map[string]interface{}{"msg": "hello"}, stub, "hello", []string{})
if err != nil {
return shim.Error(err.Error())
}
return shim.Success([]byte("hello world"))
}

4、初始化链码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"chaincode/api"
"fmt"
"time"

"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)

type BlockChainRealEstate struct {
}

// Init 链码初始化
func (t *BlockChainRealEstate) Init(stub shim.ChaincodeStubInterface) pb.Response {
fmt.Println("链码初始化")
return shim.Success(nil)
}

// Invoke 实现Invoke接口调用智能合约
func (t *BlockChainRealEstate) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
funcName, args := stub.GetFunctionAndParameters()
switch funcName {
case "hello":
return api.Hello(stub, args)
default:
return shim.Error(fmt.Sprintf("没有该功能: %s", funcName))
}
}

func main() {
timeLocal, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}
time.Local = timeLocal
err = shim.Start(new(BlockChainRealEstate))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}

5、运行以下命令获取所需依赖,会生成go.mod 和 go.sum 文件

1
2
go mod init
go mod tidy

编写应用程序

1、创建一个 config.yaml ,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
version: 1.0.0

# GO SDK使用的客户端部分。
client:
# 客户端所属的组织,必须是organizations定义的组织
organization: ZHOU
# 日志级别
logging:
level: info
# MSP证书的根路径
cryptoconfig:
path: /network/crypto-config

# 通道定义
channels:
appchannel:
orderers:
- orderer.zxy.com
peers:
peer0.zhou.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
peer1.zhou.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true

# 组织配置
organizations:
ZHOU:
mspid: "ZHOUMSP"
cryptoPath: peerOrganizations/zhou.com/users/{username}@zhou.com/msp
peers:
- peer0.zhou.com
- peer1.zhou.com

# orderer节点列表
orderers:
orderer.zxy.com:
url: orderer.zxy.com:7050
# 传递给gRPC客户端构造函数
grpcOptions:
ssl-target-name-override: orderer.zxy.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: true

# peers节点列表
peers:
# peer节点定义,可以定义多个
peer0.zhou.com:
# URL用于发送背书和查询请求
url: peer0.zhou.com:7051
# 传递给gRPC客户端构造函数
grpcOptions:
ssl-target-name-override: peer0.zhou.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: true
peer1.zhou.com:
url: peer1.zhou.com:7051
grpcOptions:
ssl-target-name-override: peer1.zhou.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: true
peer0.chen.com:
url: peer0.chen.com:7051
grpcOptions:
ssl-target-name-override: peer0.chen.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: true
peer1.chen.com:
url: peer1.chen.com:7051
grpcOptions:
ssl-target-name-override: peer1.chen.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: true

2、实例化 SDK ,创建 sdk.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package blockchain

import (
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
)

// 配置信息
var (
sdk *fabsdk.FabricSDK // Fabric SDK
configPath = "config.yaml" // 配置文件路径
channelName = "appchannel" // 通道名称
user = "Admin" // 用户
chainCodeName = "fabric-realty" // 链码名称
endpoints = []string{"peer0.zhou.com", "peer0.chen.com"} // 要发送交易的节点
)

// Init 初始化
func Init() {
var err error
// 通过配置文件初始化SDK
sdk, err = fabsdk.New(config.FromFile(configPath))
if err != nil {
panic(err)
}
}

// ChannelExecute 区块链交互
func ChannelExecute(fcn string, args [][]byte) (channel.Response, error) {
// 创建客户端,表明在通道的身份
ctx := sdk.ChannelContext(channelName, fabsdk.WithUser(user))
cli, err := channel.New(ctx)
if err != nil {
return channel.Response{}, err
}
// 对区块链账本的写操作(调用了链码的invoke)
resp, err := cli.Execute(channel.Request{
ChaincodeID: chainCodeName,
Fcn: fcn,
Args: args,
}, channel.WithTargetEndpoints(endpoints...))
if err != nil {
return channel.Response{}, err
}
//返回链码执行后的结果
return resp, nil
}

// ChannelQuery 区块链查询
func ChannelQuery(fcn string, args [][]byte) (channel.Response, error) {
// 创建客户端,表明在通道的身份
ctx := sdk.ChannelContext(channelName, fabsdk.WithUser(user))
cli, err := channel.New(ctx)
if err != nil {
return channel.Response{}, err
}
// 对区块链账本查询的操作(调用了链码的invoke),只返回结果
resp, err := cli.Query(channel.Request{
ChaincodeID: chainCodeName,
Fcn: fcn,
Args: args,
}, channel.WithTargetEndpoints(endpoints...))
if err != nil {
return channel.Response{}, err
}
//返回链码执行后的结果
return resp, nil
}

3、定义接收的请求参数,HTTP响应中返回一个标准的JSON格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package app

import (
"github.com/gin-gonic/gin"
)

type Gin struct {
C *gin.Context
}

type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}

func (g *Gin) Response(httpCode int, errMsg string, data interface{}) {
g.C.JSON(httpCode, Response{
Code: httpCode,
Msg: errMsg,
Data: data,
})
return
}

4、编写测试接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package v1

import (
"application/pkg/app"
"net/http"

"github.com/gin-gonic/gin"
)

func Hello(c *gin.Context) {
appG := app.Gin{C: c}
appG.Response(http.StatusOK, "成功", map[string]interface{}{
"msg": "Hello",
})
}

5、定义访问路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package routers

import (
v1 "application/api/v1"

"github.com/gin-gonic/gin"
)

// InitRouter 初始化路由信息
func InitRouter() *gin.Engine {
r := gin.Default()

apiV1 := r.Group("/api/v1")
{
apiV1.GET("/hello", v1.Hello)
}

return r
}

6、编写应用入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"log"
"net/http"
"time"

"application/blockchain"
"application/routers"
)

func main() {
timeLocal, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
log.Printf("时区设置失败 %s", err)
}
time.Local = timeLocal

blockchain.Init()

endPoint := fmt.Sprintf("0.0.0.0:%d", 8000)
server := &http.Server{
Addr: endPoint,
Handler: routers.InitRouter(),
}
log.Printf("[info] start http server listening %s", endPoint)
if err := server.ListenAndServe(); err != nil {
log.Printf("start http server failed %s", err)
}
}

7、继续使用 Docker 部署该应用程序

​ 首先编写 Dockerfile 文件:

1
2
3
4
5
6
7
8
9
10
11
12
FROM golang:1.14 AS app
ENV GO111MODULE=on
ENV GOPROXY https://goproxy.cn,direct
WORKDIR /root/togettoyou
COPY server/. .
RUN CGO_ENABLED=0 go build -v -o "app" .

FROM scratch
WORKDIR /root/togettoyou/
COPY --from=app /root/togettoyou/app ./
COPY --from=app /root/togettoyou/config.yaml ./
ENTRYPOINT ["./app"]

docker-compose.yml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '2.1'

networks:
fabric_network:
external:
name: fabric_network

services:
fabric-realty.app:
build: .
image: fabric-realty/application:latest
container_name: fabric-realty.app
ports:
- "8000:8000"
volumes:
- /usr/share/zoneinfo/Asia/Shanghai:/usr/share/zoneinfo/Asia/Shanghai
- ./../network/crypto-config:/network/crypto-config
networks:
- fabric_network

8、同上,使用以下两个命令获取依赖

1
2
go mod init
go mod mody

运行

上面只是准备了fabric后端接口框架所需要的文件,接下来是逐步启动改后端应用接口的命令

1、目录结构

  • application/server : fabric-sdk-go 调用链码(即智能合约),gin 提供外部访问接口(RESTful API)
  • chaincode : go 编写的链码(即智能合约)
  • network : Hyperledger Fabric 区块链网络配置

2、在network目录下编写stop.sh脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash

# 清除链码容器
function clearContainers() {
CONTAINER_IDS=$(docker ps -a | awk '($2 ~ /dev-peer.*fabric-realty.*/) {print $1}')
if [ -z "$CONTAINER_IDS" -o "$CONTAINER_IDS" == " " ]; then
echo "---- No containers available for deletion ----"
else
docker rm -f $CONTAINER_IDS
fi
}

# 清除链码镜像
function removeUnwantedImages() {
DOCKER_IMAGE_IDS=$(docker images | awk '($1 ~ /dev-peer.*fabric-realty.*/) {print $3}')
if [ -z "$DOCKER_IMAGE_IDS" -o "$DOCKER_IMAGE_IDS" == " " ]; then
echo "---- No images available for deletion ----"
else
docker rmi -f $DOCKER_IMAGE_IDS
fi
}

echo "清理环境"
mkdir -p config
mkdir -p crypto-config
rm -rf config/*
rm -rf crypto-config/*
docker-compose down -v
clearContainers
removeUnwantedImages
echo "清理完毕"

3、在该目录下继续编写脚本start.sh用于构建与启动network网络环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/bin/bash

if [[ `uname` == 'Darwin' ]]; then
echo "Mac OS"
export PATH=${PWD}/hyperledger-fabric-darwin-amd64-1.4.12/bin:$PATH
fi
if [[ `uname` == 'Linux' ]]; then
echo "Linux"
export PATH=${PWD}/hyperledger-fabric-linux-amd64-1.4.12/bin:$PATH
fi

echo "一、清理环境"
./stop.sh

echo "二、生成证书和秘钥( MSP 材料),生成结果将保存在 crypto-config 文件夹中"
cryptogen generate --config=./crypto-config.yaml

echo "三、创建排序通道创世区块"
configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./config/genesis.block -channelID firstchannel

echo "四、生成通道配置事务'appchannel.tx'"
configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./config/appchannel.tx -channelID appchannel

echo "五、为 CHEN 定义锚节点"
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./config/CHENAnchor.tx -channelID appchannel -asOrg CHEN

echo "六、为 ZHOU 定义锚节点"
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./config/ZHOUAnchor.tx -channelID appchannel -asOrg ZHOU

echo "区块链 : 启动"
docker-compose up -d
echo "正在等待节点的启动完成,等待10秒"
sleep 10

CHENPeer0Cli="CORE_PEER_ADDRESS=peer0.chen.com:7051 CORE_PEER_LOCALMSPID=CHENMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/chen.com/users/Admin@chen.com/msp"
CHENPeer1Cli="CORE_PEER_ADDRESS=peer1.chen.com:7051 CORE_PEER_LOCALMSPID=CHENMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/chen.com/users/Admin@chen.com/msp"
ZHOUPeer0Cli="CORE_PEER_ADDRESS=peer0.zhou.com:7051 CORE_PEER_LOCALMSPID=ZHOUMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/zhou.com/users/Admin@zhou.com/msp"
ZHOUPeer1Cli="CORE_PEER_ADDRESS=peer1.zhou.com:7051 CORE_PEER_LOCALMSPID=ZHOUMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/zhou.com/users/Admin@zhou.com/msp"

echo "七、创建通道"
docker exec cli bash -c "$CHENPeer0Cli peer channel create -o orderer.zxy.com:7050 -c appchannel -f /etc/hyperledger/config/appchannel.tx"

echo "八、将所有节点加入通道"
docker exec cli bash -c "$CHENPeer0Cli peer channel join -b appchannel.block"
docker exec cli bash -c "$CHENPeer1Cli peer channel join -b appchannel.block"
docker exec cli bash -c "$ZHOUPeer0Cli peer channel join -b appchannel.block"
docker exec cli bash -c "$ZHOUPeer1Cli peer channel join -b appchannel.block"

echo "九、更新锚节点"
docker exec cli bash -c "$CHENPeer0Cli peer channel update -o orderer.zxy.com:7050 -c appchannel -f /etc/hyperledger/config/CHENAnchor.tx"
docker exec cli bash -c "$ZHOUPeer0Cli peer channel update -o orderer.zxy.com:7050 -c appchannel -f /etc/hyperledger/config/ZHOUAnchor.tx"

# -n 链码名,可以自己随便设置
# -v 版本号
# -p 链码目录,在 /opt/gopath/src/ 目录下
echo "十、安装链码"
docker exec cli bash -c "$CHENPeer0Cli peer chaincode install -n fabric-realty -v 1.0.0 -l golang -p chaincode"
docker exec cli bash -c "$ZHOUPeer0Cli peer chaincode install -n fabric-realty -v 1.0.0 -l golang -p chaincode"

# 只需要其中一个节点实例化
# -n 对应上一步安装链码的名字
# -v 版本号
# -C 是通道,在fabric的世界,一个通道就是一条不同的链
# -c 为传参,传入init参数
echo "十一、实例化链码"
docker exec cli bash -c "$CHENPeer0Cli peer chaincode instantiate -o orderer.zxy.com:7050 -C appchannel -n fabric-realty -l golang -v 1.0.0 -c '{\"Args\":[\"init\"]}' -P \"AND ('CHENMSP.member','ZHOUMSP.member')\""

echo "正在等待链码实例化完成,等待5秒"
sleep 5

# 进行链码交互,验证链码是否正确安装及区块链网络能否正常工作
echo "十二、验证链码"
docker exec cli bash -c "$CHENPeer0Cli peer chaincode invoke -C appchannel -n fabric-realty -c '{\"Args\":[\"hello\"]}'"
docker exec cli bash -c "$ZHOUPeer0Cli peer chaincode invoke -C appchannel -n fabric-realty -c '{\"Args\":[\"hello\"]}'"

4、进入application目录下,编写构建应用程序的脚本build.sh

1
2
3
#!/bin/bash

docker-compose build

5、继续编写停止应用程序的脚本stop.sh

1
2
3
#!/bin/bash

docker-compose down

6、编写启动应用程序的脚本start.sh

1
2
3
#!/bin/bash

docker-compose up -d

7、运行

  1. 进入 network 目录,执行 ./start.sh 部署区块链网络和智能合约
  2. 进入 application 目录,执行 ./start.sh 启动前后端应用,然后就可使用浏览器访问接口 http://localhost:8000/api/v1/hello 进行测试(初次启动需要运行 ./build.sh 命令进行构建应用程序)

拓展

搭建区块链网络(未尝试)

1、在network目录下新建explorer目录,进入该目录,编写 docker-compose.yaml 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# SPDX-License-Identifier: Apache-2.0
version: '2.1'

volumes:
pgdata:
walletstore:

networks:
fabric_network:
external:
name: fabric_network

services:

explorerdb.com:
image: hyperledger/explorer-db:1.1.6
container_name: explorerdb.com
hostname: explorerdb.com
environment:
- DATABASE_DATABASE=fabricexplorer
- DATABASE_USERNAME=hppoc
- DATABASE_PASSWORD=password
healthcheck:
test: "pg_isready -h localhost -p 5432 -q -U postgres"
interval: 30s
timeout: 10s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- fabric_network

explorer.com:
image: hyperledger/explorer:1.1.6
container_name: explorer.com
hostname: explorer.com
environment:
- DATABASE_HOST=explorerdb.com
- DATABASE_DATABASE=fabricexplorer
- DATABASE_USERNAME=hppoc
- DATABASE_PASSWD=password
- LOG_LEVEL_APP=debug
- LOG_LEVEL_DB=debug
- LOG_LEVEL_CONSOLE=info
- LOG_CONSOLE_STDOUT=true
- DISCOVERY_AS_LOCALHOST=false
volumes:
- ./config.json:/opt/explorer/app/platform/fabric/config.json
- ./connection-profile:/opt/explorer/app/platform/fabric/connection-profile
- ../crypto-config:/tmp/crypto
- walletstore:/opt/explorer/wallet
ports:
- "8080:8080"
depends_on:
explorerdb.com:
condition: service_healthy
networks:
- fabric_network

2、编写 config.json 文件

1
2
3
4
5
6
7
8
9
{
"network-configs": {
"test-network": {
"name": "Fabric Network",
"profile": "./connection-profile/network.json"
}
},
"license": "Apache-2.0"
}

3、新建目录 connection-profile ,进入该目录,编写 network_temp.json 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
{
"name": "fabric-network",
"version": "1.0.0",
"client": {
"tlsEnable": true,
"adminCredential": {
"id": "admin",
"password": "123456"
},
"enableAuthentication": true,
"organization": "CHENMSP",
"connection": {
"timeout": {
"peer": {
"endorser": "300"
},
"orderer": "300"
}
}
},
"channels": {
"appchannel": {
"peers": {
"peer0.chen.com": {}
}
}
},
"organizations": {
"CHENMSP": {
"mspid": "CHENMSP",
"adminPrivateKey": {
"path": "/tmp/crypto/peerOrganizations/chen.com/users/Admin@chen.com/msp/keystore/priv_sk"
},
"peers": [
"peer0.chen.com"
],
"signedCert": {
"path": "/tmp/crypto/peerOrganizations/chen.com/users/Admin@chen.com/msp/signcerts/Admin@chen.com-cert.pem"
}
}
},
"peers": {
"peer0.chen.com": {
"tlsCACerts": {
"path": "/tmp/crypto/peerOrganizations/chen.com/peers/peer0.chen.com/tls/ca.crt"
},
"url": "grpc://peer0.chen.com:7051"
}
}
}

4、编写脚本 ./stop.sh

1
2
3
#!/bin/bash

docker-compose down -v

5、编写脚本 ./start.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

priv_sk_path=$(ls ../crypto-config/peerOrganizations/chen.com/users/Admin\@chen.com/msp/keystore/)

cp -rf ./connection-profile/network_temp.json ./connection-profile/network.json

sed -i "s/priv_sk/$priv_sk_path/" ./connection-profile/network.json

docker-compose down -v
docker-compose up -d

6、运行

  • 命令行输入 ./start.sh

总结

最简单的方法就是直接 拷我u盘的代码~~