Fabric-SDK-GO工作流程解析

首先按惯例,使用fabric-sample中的fabcar案例介绍,其背后的工作流程。

此处只关注于其中的application部分,即调用SDK的部分。

示例

fabcar.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
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
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"

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

func main() {
os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
fmt.Printf("Failed to create wallet: %s\n", err)
os.Exit(1)
}

if !wallet.Exists("appUser") {
err = populateWallet(wallet)
if err != nil {
fmt.Printf("Failed to populate wallet contents: %s\n", err)
os.Exit(1)
}
}

ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)

gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
fmt.Printf("Failed to connect to gateway: %s\n", err)
os.Exit(1)
}
defer gw.Close()

network, err := gw.GetNetwork("mychannel")
if err != nil {
fmt.Printf("Failed to get network: %s\n", err)
os.Exit(1)
}

contract := network.GetContract("fabcar")

result, err := contract.EvaluateTransaction("queryAllCars")
if err != nil {
fmt.Printf("Failed to evaluate transaction: %s\n", err)
os.Exit(1)
}
fmt.Println(string(result))

result, err = contract.SubmitTransaction("createCar", "CAR10", "VW", "Polo", "Grey", "Mary")
if err != nil {
fmt.Printf("Failed to submit transaction: %s\n", err)
os.Exit(1)
}
fmt.Println(string(result))

result, err = contract.EvaluateTransaction("queryCar", "CAR10")
if err != nil {
fmt.Printf("Failed to evaluate transaction: %s\n", err)
os.Exit(1)
}
fmt.Println(string(result))

_, err = contract.SubmitTransaction("changeCarOwner", "CAR10", "Archie")
if err != nil {
fmt.Printf("Failed to submit transaction: %s\n", err)
os.Exit(1)
}

result, err = contract.EvaluateTransaction("queryCar", "CAR10")
if err != nil {
fmt.Printf("Failed to evaluate transaction: %s\n", err)
os.Exit(1)
}
fmt.Println(string(result))
}

func populateWallet(wallet *gateway.Wallet) error {
credPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"users",
"User1@org1.example.com",
"msp",
)

certPath := filepath.Join(credPath, "signcerts", "cert.pem")
// read the certificate pem
cert, err := ioutil.ReadFile(filepath.Clean(certPath))
if err != nil {
return err
}

keyDir := filepath.Join(credPath, "keystore")
// there's a single file in this dir containing the private key
files, err := ioutil.ReadDir(keyDir)
if err != nil {
return err
}
if len(files) != 1 {
return errors.New("keystore folder should have contain one file")
}
keyPath := filepath.Join(keyDir, files[0].Name())
key, err := ioutil.ReadFile(filepath.Clean(keyPath))
if err != nil {
return err
}

identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key))

err = wallet.Put("appUser", identity)
if err != nil {
return err
}
return nil
}

分析

对实例中核心的代码进行深入。

构建与Farbic通讯的身份信息(Wallet)

构建一个Wallet,wallet, err := gateway.NewFileSystemWallet("wallet")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func NewFileSystemWallet(path string) (*Wallet, error) {
cleanPath := filepath.Clean(path)
err := os.MkdirAll(cleanPath, os.ModePerm)

if err != nil {
return nil, err
}

store := &fileSystemWalletStore{cleanPath}
return &Wallet{store}, nil
}

// 钱包存储用于连接到 Hyperledger Fabric 网络的身份信息。 实例是在实现对象上使用工厂方法创建的
type Wallet struct {
store WalletStore
}

构建一个X509格式的信息,identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key))

1
2
3
4
5
6
7
8
9
10
11
12
// NewX509Identity creates an X509 identity for storage in a wallet
func NewX509Identity(mspid string, cert string, key string) *X509Identity {
return &X509Identity{1, mspid, x509Type, credentials{cert, key}}
}

// X509Identity represents an X509 identity
type X509Identity struct {
Version int `json:"version"`
MspID string `json:"mspId"`
IDType string `json:"type"`
Credentials credentials `json:"credentials"`
}

以JSON格式持久化X509身份信息到wallet,err = wallet.Put("appUser", identity)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (w *Wallet) Put(label string, id Identity) error {
content, err := id.toJSON()
if err != nil {
return err
}
return w.store.Put(label, content)
}

