资料来源
https://pkg.go.dev/github.com/hyperledger/fabric-sdk-go#section-readme
https://github.com/hyperledger/fabric-sdk-go
https://wiki.hyperledger.org/display/fabric
https://github.com/hyperledger/fabric-samples
书接上回,补充getway链码分析
上期代码实战
点击上次作业链接,查看三种实例的运行结果
asset-transfer-basic
assetTransfer.go
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"
)
func main() {
//新建链码
assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
if err != nil {
log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
}
//启动链码
if err := assetChaincode.Start(); err != nil {
log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
}
}
smartcontract.go
package chaincode
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// SmartContract provides functions for managing an Asset
//链码结构
type SmartContract struct {
contractapi.Contract
}
// Asset describes basic details of what makes up a simple asset
//Insert struct field in alphabetic order => to achieve determinism across languages
// golang keeps the order when marshal to json but doesn't order automatically
type Asset struct {
AppraisedValue int `json:"AppraisedValue"`
Color string `json:"Color"`
ID string `json:"ID"`
Owner string `json:"Owner"`
Size int `json:"Size"`
}
// InitLedger adds a base set of assets to the ledger
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []Asset{
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}
for _, asset := range assets {
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
//存放数据
err = ctx.GetStub().PutState(asset.ID, assetJSON)
if err != nil {
return fmt.Errorf("failed to put to world state. %v", err)
}
}
return nil
}
// CreateAsset issues a new asset to the world state with given details.
//查看代码中的创建资产函数名称及参数
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if exists {
return fmt.Errorf("the asset %s already exists", id)
}
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// ReadAsset returns the asset stored in the world state with given id.
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("the asset %s does not exist", id)
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
// UpdateAsset updates an existing asset in the world state with provided parameters.
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
// overwriting original asset with new asset
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// DeleteAsset deletes an given asset from the world state.
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
return ctx.GetStub().DelState(id)
}
// AssetExists returns true when asset with given ID exists in world state
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read from world state: %v", err)
}
return assetJSON != nil, nil
}
// TransferAsset updates the owner field of asset with given id in world state, and returns the old owner.
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return "", err
}
oldOwner := asset.Owner
asset.Owner = newOwner
assetJSON, err := json.Marshal(asset)
if err != nil {
return "", err
}
err = ctx.GetStub().PutState(id, assetJSON)
if err != nil {
return "", err
}
return oldOwner, nil
}
// GetAllAssets returns all assets found in world state
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
// range query with empty string for startKey and endKey does an
// open-ended query of all assets in the chaincode namespace.
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var assets []*Asset
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
err = json.Unmarshal(queryResponse.Value, &asset)
if err != nil {
return nil, err
}
assets = append(assets, &asset)
}
return assets, nil
}
asset-transfer-events
connect.go
/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"crypto/x509"
"fmt"
"os"
"path"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
mspID = "Org1MSP"
cryptoPath = "../../test-network/organizations/peerOrganizations/org1.example.com"
certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"
keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"
tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
peerEndpoint = "localhost:7051"
gatewayPeer = "peer0.org1.example.com"
)
// newGrpcConnection creates a gRPC connection to the Gateway server.
func newGrpcConnection() *grpc.ClientConn {
certificate, err := loadCertificate(tlsCertPath)
if err != nil {
panic(err)
}
certPool := x509.NewCertPool()
certPool.AddCert(certificate)
transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
if err != nil {
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
}
return connection
}
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
func newIdentity() *identity.X509Identity {
certificate, err := loadCertificate(certPath)
if err != nil {
panic(err)
}
id, err := identity.NewX509Identity(mspID, certificate)
if err != nil {
panic(err)
}
return id
}
func loadCertificate(filename string) (*x509.Certificate, error) {
certificatePEM, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read certificate file: %w", err)
}
return identity.CertificateFromPEM(certificatePEM)
}
// newSign creates a function that generates a digital signature from a message digest using a private key.
func newSign() identity.Sign {
files, err := os.ReadDir(keyPath)
if err != nil {
panic(fmt.Errorf("failed to read private key directory: %w", err))
}
privateKeyPEM, err := os.ReadFile(path.Join(keyPath, files[0].Name()))
if err != nil {
panic(fmt.Errorf("failed to read private key file: %w", err))
}
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
if err != nil {
panic(err)
}
sign, err := identity.NewPrivateKeySign(privateKey)
if err != nil {
panic(err)
}
return sign
}
app.go
/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
const (
channelName = "mychannel"
chaincodeName = "events"
)
var now = time.Now()
var assetID = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6)
func main() {
clientConnection := newGrpcConnection()
defer clientConnection.Close()
id := newIdentity()
sign := newSign()
gateway, err := client.Connect(
id,
client.WithSign(sign),
client.WithClientConnection(clientConnection),
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),
client.WithSubmitTimeout(5*time.Second),
client.WithCommitStatusTimeout(1*time.Minute),
)
if err != nil {
panic(err)
}
defer gateway.Close()
network := gateway.GetNetwork(channelName)
contract := network.GetContract(chaincodeName)
// Context used for event listening
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Listen for events emitted by subsequent transactions
startChaincodeEventListening(ctx, network)
firstBlockNumber := createAsset(contract)
updateAsset(contract)
transferAsset(contract)
deleteAsset(contract)
// Replay events from the block containing the first transaction
replayChaincodeEvents(ctx, network, firstBlockNumber)
}
//开始监听
func startChaincodeEventListening(ctx context.Context, network *client.Network) {
fmt.Println("\n*** Start chaincode event listening")
events, err := network.ChaincodeEvents(ctx, chaincodeName)
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
go func() {
for event := range events {
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event received: %s - %s\n", event.EventName, asset)
}
}()
}
func formatJSON(data []byte) string {
var result bytes.Buffer
if err := json.Indent(&result, data, "", " "); err != nil {
panic(fmt.Errorf("failed to parse JSON: %w", err))
}
return result.String()
}
func createAsset(contract *client.Contract) uint64 {
fmt.Printf("\n--> Submit transaction: CreateAsset, %s owned by Sam with appraised value 100\n", assetID)
_, commit, err := contract.SubmitAsync("CreateAsset", client.WithArguments(assetID, "blue", "10", "Sam", "100"))
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
status, err := commit.Status()
if err != nil {
panic(fmt.Errorf("failed to get transaction commit status: %w", err))
}
if !status.Successful {
panic(fmt.Errorf("failed to commit transaction with status code %v", status.Code))
}
fmt.Println("\n*** CreateAsset committed successfully")
return status.BlockNumber
}
func updateAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: UpdateAsset, %s update appraised value to 200\n", assetID)
_, err := contract.SubmitTransaction("UpdateAsset", assetID, "blue", "10", "Sam", "200")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** UpdateAsset committed successfully")
}
func transferAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: TransferAsset, %s to Mary\n", assetID)
_, err := contract.SubmitTransaction("TransferAsset", assetID, "Mary")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** TransferAsset committed successfully")
}
func deleteAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: DeleteAsset, %s\n", assetID)
_, err := contract.SubmitTransaction("DeleteAsset", assetID)
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** DeleteAsset committed successfully")
}
func replayChaincodeEvents(ctx context.Context, network *client.Network, startBlock uint64) {
fmt.Println("\n*** Start chaincode event replay")
events, err := network.ChaincodeEvents(ctx, chaincodeName, client.WithStartBlock(startBlock))
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
for {
select {
case <-time.After(10 * time.Second):
panic(errors.New("timeout waiting for event replay"))
case event := <-events:
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event replayed: %s - %s\n", event.EventName, asset)
if event.EventName == "DeleteAsset" {
// Reached the last submitted transaction so return to stop listening for events
return
}
}
}
}
SDK运行
参见SDK运行实战,这是之前实验的内容,在此不多赘述。
https://algernon98.github.io/post/%E5%8C%BA%E5%9D%97%E9%93%BE4
cd .. && go build && ./fabric-go-sdk
>> 开始创建通道......
>>>> 使用每个org的管理员身份更新锚节点配置...
>>>> 使用每个org的管理员身份更新锚节点配置完成
>> 创建通道成功
>> 加入通道......
>> 加入通道成功
>> 开始打包链码......
>> 打包链码成功
>> 开始安装链码......
>> 安装链码成功
>> 组织认可智能合约定义......
>>> chaincode approved by Org1 peers:
peer0.org1.example.com:7051
peer1.org1.example.com:9051
>> 组织认可智能合约定义完成
>> 检查智能合约是否就绪......
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org1MSP:true]}
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org1MSP:true]}
>> 智能合约已经就绪
>> 提交智能合约定义......
>> 智能合约定义提交完成
>> 调用智能合约初始化方法......
>> 完成智能合约初始化
>> 通过链码外部服务设置链码状态......
>> 设置链码状态完成
<--- 添加信息 --->: 18c0c86ce029d7de04461484976c5151992864b52ca28905d0ccf911443fdfcb
<--- 查询信息 --->: 123
---------------------------------
作者: Algernon
本文来自于: https://algernon98.github.io/
博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议
sdk链码分析
chaincode
https://github.com/sxguan/fabric-go-sdk/blob/main/chaincode/chaincode.go
完整代码
package main
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-protos-go/peer"
)
// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}
type outputEvent struct {
EventName string
}
// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
fmt.Printf("init...")
return shim.Success(nil)
}
// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// Extract the function and args from the transaction proposal
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}
// Return the result as success payload
return shim.Success([]byte(result))
}
// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
event := outputEvent{
EventName: "set",
}
payload, err := json.Marshal(event)
if err != nil {
return "", err
}
err = stub.SetEvent("chaincode-event", payload)
return args[1], nil
}
// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
// main function starts up the chaincode in the container during instantiate
func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
逐个分析
现在我们来逐步分析代码:
导入所需包
之后的函数中都有这一部分,如遇到import导入部分则省略。
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-protos-go/peer"
)
integration
https://github.com/sxguan/fabric-go-sdk/blob/main/sdkInit/integration.go
完整代码
package sdkInit
import (
"encoding/hex"
"fmt"
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
"github.com/hyperledger/fabric-sdk-go/pkg/client/event"
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/status"
contextAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
fabAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
contextImpl "github.com/hyperledger/fabric-sdk-go/pkg/context"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
"log"
)
func DiscoverLocalPeers(ctxProvider contextAPI.ClientProvider, expectedPeers int) ([]fabAPI.Peer, error) {
ctx, err := contextImpl.NewLocal(ctxProvider)
if err != nil {
return nil, fmt.Errorf("error creating local context: %v", err)
}
discoveredPeers, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
peers, serviceErr := ctx.LocalDiscoveryService().GetPeers()
if serviceErr != nil {
return nil, fmt.Errorf("getting peers for MSP [%s] error: %v", ctx.Identifier().MSPID, serviceErr)
}
if len(peers) < expectedPeers {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("Expecting %d peers but got %d", expectedPeers, len(peers)), nil)
}
return peers, nil
},
)
if err != nil {
return nil, err
}
return discoveredPeers.([]fabAPI.Peer), nil
}
func (t *SdkEnvInfo) InitService(chaincodeID, channelID string, org *OrgInfo, sdk *fabsdk.FabricSDK) error {
handler := &SdkEnvInfo{
ChaincodeID: chaincodeID,
}
//prepare channel client context using client context
clientChannelContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org.OrgUser), fabsdk.WithOrg(org.OrgName))
// Channel client is used to query and execute transactions (Org1 is default org)
var err error
t.ChClient, err = channel.New(clientChannelContext)
if err != nil {
return err
}
t.EvClient, err = event.New(clientChannelContext, event.WithBlockEvents())
if err != nil {
return err
}
handler.ChClient = t.ChClient
handler.EvClient = t.EvClient
return nil
}
func regitserEvent(client *event.Client, chaincodeID string) (fabAPI.Registration, <-chan *fabAPI.CCEvent) {
eventName := "chaincode-event"
reg, notifier, err := client.RegisterChaincodeEvent(chaincodeID, eventName)
if err != nil {
fmt.Println("注册链码事件失败: %s", err)
}
return reg, notifier
}
func ChainCodeEventListener(c *event.Client, ccID string) fabAPI.Registration {
reg, notifier := regitserEvent(c, ccID)
// consume event
go func() {
for e := range notifier {
log.Printf("Receive cc event, ccid: %v \neventName: %v\n"+
"payload: %v \ntxid: %v \nblock: %v \nsourceURL: %v\n",
e.ChaincodeID, e.EventName, string(e.Payload), e.TxID, e.BlockNumber, e.SourceURL)
}
}()
return reg
}
func TxListener(c *event.Client, txIDCh chan string) {
log.Println("Transaction listener start")
defer log.Println("Transaction listener exit")
for id := range txIDCh {
// Register monitor transaction event
log.Printf("Register transaction event for: %v", id)
txReg, txCh, err := c.RegisterTxStatusEvent(id)
if err != nil {
log.Printf("Register transaction event error: %v", err)
continue
}
defer c.Unregister(txReg)
// Receive transaction event
go func() {
for e := range txCh {
log.Printf("Receive transaction event: txid: %v, "+
"validation code: %v, block number: %v",
e.TxID,
e.TxValidationCode,
e.BlockNumber)
}
}()
}
}
func BlockListener(ec *event.Client) fabAPI.Registration {
// Register monitor block event
beReg, beCh, err := ec.RegisterBlockEvent()
if err != nil {
log.Printf("Register block event error: %v", err)
}
log.Println("Registered block event")
// Receive block event
go func() {
for e := range beCh {
log.Printf("Receive block event:\nSourceURL: %v\nNumber: %v\nHash"+
": %v\nPreviousHash: %v\n\n",
e.SourceURL,
e.Block.Header.Number,
hex.EncodeToString(e.Block.Header.DataHash),
hex.EncodeToString(e.Block.Header.PreviousHash))
}
}()
return beReg
}
逐个分析
查找本地结点
func DiscoverLocalPeers(ctxProvider contextAPI.ClientProvider, expectedPeers int) ([]fabAPI.Peer, error) {
ctx, err := contextImpl.NewLocal(ctxProvider)
if err != nil {
return nil, fmt.Errorf("error creating local context: %v", err)
}
discoveredPeers, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
peers, serviceErr := ctx.LocalDiscoveryService().GetPeers()
if serviceErr != nil {
return nil, fmt.Errorf("getting peers for MSP [%s] error: %v", ctx.Identifier().MSPID, serviceErr)
}
if len(peers) < expectedPeers {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("Expecting %d peers but got %d", expectedPeers, len(peers)), nil)
}
return peers, nil
},
)
if err != nil {
return nil, err
}
return discoveredPeers.([]fabAPI.Peer), nil
}
main
完整代码
package main
import (
"fabric-go-sdk/sdkInit"
"fmt"
"os"
"time"
)
const (
cc_name = "simplecc"
cc_version = "1.0.0"
)
var App sdkInit.Application
func main() {
// init orgs information 初始化组织信息
orgs := []*sdkInit.OrgInfo{
{
OrgAdminUser: "Admin",
OrgName: "Org1",
OrgMspId: "Org1MSP",
OrgUser: "User1",
OrgPeerNum: 2,
OrgAnchorFile: "/root/go/src/fabric-go-sdk/fixtures/channel-artifacts/Org1MSPanchors.tx",
},
}
// init sdk env info 初始化sdk相关信息
info := sdkInit.SdkEnvInfo{
ChannelID: "mychannel",
ChannelConfig: "/root/go/src/fabric-go-sdk/fixtures/channel-artifacts/channel.tx",
Orgs: orgs,
OrdererAdminUser: "Admin",
OrdererOrgName: "OrdererOrg",
OrdererEndpoint: "orderer.example.com",
ChaincodeID: cc_name,
ChaincodePath: "/root/go/src/fabric-go-sdk/chaincode/",
ChaincodeVersion: cc_version,
}
// sdk setup 调用setup方法将sdk初始化
sdk, err := sdkInit.Setup("config.yaml", &info)
if err != nil {
fmt.Println(">> SDK setup error:", err)
os.Exit(-1)
}
// create channel and join 调用CreateAndJoinChannel方法,创建并加入通道
if err := sdkInit.CreateAndJoinChannel(&info); err != nil {
fmt.Println(">> Create channel and join error:", err)
os.Exit(-1)
}
// create chaincode lifecycle 调用CreateCCLifecycle方法实现链码生命周期
if err := sdkInit.CreateCCLifecycle(&info, 1, false, sdk); err != nil {
fmt.Println(">> create chaincode lifecycle error: %v", err)
os.Exit(-1)
}
// invoke chaincode set status
fmt.Println(">> 通过链码外部服务设置链码状态......")
if err := info.InitService(info.ChaincodeID, info.ChannelID, info.Orgs[0], sdk); err != nil {
fmt.Println("InitService successful")
os.Exit(-1)
}
App = sdkInit.Application{
SdkEnvInfo: &info,
}
fmt.Println(">> 设置链码状态完成")
defer info.EvClient.Unregister(sdkInit.BlockListener(info.EvClient))
defer info.EvClient.Unregister(sdkInit.ChainCodeEventListener(info.EvClient, info.ChaincodeID))
a := []string{"set", "ID1", "123"}
ret, err := App.Set(a)
if err != nil {
fmt.Println(err)
}
fmt.Println("<--- 添加信息 --->:", ret)
a = []string{"set", "ID2", "456"}
ret, err = App.Set(a)
if err != nil {
fmt.Println(err)
}
fmt.Println("<--- 添加信息 --->:", ret)
a = []string{"set", "ID3", "789"}
ret, err = App.Set(a)
if err != nil {
fmt.Println(err)
}
fmt.Println("<--- 添加信息 --->:", ret)
a = []string{"get", "ID3"}
response, err := App.Get(a)
if err != nil {
fmt.Println(err)
}
fmt.Println("<--- 查询信息 --->:", response)
time.Sleep(time.Second * 10)
}
逐个分析
定义链码名称与版本
const (
cc_name = "simplecc"
cc_version = "1.0.0"
)
get
完整代码
package sdkInit
import (
"fmt"
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
)
func (t *Application) Get(args []string) (string, error) {
response, err := t.SdkEnvInfo.ChClient.Query(channel.Request{ChaincodeID: t.SdkEnvInfo.ChaincodeID, Fcn: args[0], Args: [][]byte{[]byte(args[1])}})
if err != nil {
return "", fmt.Errorf("failed to query: %v", err)
}
return string(response.Payload), nil
}
sdkInfo
https://github.com/sxguan/fabric-go-sdk/blob/main/sdkInit/sdkInfo.go
完整代码
注释写在代码里了,就不单拿出来讲了。
package sdkInit
import (
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
"github.com/hyperledger/fabric-sdk-go/pkg/client/event"
mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
contextAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
)
type OrgInfo struct {
OrgAdminUser string // like "Admin"
OrgName string // like "Org1"
OrgMspId string // like "Org1MSP"
OrgUser string // like "User1"
orgMspClient *mspclient.Client
OrgAdminClientContext *contextAPI.ClientProvider
OrgResMgmt *resmgmt.Client
OrgPeerNum int
//Peers []*fab.Peer
OrgAnchorFile string // like ./channel-artifacts/Org2MSPanchors.tx
}
type SdkEnvInfo struct {
// 通道信息
ChannelID string // like "simplecc"
ChannelConfig string // like os.Getenv("GOPATH") + "/src/github.com/hyperledger/fabric-samples/test-network/channel-artifacts/testchannel.tx"
// 组织信息
Orgs []*OrgInfo
// 排序服务节点信息
OrdererAdminUser string // like "Admin"
OrdererOrgName string // like "OrdererOrg"
OrdererEndpoint string
OrdererClientContext *contextAPI.ClientProvider
// 链码信息
ChaincodeID string
ChaincodeGoPath string
ChaincodePath string
ChaincodeVersion string
ChClient *channel.Client
EvClient *event.Client
}
type Application struct {
SdkEnvInfo *SdkEnvInfo
}
sdkSetting
完整代码
package sdkInit
import (
"fmt"
mb "github.com/hyperledger/fabric-protos-go/msp"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/status"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
lcpackager "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/lifecycle"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/policydsl"
"strings"
)
func Setup(configFile string, info *SdkEnvInfo) (*fabsdk.FabricSDK, error) {
// Create SDK setup for the integration tests
var err error
sdk, err := fabsdk.New(config.FromFile(configFile))
if err != nil {
return nil, err
}
// 为组织获得Client句柄和Context信息
for _, org := range info.Orgs {
org.orgMspClient, err = mspclient.New(sdk.Context(), mspclient.WithOrg(org.OrgName))
if err != nil {
return nil, err
}
orgContext := sdk.Context(fabsdk.WithUser(org.OrgAdminUser), fabsdk.WithOrg(org.OrgName))
org.OrgAdminClientContext = &orgContext
// New returns a resource management client instance.
resMgmtClient, err := resmgmt.New(orgContext)
if err != nil {
return nil, fmt.Errorf("根据指定的资源管理客户端Context创建通道管理客户端失败: %v", err)
}
org.OrgResMgmt = resMgmtClient
}
// 为Orderer获得Context信息
ordererClientContext := sdk.Context(fabsdk.WithUser(info.OrdererAdminUser), fabsdk.WithOrg(info.OrdererOrgName))
info.OrdererClientContext = &ordererClientContext
return sdk, nil
}
func CreateAndJoinChannel(info *SdkEnvInfo) error {
fmt.Println(">> 开始创建通道......")
if len(info.Orgs) == 0 {
return fmt.Errorf("通道组织不能为空,请提供组织信息")
}
// 获得所有组织的签名信息
signIds := []msp.SigningIdentity{}
for _, org := range info.Orgs {
// Get signing identity that is used to sign create channel request
orgSignId, err := org.orgMspClient.GetSigningIdentity(org.OrgAdminUser)
if err != nil {
return fmt.Errorf("GetSigningIdentity error: %v", err)
}
signIds = append(signIds, orgSignId)
}
// 创建通道
if err := createChannel(signIds, info); err != nil {
return fmt.Errorf("Create channel error: %v", err)
}
fmt.Println(">> 创建通道成功")
fmt.Println(">> 加入通道......")
for _, org := range info.Orgs {
// 加入通道
// Org peers join channel
if err := org.OrgResMgmt.JoinChannel(info.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
return fmt.Errorf("%s peers failed to JoinChannel: %v", org.OrgName, err)
}
}
fmt.Println(">> 加入通道成功")
return nil
}
func createChannel(signIDs []msp.SigningIdentity, info *SdkEnvInfo) error {
// Channel management client is responsible for managing channels (create/update channel)
chMgmtClient, err := resmgmt.New(*info.OrdererClientContext)
if err != nil {
return fmt.Errorf("Channel management client create error: %v", err)
}
// create a channel for orgchannel.tx
req := resmgmt.SaveChannelRequest{ChannelID: info.ChannelID,
ChannelConfigPath: info.ChannelConfig,
SigningIdentities: signIDs}
if _, err := chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
return fmt.Errorf("error should be nil for SaveChannel of orgchannel: %v", err)
}
fmt.Println(">>>> 使用每个org的管理员身份更新锚节点配置...")
//do the same get ch client and create channel for each anchor peer as well (first for Org1MSP)
for i, org := range info.Orgs {
req = resmgmt.SaveChannelRequest{ChannelID: info.ChannelID,
ChannelConfigPath: org.OrgAnchorFile,
SigningIdentities: []msp.SigningIdentity{signIDs[i]}}
if _, err = org.OrgResMgmt.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
return fmt.Errorf("SaveChannel for anchor org %s error: %v", org.OrgName, err)
}
}
fmt.Println(">>>> 使用每个org的管理员身份更新锚节点配置完成")
//integration.WaitForOrdererConfigUpdate(t, configQueryClient, mc.channelID, false, lastConfigBlock)
return nil
}
func CreateCCLifecycle(info *SdkEnvInfo, sequence int64, upgrade bool, sdk *fabsdk.FabricSDK) error {
if len(info.Orgs) == 0 {
return fmt.Errorf("the number of organization should not be zero.")
}
// Package cc
fmt.Println(">> 开始打包链码......")
label, ccPkg, err := packageCC(info.ChaincodeID, info.ChaincodeVersion, info.ChaincodePath)
if err != nil {
return fmt.Errorf("pakcagecc error: %v", err)
}
packageID := lcpackager.ComputePackageID(label, ccPkg)
fmt.Println(">> 打包链码成功")
// Install cc
fmt.Println(">> 开始安装链码......")
if err := installCC(label, ccPkg, info.Orgs); err != nil {
return fmt.Errorf("installCC error: %v", err)
}
// Get installed cc package
if err := getInstalledCCPackage(packageID, info.Orgs[0]); err != nil {
return fmt.Errorf("getInstalledCCPackage error: %v", err)
}
// Query installed cc
if err := queryInstalled(packageID, info.Orgs[0]); err != nil {
return fmt.Errorf("queryInstalled error: %v", err)
}
fmt.Println(">> 安装链码成功")
// Approve cc
fmt.Println(">> 组织认可智能合约定义......")
if err := approveCC(packageID, info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs, info.OrdererEndpoint); err != nil {
return fmt.Errorf("approveCC error: %v", err)
}
// Query approve cc
if err:=queryApprovedCC(info.ChaincodeID, sequence, info.ChannelID, info.Orgs);err!=nil{
return fmt.Errorf("queryApprovedCC error: %v", err)
}
fmt.Println(">> 组织认可智能合约定义完成")
// Check commit readiness
fmt.Println(">> 检查智能合约是否就绪......")
if err:=checkCCCommitReadiness(packageID, info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs); err!=nil{
return fmt.Errorf("checkCCCommitReadiness error: %v", err)
}
fmt.Println(">> 智能合约已经就绪")
// Commit cc
fmt.Println(">> 提交智能合约定义......")
if err:=commitCC(info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs, info.OrdererEndpoint);err!=nil{
return fmt.Errorf("commitCC error: %v", err)
}
// Query committed cc
if err:=queryCommittedCC(info.ChaincodeID, info.ChannelID, sequence, info.Orgs); err!=nil{
return fmt.Errorf("queryCommittedCC error: %v", err)
}
fmt.Println(">> 智能合约定义提交完成")
// Init cc
fmt.Println(">> 调用智能合约初始化方法......")
if err:=initCC(info.ChaincodeID, upgrade, info.ChannelID, info.Orgs[0], sdk); err!=nil{
return fmt.Errorf("initCC error: %v", err)
}
fmt.Println(">> 完成智能合约初始化")
return nil
}
func packageCC(ccName, ccVersion, ccpath string) (string, []byte, error) {
label := ccName + "_" + ccVersion
desc := &lcpackager.Descriptor{
Path: ccpath,
Type: pb.ChaincodeSpec_GOLANG,
Label: label,
}
ccPkg, err := lcpackager.NewCCPackage(desc)
if err != nil {
return "", nil, fmt.Errorf("Package chaincode source error: %v", err)
}
return desc.Label, ccPkg, nil
}
func installCC(label string, ccPkg []byte, orgs []*OrgInfo) error {
installCCReq := resmgmt.LifecycleInstallCCRequest{
Label: label,
Package: ccPkg,
}
packageID := lcpackager.ComputePackageID(installCCReq.Label, installCCReq.Package)
for _, org := range orgs {
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err != nil {
fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
if flag, _ := checkInstalled(packageID, orgPeers[0], org.OrgResMgmt); flag == false {
if _, err := org.OrgResMgmt.LifecycleInstallCC(installCCReq, resmgmt.WithTargets(orgPeers...), resmgmt.WithRetry(retry.DefaultResMgmtOpts)); err != nil {
return fmt.Errorf("LifecycleInstallCC error: %v", err)
}
}
}
return nil
}
func getInstalledCCPackage(packageID string, org *OrgInfo) error {
// use org1
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, 1)
if err != nil {
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
if _, err := org.OrgResMgmt.LifecycleGetInstalledCCPackage(packageID, resmgmt.WithTargets([]fab.Peer{orgPeers[0]}...)); err != nil {
return fmt.Errorf("LifecycleGetInstalledCCPackage error: %v", err)
}
return nil
}
func queryInstalled(packageID string, org *OrgInfo) error {
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, 1)
if err != nil {
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
resp1, err := org.OrgResMgmt.LifecycleQueryInstalledCC(resmgmt.WithTargets([]fab.Peer{orgPeers[0]}...))
if err != nil {
return fmt.Errorf("LifecycleQueryInstalledCC error: %v", err)
}
packageID1 := ""
for _, t := range resp1 {
if t.PackageID == packageID {
packageID1 = t.PackageID
}
}
if !strings.EqualFold(packageID, packageID1) {
return fmt.Errorf("check package id error")
}
return nil
}
func checkInstalled(packageID string, peer fab.Peer, client *resmgmt.Client) (bool, error) {
flag := false
resp1, err := client.LifecycleQueryInstalledCC(resmgmt.WithTargets(peer))
if err != nil {
return flag, fmt.Errorf("LifecycleQueryInstalledCC error: %v", err)
}
for _, t := range resp1 {
if t.PackageID == packageID {
flag = true
}
}
return flag, nil
}
func approveCC(packageID string, ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo, ordererEndpoint string) error {
mspIDs := []string{}
for _, org := range orgs {
mspIDs = append(mspIDs, org.OrgMspId)
}
ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIDs)), mb.MSPRole_MEMBER, mspIDs)
approveCCReq := resmgmt.LifecycleApproveCCRequest{
Name: ccName,
Version: ccVersion,
PackageID: packageID,
Sequence: sequence,
EndorsementPlugin: "escc",
ValidationPlugin: "vscc",
SignaturePolicy: ccPolicy,
InitRequired: true,
}
for _, org := range orgs{
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
fmt.Printf(">>> chaincode approved by %s peers:\n", org.OrgName)
for _, p := range orgPeers {
fmt.Printf(" %s\n", p.URL())
}
if err!=nil{
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
if _, err := org.OrgResMgmt.LifecycleApproveCC(channelID, approveCCReq, resmgmt.WithTargets(orgPeers...), resmgmt.WithOrdererEndpoint(ordererEndpoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts));err != nil {
fmt.Errorf("LifecycleApproveCC error: %v", err)
}
}
return nil
}
func queryApprovedCC(ccName string, sequence int64, channelID string, orgs []*OrgInfo) error {
queryApprovedCCReq := resmgmt.LifecycleQueryApprovedCCRequest{
Name: ccName,
Sequence: sequence,
}
for _, org := range orgs{
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err!=nil{
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
// Query approve cc
for _, p := range orgPeers {
resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
resp1, err := org.OrgResMgmt.LifecycleQueryApprovedCC(channelID, queryApprovedCCReq, resmgmt.WithTargets(p))
if err != nil {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryApprovedCC returned error: %v", err), nil)
}
return resp1, err
},
)
if err != nil {
return fmt.Errorf("Org %s Peer %s NewInvoker error: %v", org.OrgName, p.URL(), err)
}
if resp==nil{
return fmt.Errorf("Org %s Peer %s Got nil invoker", org.OrgName, p.URL())
}
}
}
return nil
}
func checkCCCommitReadiness(packageID string, ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo) error {
mspIds := []string{}
for _, org := range orgs {
mspIds = append(mspIds, org.OrgMspId)
}
ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIds)), mb.MSPRole_MEMBER, mspIds)
req := resmgmt.LifecycleCheckCCCommitReadinessRequest{
Name: ccName,
Version: ccVersion,
//PackageID: packageID,
EndorsementPlugin: "escc",
ValidationPlugin: "vscc",
SignaturePolicy: ccPolicy,
Sequence: sequence,
InitRequired: true,
}
for _, org := range orgs{
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err!=nil{
fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
for _, p := range orgPeers {
resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
resp1, err := org.OrgResMgmt.LifecycleCheckCCCommitReadiness(channelID, req, resmgmt.WithTargets(p))
fmt.Printf("LifecycleCheckCCCommitReadiness cc = %v, = %v\n", ccName, resp1)
if err != nil {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned error: %v", err), nil)
}
flag := true
for _, r := range resp1.Approvals {
flag = flag && r
}
if !flag {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned : %v", resp1), nil)
}
return resp1, err
},
)
if err != nil {
return fmt.Errorf("NewInvoker error: %v", err)
}
if resp==nil{
return fmt.Errorf("Got nill invoker response")
}
}
}
return nil
}
func commitCC(ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo, ordererEndpoint string) error{
mspIDs := []string{}
for _, org := range orgs {
mspIDs = append(mspIDs, org.OrgMspId)
}
ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIDs)), mb.MSPRole_MEMBER, mspIDs)
req := resmgmt.LifecycleCommitCCRequest{
Name: ccName,
Version: ccVersion,
Sequence: sequence,
EndorsementPlugin: "escc",
ValidationPlugin: "vscc",
SignaturePolicy: ccPolicy,
InitRequired: true,
}
_, err := orgs[0].OrgResMgmt.LifecycleCommitCC(channelID, req, resmgmt.WithOrdererEndpoint(ordererEndpoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts))
if err != nil {
return fmt.Errorf("LifecycleCommitCC error: %v", err)
}
return nil
}
func queryCommittedCC( ccName string, channelID string, sequence int64, orgs []*OrgInfo) error {
req := resmgmt.LifecycleQueryCommittedCCRequest{
Name: ccName,
}
for _, org := range orgs {
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err!=nil{
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
for _, p := range orgPeers {
resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
resp1, err := org.OrgResMgmt.LifecycleQueryCommittedCC(channelID, req, resmgmt.WithTargets(p))
if err != nil {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned error: %v", err), nil)
}
flag := false
for _, r := range resp1 {
if r.Name == ccName && r.Sequence == sequence {
flag = true
break
}
}
if !flag {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned : %v", resp1), nil)
}
return resp1, err
},
)
if err != nil {
return fmt.Errorf("NewInvoker error: %v", err)
}
if resp==nil{
return fmt.Errorf("Got nil invoker response")
}
}
}
return nil
}
func initCC(ccName string, upgrade bool, channelID string, org *OrgInfo, sdk *fabsdk.FabricSDK) error {
//prepare channel client context using client context
clientChannelContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org.OrgUser), fabsdk.WithOrg(org.OrgName))
// Channel client is used to query and execute transactions (Org1 is default org)
client, err := channel.New(clientChannelContext)
if err != nil {
return fmt.Errorf("Failed to create new channel client: %s", err)
}
// init
_, err = client.Execute(channel.Request{ChaincodeID: ccName, Fcn: "init", Args: nil, IsInit: true},
channel.WithRetry(retry.DefaultChannelOpts))
if err != nil {
return fmt.Errorf("Failed to init: %s", err)
}
return nil
}
逐个分析
打包链码
在被安装到peer节点之前,链码需要被打包进一个tar文件。当你创建一个链码包的时候,你需要提交一个用来创建简明易读的包描述的链码包标签。
使用fabric-go-sdk将会自动以这个格式来创建文件。
链码需要被打包进一个以 .tar.gz 文件扩展名结尾的tar文件。
func packageCC(ccName, ccVersion, ccpath string) (string, []byte, error) {
label := ccName + "_" + ccVersion // 链码的标签
desc := &lcpackager.Descriptor{ // 使用lcpackager包中的Descriptor结构体添加描述信息
Path: ccpath, //链码路径
Type: pb.ChaincodeSpec_GOLANG, //链码的语言
Label: label, // 链码的标签
}
ccPkg, err := lcpackager.NewCCPackage(desc) // 使用lcpackager包中NewCCPackage方法对链码进行打包
if err != nil {
return "", nil, fmt.Errorf("Package chaincode source error: %v", err)
}
return desc.Label, ccPkg, nil
}
安装链码
你需要在每个要执行和背书交易的peer节点上安装链码包。使用SDK时,你需要以 Peer Administrator(peer所在组织的管理员) 的身份来完成这步。链码安装后,你的 peer 节点会构建链码,并且如果你的链码有问题,会返回一个构建错误。建议每个组织只打包链码一次,然后安装相同的包在属于他们组织的每一个peer节点上。如果某个通道希望确保每个组织都运行同样的链码,某一个组织可以打包链码并通过带外数据(不通过链上)把它发送给其他通道成员.
通过指令成功安装链码后会返回链码包标识符,它是包标签和包哈希值的结合。这个包标识符用来关联安装在你的peer节点上的链码包已被批准的链码。为下一步的操作保存这个标识符。你也可以查询安装在peer节点上的包来查看包标识符。
func installCC(label string, ccPkg []byte, orgs []*OrgInfo) error {
installCCReq := resmgmt.LifecycleInstallCCRequest{
Label: label,
Package: ccPkg,
}
// 使用lcpackager中的ComputePackageID方法查询并返回链码的packageID
packageID := lcpackager.ComputePackageID(installCCReq.Label, installCCReq.Package)
for _, org := range orgs {
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err != nil {
fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
// 检查是否安装链码,如果未安装则继续执行
if flag, _ := checkInstalled(packageID, orgPeers[0], org.OrgResMgmt); flag == false {
// 使用resmgmt中的LifecycleInstallCC方法安装链码,其中WithRetry方法为安装不成功时重试安装,DefaultResMgmtOpts为默认的重试安装规则
if _, err := org.OrgResMgmt.LifecycleInstallCC(installCCReq, resmgmt.WithTargets(orgPeers...), resmgmt.WithRetry(retry.DefaultResMgmtOpts)); err != nil {
return fmt.Errorf("LifecycleInstallCC error: %v", err)
}
}
}
return nil
}
//检查是否安装过链码
func checkInstalled(packageID string, peer fab.Peer, client *resmgmt.Client) (bool, error) {
flag := false
resp1, err := client.LifecycleQueryInstalledCC(resmgmt.WithTargets(peer))
if err != nil {
return flag, fmt.Errorf("LifecycleQueryInstalledCC error: %v", err)
}
for _, t := range resp1 {
if t.PackageID == packageID {
flag = true
}
}
return flag, nil
}
获取已安装链码包
func getInstalledCCPackage(packageID string, org *OrgInfo) error {
// use org1
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, 1)
if err != nil {
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
// 使用resmgmt中的LifecycleGetInstalledCCPackage方法,对于给定的packageID检索已安装的链码包
if _, err := org.OrgResMgmt.LifecycleGetInstalledCCPackage(packageID, resmgmt.WithTargets([]fab.Peer{orgPeers[0]}...)); err != nil {
return fmt.Errorf("LifecycleGetInstalledCCPackage error: %v", err)
}
return nil
}
查询安装
func queryInstalled(packageID string, org *OrgInfo) error {
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, 1)
if err != nil {
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
// 使用resmgmt中的LifecycleQueryInstalledCC方法,返回在指定节点上安装的链码packageID
resp1, err := org.OrgResMgmt.LifecycleQueryInstalledCC(resmgmt.WithTargets([]fab.Peer{orgPeers[0]}...))
if err != nil {
return fmt.Errorf("LifecycleQueryInstalledCC error: %v", err)
}
packageID1 := ""
for _, t := range resp1 {
if t.PackageID == packageID {
packageID1 = t.PackageID
}
}
// 查询的packageID与给定的packageID不一致则报错
if !strings.EqualFold(packageID, packageID1) {
return fmt.Errorf("check package id error")
}
return nil
}
各组织批准链码
func approveCC(packageID string, ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo, ordererEndpoint string) error {
mspIDs := []string{}
// 获取各个组织的mspID
for _, org := range orgs {
mspIDs = append(mspIDs, org.OrgMspId)
}
// 签名策略,由所有给出的mspid签名
ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIDs)), mb.MSPRole_MEMBER, mspIDs)
// approve所需参数
approveCCReq := resmgmt.LifecycleApproveCCRequest{
Name: ccName, // 链码名
Version: ccVersion, // 版本
PackageID: packageID, // 链码包id
Sequence: sequence, // 序列号
EndorsementPlugin: "escc", // 系统内置链码escc
ValidationPlugin: "vscc", // 系统内置链码vscc
SignaturePolicy: ccPolicy, // 组织签名策略
InitRequired: true, // 是否初始化
}
for _, org := range orgs{
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
fmt.Printf(">>> chaincode approved by %s peers:\n", org.OrgName)
for _, p := range orgPeers {
fmt.Printf(" %s\n", p.URL())
}
if err!=nil{
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
// 使用resmgmt中的LifecycleApproveCC方法为组织批准链码
if _, err := org.OrgResMgmt.LifecycleApproveCC(channelID, approveCCReq, resmgmt.WithTargets(orgPeers...), resmgmt.WithOrdererEndpoint(ordererEndpoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts));err != nil {
fmt.Errorf("LifecycleApproveCC error: %v", err)
}
}
return nil
}
查询已批准的链码
func queryApprovedCC(ccName string, sequence int64, channelID string, orgs []*OrgInfo) error {
// queryApproved所需参数
queryApprovedCCReq := resmgmt.LifecycleQueryApprovedCCRequest{
Name: ccName, // 链码名称
Sequence: sequence,// 序列号
}
for _, org := range orgs{
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err!=nil{
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
// Query approve cc
for _, p := range orgPeers {
resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
// LifecycleQueryApprovedCC返回有关已批准的链码定义的信息
resp1, err := org.OrgResMgmt.LifecycleQueryApprovedCC(channelID, queryApprovedCCReq, resmgmt.WithTargets(p))
if err != nil {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryApprovedCC returned error: %v", err), nil)
}
return resp1, err
},
)
if err != nil {
return fmt.Errorf("Org %s Peer %s NewInvoker error: %v", org.OrgName, p.URL(), err)
}
if resp==nil{
return fmt.Errorf("Org %s Peer %s Got nil invoker", org.OrgName, p.URL())
}
}
}
return nil
}
检查智能合约是否就绪
func checkCCCommitReadiness(packageID string, ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo) error {
mspIds := []string{}
for _, org := range orgs {
mspIds = append(mspIds, org.OrgMspId)
}
// 签名策略,由所有给出的mspid签名
ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIds)), mb.MSPRole_MEMBER, mspIds)
// 所需所有参数,同上
req := resmgmt.LifecycleCheckCCCommitReadinessRequest{
Name: ccName,
Version: ccVersion,
//PackageID: packageID,
EndorsementPlugin: "escc",
ValidationPlugin: "vscc",
SignaturePolicy: ccPolicy,
Sequence: sequence,
InitRequired: true,
}
for _, org := range orgs{
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err!=nil{
fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
for _, p := range orgPeers {
resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
// 使用resmgmt中的LifecycleCheckCCCommitReadiness方法检查链代码的“提交准备”,返回组织批准。
resp1, err := org.OrgResMgmt.LifecycleCheckCCCommitReadiness(channelID, req, resmgmt.WithTargets(p))
fmt.Printf("LifecycleCheckCCCommitReadiness cc = %v, = %v\n", ccName, resp1)
if err != nil {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned error: %v", err), nil)
}
flag := true
for _, r := range resp1.Approvals {
flag = flag && r
}
if !flag {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned : %v", resp1), nil)
}
return resp1, err
},
)
if err != nil {
return fmt.Errorf("NewInvoker error: %v", err)
}
if resp==nil{
return fmt.Errorf("Got nill invoker response")
}
}
}
return nil
}
提交智能合约定义
一旦足够多的通道成员同意一个链码定义,某个组织能够提交定义到通道。你可以用上述 checkcommitreadiness 方法在将链码定义提交到通道之前,基于哪个通道成员已经批准了该定义,来检查提交链码定义是否应该成功。(根据通道成员同意的状况,来判断提交是否可能成功)。提交交易请求首先发送给通道成员的 peer节点,peer节点会查询链码定义被他们组织同意的状况,并且为定义背书如果所在组织已经同意了。交易然后提交给排序服务,排序服务会把链码定义提交给通道。提交定义交易需要以 Organization Administrator 身份来提交。
链码在被成功提交到通道之前,需要被同意的组织的数量是通过 Channel/Application/LifecycleEndorsement 策略来管理的。默认情况下,这个策略需要通道中大多数的组织来给交易背书。生命周期背书策略不同于链码背书策略。例如,尽管一个链码背书策略只需要一个或两个组织的签名,根据默认策略大多数的通道成员仍然需要批准链码定义。当提交一个通道定义,你需要面向足够多的 peer 组织,以确保你的生命周期背书策略被满足。
你也可以设置 Channel/Application/LifecycleEndorsement 策略为一个签名策略并且明确指明通道上可以批准链码定义的组织集合。这允许你创建一个其中大多数组织作为链码管理者并且治理通道业务逻辑的通道。如果你的通道有大量的Idemix(身份混合,实现零知识证明)组织,你也可以用一个签名策略(策略只需要一个签名),因为这些组织不能批准链码定义或者为链码背书并且可能阻碍通道达成大多数成员同意的结果。
一个组织在不安装链码包的条件下能够批准链码定义。如果一个组织不需要使用链码,他们可以在没有包身份的情况下批准一个链码定义来确保生命周期背书策略被满足。
在链码定义已经提交到通道上后,链码容器会在所有的链码安装到的 peer 节点上启动,来允许通道成员开始使用链码。可能会花费几分钟的时间来启动链码容器。你可以用链码定义来要求调用 Init 方法初始化链码。如果 Init 方法调用是需要的,链码的第一个调用必须是调用 Init 方法。Init 方法的调用服从于链码的背书策略。
func commitCC(ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo, ordererEndpoint string) error{
mspIDs := []string{}
for _, org := range orgs {
mspIDs = append(mspIDs, org.OrgMspId)
}
ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIDs)), mb.MSPRole_MEMBER, mspIDs)
// commit所需参数信息,内容同上
req := resmgmt.LifecycleCommitCCRequest{
Name: ccName,
Version: ccVersion,
Sequence: sequence,
EndorsementPlugin: "escc",
ValidationPlugin: "vscc",
SignaturePolicy: ccPolicy,
InitRequired: true,
}
// LifecycleCommitCC将链代码提交给给定的通道
_, err := orgs[0].OrgResMgmt.LifecycleCommitCC(channelID, req, resmgmt.WithOrdererEndpoint(ordererEndpoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts))
if err != nil {
return fmt.Errorf("LifecycleCommitCC error: %v", err)
}
return nil
}
查询已提交的智能合约定义
func queryCommittedCC( ccName string, channelID string, sequence int64, orgs []*OrgInfo) error {
req := resmgmt.LifecycleQueryCommittedCCRequest{
Name: ccName,
}
for _, org := range orgs {
orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
if err!=nil{
return fmt.Errorf("DiscoverLocalPeers error: %v", err)
}
for _, p := range orgPeers {
resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
func() (interface{}, error) {
// LifecycleQueryCommittedCC查询给定通道上提交的链码
resp1, err := org.OrgResMgmt.LifecycleQueryCommittedCC(channelID, req, resmgmt.WithTargets(p))
if err != nil {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned error: %v", err), nil)
}
flag := false
for _, r := range resp1 {
if r.Name == ccName && r.Sequence == sequence {
flag = true
break
}
}
if !flag {
return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned : %v", resp1), nil)
}
return resp1, err
},
)
if err != nil {
return fmt.Errorf("NewInvoker error: %v", err)
}
if resp==nil{
return fmt.Errorf("Got nil invoker response")
}
}
}
return nil
}
智能合约初始化
func initCC(ccName string, upgrade bool, channelID string, org *OrgInfo, sdk *fabsdk.FabricSDK) error {
// 准备通道客户端上下文
clientChannelContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org.OrgUser), fabsdk.WithOrg(org.OrgName))
// 通道客户端用于查询执行交易
client, err := channel.New(clientChannelContext)
if err != nil {
return fmt.Errorf("Failed to create new channel client: %s", err)
}
// 调用链码初始化
_, err = client.Execute(channel.Request{ChaincodeID: ccName, Fcn: "init", Args: nil, IsInit: true},
channel.WithRetry(retry.DefaultChannelOpts))
if err != nil {
return fmt.Errorf("Failed to init: %s", err)
}
return nil
}
调用创建通道函数及加入通道
func CreateAndJoinChannel(info *SdkEnvInfo) error {
fmt.Println(">> 开始创建通道......")
if len(info.Orgs) == 0 {
return fmt.Errorf("通道组织不能为空,请提供组织信息")
}
// 获得所有组织的签名信息
signIds := []msp.SigningIdentity{}
for _, org := range info.Orgs {
// Get signing identity that is used to sign create channel request
orgSignId, err := org.orgMspClient.GetSigningIdentity(org.OrgAdminUser)
if err != nil {
return fmt.Errorf("GetSigningIdentity error: %v", err)
}
signIds = append(signIds, orgSignId)
}
// 创建通道,createChannel方法在下面定义
if err := createChannel(signIds, info); err != nil {
return fmt.Errorf("Create channel error: %v", err)
}
fmt.Println(">> 创建通道成功")
fmt.Println(">> 加入通道......")
for _, org := range info.Orgs {
// 加入通道
if err := org.OrgResMgmt.JoinChannel(info.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
return fmt.Errorf("%s peers failed to JoinChannel: %v", org.OrgName, err)
}
}
fmt.Println(">> 加入通道成功")
return nil
}
创建通道
func createChannel(signIDs []msp.SigningIdentity, info *SdkEnvInfo) error {
// Channel management client 负责管理通道,如创建更新通道
chMgmtClient, err := resmgmt.New(*info.OrdererClientContext)
if err != nil {
return fmt.Errorf("Channel management client create error: %v", err)
}
// 根据channel.tx创建通道
req := resmgmt.SaveChannelRequest{ChannelID: info.ChannelID,
ChannelConfigPath: info.ChannelConfig,
SigningIdentities: signIDs}
if _, err := chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
return fmt.Errorf("error should be nil for SaveChannel of orgchannel: %v", err)
}
fmt.Println(">>>> 使用每个org的管理员身份更新锚节点配置...")
//根据锚节点文件更新锚节点,与上面创建通道流程相同
for i, org := range info.Orgs {
req = resmgmt.SaveChannelRequest{ChannelID: info.ChannelID,
ChannelConfigPath: org.OrgAnchorFile,
SigningIdentities: []msp.SigningIdentity{signIDs[i]}}
if _, err = org.OrgResMgmt.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
return fmt.Errorf("SaveChannel for anchor org %s error: %v", org.OrgName, err)
}
}
fmt.Println(">>>> 使用每个org的管理员身份更新锚节点配置完成")
return nil
}
智能合约完整生命周期
func CreateCCLifecycle(info *SdkEnvInfo, sequence int64, upgrade bool, sdk *fabsdk.FabricSDK) error {
if len(info.Orgs) == 0 {
return fmt.Errorf("the number of organization should not be zero.")
}
// 打包链码
fmt.Println(">> 开始打包链码......")
label, ccPkg, err := packageCC(info.ChaincodeID, info.ChaincodeVersion, info.ChaincodePath)
if err != nil {
return fmt.Errorf("pakcagecc error: %v", err)
}
packageID := lcpackager.ComputePackageID(label, ccPkg)
fmt.Println(">> 打包链码成功")
// 安装链码
fmt.Println(">> 开始安装链码......")
if err := installCC(label, ccPkg, info.Orgs); err != nil {
return fmt.Errorf("installCC error: %v", err)
}
// 检索已安装链码包
if err := getInstalledCCPackage(packageID, info.Orgs[0]); err != nil {
return fmt.Errorf("getInstalledCCPackage error: %v", err)
}
// 查询已安装链码
if err := queryInstalled(packageID, info.Orgs[0]); err != nil {
return fmt.Errorf("queryInstalled error: %v", err)
}
fmt.Println(">> 安装链码成功")
// 批准链码
fmt.Println(">> 组织认可智能合约定义......")
if err := approveCC(packageID, info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs, info.OrdererEndpoint); err != nil {
return fmt.Errorf("approveCC error: %v", err)
}
// 查询批准
if err:=queryApprovedCC(info.ChaincodeID, sequence, info.ChannelID, info.Orgs);err!=nil{
return fmt.Errorf("queryApprovedCC error: %v", err)
}
fmt.Println(">> 组织认可智能合约定义完成")
// 检查智能合约是否就绪
fmt.Println(">> 检查智能合约是否就绪......")
if err:=checkCCCommitReadiness(packageID, info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs); err!=nil{
return fmt.Errorf("checkCCCommitReadiness error: %v", err)
}
fmt.Println(">> 智能合约已经就绪")
// Commit
fmt.Println(">> 提交智能合约定义......")
if err:=commitCC(info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs, info.OrdererEndpoint);err!=nil{
return fmt.Errorf("commitCC error: %v", err)
}
// 查询Commit结果
if err:=queryCommittedCC(info.ChaincodeID, info.ChannelID, sequence, info.Orgs); err!=nil{
return fmt.Errorf("queryCommittedCC error: %v", err)
}
fmt.Println(">> 智能合约定义提交完成")
// 初始化
fmt.Println(">> 调用智能合约初始化方法......")
if err:=initCC(info.ChaincodeID, upgrade, info.ChannelID, info.Orgs[0], sdk); err!=nil{
return fmt.Errorf("initCC error: %v", err)
}
fmt.Println(">> 完成智能合约初始化")
return nil
}
set
package sdkInit
import (
"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
)
func (t *Application) Set(args []string) (string, error) {
var tempArgs [][]byte
for i := 1; i < len(args); i++ {
tempArgs = append(tempArgs, []byte(args[i]))
}
request := channel.Request{ChaincodeID: t.SdkEnvInfo.ChaincodeID, Fcn: args[0], Args: [][]byte{[]byte(args[1]), []byte(args[2])}}
response, err := t.SdkEnvInfo.ChClient.Execute(request)
if err != nil {
// 资产转移失败
return "", err
}
//fmt.Println("============== response:",response)
return string(response.TransactionID), nil
}