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部分。
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的功能分析
其中身份管理可以通过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); 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 mainimport ( "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 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 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 } 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 func (c *Ledger) QueryInfo (reqCtx reqContext.Context, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*fab.BlockchainInfoResponse, error) { logger.Debug("queryInfo - start" ) 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