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 package mainimport ( "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" ) cert, err := ioutil.ReadFile(filepath.Clean(certPath)) if err != nil { return err } keyDir := filepath.Join(credPath, "keystore" ) 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 } 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 func NewX509Identity (mspid string , cert string , key string ) *X509Identity { return &X509Identity{1 , mspid, x509Type, credentials{cert, key}} } 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) } 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() 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) } 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 } 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 } 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 } 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...) } 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 } type Request struct { ChaincodeID string Fcn string Args [][]byte TransientMap map [string ][]byte 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}) } 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
将向账本提交交易。 交易将在背书节点上进行评估,然后提交到排序服务以提交到账本 。这可用于修改世界状态。