// Put an identity into the wallet.
func (fsw *fileSystemWalletStore) Put(label string, content []byte) error {
pathname := filepath.Join(fsw.path, label) + dataFileExtension
f, err := os.OpenFile(filepath.Clean(pathname), os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return err
}
if _, err := f.Write(content); err != nil {
_ = f.Close() // ignore error; Write error takes precedence
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}

构建与Farbic通讯的网络设置(Gateway)

通过配置信息和Wallet和Fabric通讯的基本配置信息,gw, err := gateway.Connect( gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))), gateway.WithIdentity(wallet, "appUser"), )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 连接到由网络配置文件定义的网关。 必须指定一个配置选项、一个身份选项和零个或多个策略选项。
func Connect(config ConfigOption, identity IdentityOption, options ...Option) (*Gateway, error) {
g := &Gateway{
options: &gatewayOptions{
Timeout: defaultTimeout,
},
}
err := identity(g)
if err != nil {
return nil, errors.Wrap(err, "Failed to apply identity option")
}
err = config(g)
if err != nil {
return nil, errors.Wrap(err, "Failed to apply config option")
}
for _, option := range options {
err = option(g)
if err != nil {
return nil, errors.Wrap(err, "Failed to apply gateway option")
}
}
return g, nil
}

选择和Fabric通讯的通道,network, err := gw.GetNetwork("mychannel")

返回通讯的上下文信息(ChannelContext)

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
func (gw *Gateway) GetNetwork(name string) (*Network, error) {
var channelProvider context.ChannelProvider
if gw.options.Identity != nil {
channelProvider = gw.sdk.ChannelContext(name, fabsdk.WithIdentity(gw.options.Identity), fabsdk.WithOrg(gw.org))
} else {
channelProvider = gw.sdk.ChannelContext(name, fabsdk.WithUser(gw.options.User), fabsdk.WithOrg(gw.org))
}
return newNetwork(gw, channelProvider)
}

//ChannelContext creates and returns channel context
func (sdk *FabricSDK) ChannelContext(channelID string, options ...ContextOption) contextApi.ChannelProvider {
channelProvider := func() (contextApi.Channel, error) {
clientCtxProvider := sdk.Context(options...)
return context.NewChannel(clientCtxProvider, channelID)
}
return channelProvider
}

//Context creates and returns context client which has all the necessary providers
func (sdk *FabricSDK) Context(options ...ContextOption) contextApi.ClientProvider {
clientProvider := func() (contextApi.Client, error) {
identity, err := sdk.newIdentity(options...)
if err == ErrAnonymousIdentity {
identity = nil
err = nil
}
return &context.Client{Providers: sdk.provider, SigningIdentity: identity}, err
}
return clientProvider
}

// 读取配置信息,组装user认证信息
func (sdk *FabricSDK) newIdentity(options ...ContextOption) (msp.SigningIdentity, error) {
opts := identityOptions{
orgName: sdk.provider.IdentityConfig().Client().Organization,
}

for _, option := range options {
err1 := option(&opts)
if err1 != nil {
return nil, errors.WithMessage(err1, "error in option passed to create identity")
}
}

if opts.signingIdentity == nil && opts.username == "" {
return nil, ErrAnonymousIdentity
}

if opts.signingIdentity != nil {
return opts.signingIdentity, nil
}

if opts.username == "" || opts.orgName == "" {
return nil, errors.New("invalid options to create identity")
}

mgr, ok := sdk.provider.IdentityManager(opts.orgName)
if !ok {
return nil, errors.New("invalid options to create identity, invalid org name")
}

user, err := mgr.GetSigningIdentity(opts.username)
if err != nil {
return nil, err
}

return user, nil
}

// 根据上下文信息和channel id 构建新通道
func NewChannel(clientProvider context.ClientProvider, channelID string) (*Channel, error) {
client, err := clientProvider()
if err != nil {
return nil, errors.WithMessage(err, "failed to get client context to create channel client")
}
channelService, err := client.ChannelProvider().ChannelService(client, channelID)
if err != nil {
return nil, errors.WithMessage(err, "failed to get channel service to create channel client")
}
channel := &Channel{
Client: client,
channelService: channelService,
channelID: channelID,
metrics: client.GetMetrics(),
}
if pi, ok := channelService.(serviceInit); ok {
if err := pi.Initialize(channel); err != nil {
return nil, err
}
}
return channel, nil
}

执行智能合约(ChainCode)

选择网络上的智能合约,contract := network.GetContract("fabcar")

