HyperledgerExplorer安装与SDK解析

安装

详细步骤参照hyperledger Explorer README 文件中 quick start using docker 部分。

试验目录: /data/explorer

前置环境:启动 fabric test-netwrok 网络 并安装 fabric-sample中的 fabcar 链码

目录结构如下:

1
2
3
4
5
docker-compose.yaml
config.json
connection-profile/test-network.json
organizations/ordererOrganizations/
organizations/peerOrganizations/

核心就是配置前三个文件,完善explorer的用户、证书、和网络配置。后两个是test-network生成的证书文件夹

其中test-network.json指定了explorer的登录账号密码和channel及组织证书信息

docker-compose.yaml 指定了explorer的数据库容器和explorer容器的生成配置

解析

hyplerledger explorer 主要可以分为两个部分组成 fabric-sdk-node 组成的后端 和 react.js 组成的前端

其中此处主要是 借explorer fabric-sdk-node的 调用,分析通过sdk调用系统信息的流程。

此处以fabric-sdk-go做实例分析,并通过go的sdk初略重构一下explorer的sdk部分。

SDK中提供的软件包

pkg/fabsdk:Fabric SDK 的主要包。(配置创建上下文)参考:https : //godoc.org/github.com/hyperledger/fabric-sdk-go/pkg/fabsdk

pkg/client/channel:提供通道事务能力。(查询、调用链码方法)参考:https : //godoc.org/github.com/hyperledger/fabric-sdk-go/pkg/client/channel

pkg/client/event:提供通道事件功能。(注册、查询事件)参考:https : //godoc.org/github.com/hyperledger/fabric-sdk-go/pkg/client/event

pkg/client/ledger:启用对通道底层账本的查询。(查询区块、交易、配置等信息)参考:https : //godoc.org/github.com/hyperledger/fabric-sdk-go/pkg/client/ledger

pkg/client/resmgmt:提供安装链码等资源管理能力。(创建、加入通道、安装链码)参考:https : //godoc.org/github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt

pkg/client/msp:启用身份管理功能。(创建msp、登录注册user)参考:https : //godoc.org/github.com/hyperledger/fabric-sdk-go/pkg/client/msp

基本工作流程

1
2
3
4
5
6
7
8
1) 使用配置实例化 fabsdk 实例。
注意:fabsdk 维护缓存,所以你应该尽量减少 fabsdk 本身的实例。
2) 使用您的 fabsdk 实例创建基于用户和组织的上下文。
注意:频道上下文还需要频道 ID。
3) 使用其 New func 创建一个客户端实例,传递上下文。
注意:您为每个需要的上下文创建一个新的客户端实例。
4) 使用每个客户端提供的函数来创建您的解决方案!
5)调用fabsdk.Close()释放资源和缓存。

由于hyperledger explorer 更多是关注于对系统的底层信息的获取,如:block number、block hash、 transaction、chaincode等信息。所以我们以pkg/client/ledger`所提供的对通道底层账本的查询为例,其他的功能参照其他几个包的实现。

ledger主要提供了如下几个方法:

  • ```
    func New(channelProvider context.ChannelProvider, opts …ClientOption) (*Client, error)

    1
    2
    3

    * ```
    func (c *Client) QueryBlock(blockNumber uint64, options ...RequestOption) (*common.Block, error)
  • ```
    func (c Client) QueryBlockByHash(blockHash []byte, options …RequestOption) (common.Block, error)

    1
    2
    3

    * ```
    func (c *Client) QueryBlockByTxID(txID fab.TransactionID, options ...RequestOption) (*common.Block, error)
  • ```
    unc (c *Client) QueryConfig(options …RequestOption) (fab.ChannelCfg, error)

    1
    2
    3

    * ```
    func (c *Client) QueryConfigBlock(options ...RequestOption) (*common.Block, error)
  • ```
    func (c Client) QueryInfo(options …RequestOption) (fab.BlockchainInfoResponse, error)

    1
    2
    3

    * ```
    func (c *Client) QueryTransaction(transactionID fab.TransactionID, options ...RequestOption) (*pb.ProcessedTransaction, error)

其中New创建SDK客户端,QueryInfo查询整体概览信息,QueryTransaction查询交易信息,QueryConfig查询配置信息,其他的大多都是查询具体的区块信息。

Explorer的功能分析

  • 身份管理,如:添加用户

  • 网络信息,如:node name、node url、node type……

  • 区块信息
  • 交易信息
  • 链码信息
  • 通道信息

其中身份管理可以通过msp管理、网络信息通过getway管理、通道信息通过channel获取;区块、交易、链码等信息可以通过ledger获取。

其中对智能合约的调用可以通过getway获得Contract对象调用合约中的方法。

Explorer的方法分析

app/platform/fabric/geteway/FabricGetway.ts为例

explorer中查询区块链的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 async queryChainInfo(channelName) {
try {
const network = await this.gateway.getNetwork(this.defaultChannelName);

// Get the contract from the network.
const contract = network.getContract('qscc');
const resultByte = await contract.evaluateTransaction(
'GetChainInfo',
channelName
);
const resultJson = fabprotos.common.BlockchainInfo.decode(resultByte);
logger.debug('queryChainInfo', resultJson);
return resultJson;
} catch (error) {
logger.error(
`Failed to get chain info from channel ${channelName} : `,
error
);
return null;
}
}

其步骤和平时调用智能合约一样

  • 获取channel name
  • 获得系统合约
  • 调用系统合约中的方法
  • 获得调用结果

只不过需要了解fabric中系统合约及其相关的系统函数

基于fabric-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
package main

import (
"encoding/hex"
"errors"
"fmt"
"github.com/hyperledger/fabric-sdk-go/pkg/client/ledger"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
"io/ioutil"
"log"
_ "os"
"path/filepath"

"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)

func main() {
GetSystemInfo()
}

const (
channelID = "mychannel"
chaincodeID = "econtract"
orgID = "Org1"
userID = "Admin"
)


func GetSystemInfo() {
configOpt := config.FromFile("./config.yaml")
sdk, err := fabsdk.New(configOpt)
if err != nil {
log.Fatalf("创建新的SDK失败: %v\n", err)
return
}
defer sdk.Close()
log.Printf("---> 创建SDK成功\n")

var options_user fabsdk.ContextOption
var options_org fabsdk.ContextOption

options_user = fabsdk.WithUser(userID)
options_org = fabsdk.WithOrg(orgID)

clientChannelContext := sdk.ChannelContext(channelID, options_user, options_org)
client, err := ledger.New(clientChannelContext)
if err != nil {
log.Fatalf("创建sdk客户端失败: %v\n", err)
return
}

info, err := client.QueryInfo()
if err != nil {
log.Fatalf("查询区块链概况: %v\n", err)
return
}

fmt.Printf("blockHeight:\n%v\n", info.BCI.Height)
fmt.Printf("CurrentBlockHash:\n%v\n", hex.EncodeToString(info.BCI.CurrentBlockHash))
fmt.Printf("PreviousBlockHash:\n%v\n", hex.EncodeToString(info.BCI.PreviousBlockHash))
}

其配置文件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
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
name: "test-network"
version: 1.0.0

client:
organization: Org1
logging:
level: info


cryptoconfig:
path: ./organizations

credentialStore:
path: /tmp/test-network/store

cryptoStore:
path: /tmp/test-network/msp

BCCSP:
security:
enabled: true
default:
provider: "SW"
hashAlgorithm: "SHA2"
softVerify: true
level: 256

tlsCerts:
systemCertPool: false


channels:
mychannel:

peers:
peer0.org1.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true

policies:
queryChannelConfig:
minResponses: 1
maxTargets: 1
retryOpts:
attempts: 5
initialBackoff: 500ms
maxBackoff: 5s
backoffFactor: 2.0
discovery:
maxTargets: 2
retryOpts:
attempts: 4
initialBackoff: 500ms
maxBackoff: 5s
backoffFactor: 2.0
eventService:
resolverStrategy: PreferOrg
balancer: Random
blockHeightLagThreshold: 5
reconnectBlockHeightLagThreshold: 10
peerMonitorPeriod: 5s

organizations:
Org1:
mspid: Org1MSP
cryptoPath: peerOrganizations/org1.example.com/users/{userName}@org1.example.com/msp
peers:
- peer0.org1.example.com
users:
Admin:
key:
path: ./organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/priv_sk
cert:
path: ./organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem
User1:
key:
path: ./organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk
cert:
path: ./organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem



orderers:
orderer.example.com:
url: grpcs://orderer.example.com:7050

grpcOptions:
ssl-target-name-override: orderer.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: false

tlsCACerts:
path: ./organizations/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pem

peers:
peer0.org1.example.com:
url: grpcs://localhost:7051
eventUrl: grpcs://peer0.org1.example.com:7053

grpcOptions:
ssl-target-name-override: peer0.org1.example.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: false

tlsCACerts:
path: ./organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem

entityMatchers:
peer:
- pattern: (\w*)peer0.org1.example.com(\w*)
urlSubstitutionExp: grpcs://localhost:7051
eventUrlSubstitutionExp: grpcs://localhost:7053
sslTargetOverrideUrlSubstitutionExp: peer0.org1.example.com
mappedHost: peer0.org1.example.com

orderer:
- pattern: (.*)
urlSubstitutionExp: grpcs://orderer.example.com:7050
sslTargetOverrideUrlSubstitutionExp: orderer.example.com
mappedHost: orderer.example.com

剖析fabric-sdk-go的细节

很容易发现,我们使用fabric-sdk-go时的代码和sdk-node仿佛有些不同,但其实本质都是一样的。

sdk-go 的接口只是使用了更高阶封装

查询的本质就是调用智能合约

SDK-node中使用最本质的方法调用系统的智能合约

1
2
3
4
5
6
// Get the contract from the network.
const contract = network.getContract('qscc');
const resultByte = await contract.evaluateTransaction(
'GetChainInfo',
channelName
);

SDK-go中在上述的基础上封装了ledger负责处理系统信息

main.go

1
2
client, err := ledger.New(clientChannelContext)
block, err := client.QueryBlock(1)

ledger\ledger.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
// QueryInfo queries for various useful blockchain information on this channel such as block height and current block hash.
// Parameters:
// options are optional request options
//
// Returns:
// blockchain information
func (c *Client) QueryInfo(options ...RequestOption) (*fab.BlockchainInfoResponse, error) {

targets, opts, err := c.prepareRequestParams(options...)
if err != nil {
return nil, errors.WithMessage(err, "QueryInfo failed to prepare request parameters")
}
reqCtx, cancel := c.createRequestContext(opts)
defer cancel()
// 构建请求体 调用智能合约
responses, err := c.ledger.QueryInfo(reqCtx, peersToTxnProcessors(targets), c.verifier)
if err != nil && len(responses) == 0 {
return nil, errors.WithMessage(err, "QueryInfo failed")
}

if len(responses) < opts.MinTargets {
return nil, errors.Errorf("Number of responses %d is less than MinTargets %d. Targets: %v, Error: %s", len(responses), opts.MinTargets, targets, err)
}

response := responses[0]
maxHeight := response.BCI.Height
for i, r := range responses {
if i == 0 {
continue
}

// Match one with highest block height,
if r.BCI.Height > maxHeight {
response = r
maxHeight = r.BCI.Height
}

}

return response, nil
}

channel\ledger.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// QueryInfo queries for various useful information on the state of the channel
// (height, known peers).
func (c *Ledger) QueryInfo(reqCtx reqContext.Context, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*fab.BlockchainInfoResponse, error) {
logger.Debug("queryInfo - start")
// 核心:通过channel调用智能合约
cir := createChannelInfoInvokeRequest(c.chName)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)

responses := []*fab.BlockchainInfoResponse{}
for _, tpr := range tprs {
r, err := createBlockchainInfo(tpr)
if err != nil {
errs = multi.Append(errs, errors.WithMessage(err, "From target: "+tpr.Endorser))
} else {
responses = append(responses, &fab.BlockchainInfoResponse{Endorser: tpr.Endorser, Status: tpr.Status, BCI: r})
}
}
return responses, errs
}

qscc.go

1
2
3
4
5
6
7
8
9
10
11
func createChannelInfoInvokeRequest(channelID string) fab.ChaincodeInvokeRequest {
var args [][]byte
args = append(args, []byte(channelID))

cir := fab.ChaincodeInvokeRequest{
ChaincodeID: qscc,
Fcn: "GetChainInfo",
Args: args,
}
return cir
}

到这里我们可以发现,其实各种查询封装的本质就是官方文档给出的基本流程。

1
2
3
4
1) 使用配置实例化 fabsdk 实例。如:sdk, err := fabsdk.New(configOpt)
2) 使用其 New func 创建一个客户端实例,传递上下文。如:client, err := ledger.New(clientChannelContext)
3) 使用每个客户端提供的函数来创建您的解决方案!如:client.QueryBlock(1)
4)调用fabsdk.Close()释放资源和缓存。

需要注意的是: 调用qscc等系统合约和普通的智能合约不同,不需要背书(discover) 即:只用访问peer即可。
只需要向对等方发送 QSCC 查询,而无需要求发现 QSCC 的背书人。因为每个对等点都有 QSCC。

参考:

fabric-sdk-go官方文档

hyperledger explorer官方文档

fabric sample中fabric-cli案例

https://github.com/jiftle/fabric-sdk-go-demo-observ