1
2
3
4
5
6
7
func (n *Network) GetContract(chaincodeID string) *Contract {
return newContract(n, chaincodeID, "")
}

func newContract(network *Network, chaincodeID string, name string) *Contract {
return &Contract{network: network, client: network.client, chaincodeID: chaincodeID, name: name}
}

执行智能合约,result, err := contract.EvaluateTransaction("queryAllCars")

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
func (c *Contract) EvaluateTransaction(name string, args ...string) ([]byte, error) {
txn, err := c.CreateTransaction(name)
if err != nil {
return nil, err
}
return txn.Evaluate(args...)
}

// CreateTransaction 创建一个对象,表示由该合约实现的交易函数的特定调用,并使用可选参数提供对交易调用的更多控制。 必须为每个事务调用创建一个新的事务对象。
func (c *Contract) CreateTransaction(name string, opts ...TransactionOption) (*Transaction, error) {
return newTransaction(name, c, opts...)
}

func newTransaction(name string, contract *Contract, options ...TransactionOption) (*Transaction, error) {
qname := name
if len(contract.name) > 0 {
qname = contract.name + ":" + name
}
txn := &Transaction{
contract: contract,
request: &channel.Request{ChaincodeID: contract.chaincodeID, Fcn: qname},
}
for _, option := range options {
err := option(txn)
if err != nil {
return nil, err
}
}
return txn, nil
}

// 构造交易
type Transaction struct {
contract *Contract
request *channel.Request
endorsingPeers []string
collections []string
eventch chan *fab.TxStatusEvent
}

// 构建交易的GRPC请求 contains the parameters to query and execute an invocation transaction
type Request struct {
ChaincodeID string
Fcn string
Args [][]byte
TransientMap map[string][]byte

// InvocationChain contains meta-data that's used by some Selection Service implementations
// to choose endorsers that satisfy the endorsement policies of all chaincodes involved
// in an invocation chain (i.e. for CC-to-CC invocations).
// Each chaincode may also be associated with a set of private data collection names
// which are used by some Selection Services (e.g. Fabric Selection) to exclude endorsers
// that do NOT have read access to the collections.
// The invoked chaincode (specified by ChaincodeID) may optionally be added to the invocation
// chain along with any collections, otherwise it may be omitted.
InvocationChain []*fab.ChaincodeCall
IsInit bool
}

// 执行交易
func (txn *Transaction) Evaluate(args ...string) ([]byte, error) {
bytes := make([][]byte, len(args))
for i, v := range args {
bytes[i] = []byte(v)
}
txn.request.Args = bytes

var options []channel.RequestOption
if txn.endorsingPeers != nil {
options = append(options, channel.WithTargetEndpoints(txn.endorsingPeers...))
}
options = append(options, channel.WithTimeout(fab.Query, txn.contract.network.gateway.options.Timeout))

if txn.collections != nil {
txn.request.InvocationChain = append(txn.request.InvocationChain, &fab.ChaincodeCall{ID: txn.contract.chaincodeID, Collections: txn.collections})
}
// client GRPC调用请求
response, err := txn.contract.client.Query(
*txn.request,
options...,
)
if err != nil {
return nil, errors.Wrap(err, "Failed to evaluate")
}

return response.Payload, nil
}

不难发现,其实整个SDK的过程,无非是是在准备和调用。

准备和Fabric连接的User信息,网络配置信息,通道,合约信息

然后通过构造好的Client 发起GRPC请求。各种信息被包含在Client 和 Transaction中。

附 两种不同的交易提交模式

1
2
3
4
5
6
7
func (c *Contract) EvaluateTransaction(name string, args ...string) ([]byte, error) {
txn, err := c.CreateTransaction(name)
if err != nil {
return nil, err
}
return txn.Evaluate(args...)
}

EvaluateTransaction 将评估交易函数并返回其结果。 交易将在背书节点上进行评估,但响应不会发送到排序服务,因此不会提交到账本。 这可用于查询世界状态。

1
2
3
4
5
6
7
func (c *Contract) SubmitTransaction(name string, args ...string) ([]byte, error) {
txn, err := c.CreateTransaction(name)
if err != nil {
return nil, err
}
return txn.Submit(args...)
}

SubmitTransaction 将向账本提交交易。 交易将在背书节点上进行评估,然后提交到排序服务以提交到账本。这可用于修改世界状态。