最近实验室的项目要部署到fabric2.0以上版本,之前写的都是1.4的链码,现在看2.0版本的链码还是有些不一样的,主要是链码api改了:
前提:如果想在fabric2.0以上环境中还是想用shim和peerAPI的话:也就是:这里记录一下我们在1.4链码中是需要初始化的,因为我们有些初始化的操作是写在Init方法中的,但是呢在2.0以上版本取消了链码的初始化操作,所以当我们在2.0以上环境中需要延续1.4的Init方法的时候,我们需要在Invoke方法中也把Init方法加进去:实例:
Init方法:
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {// Get the args from the transaction proposal_, args := stub.GetFunctionAndParameters()if len(args) != 2 {return shim.Error("Incorrect arguments. Expecting a key and a value")}// Set up any variables or assets here by calling stub.PutState()// We store the key and the value on the ledgererr := stub.PutState(args[0], []byte(args[1]))if err != nil {return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))}return shim.Success([]byte("ok....."))
}
Invoke方法:
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {// Extract the function and args from the transaction proposalfn, args := stub.GetFunctionAndParameters()var result stringvar err errorif fn == "set" {result, err = set(stub, args)} else if fn == "get"{ // assume 'get' even if fn is nilresult, err = get(stub, args)} else if fn == "Init"{ // assume 'get' even if fn is nilreturn t.Init(stub)}if err != nil {return shim.Error(err.Error())}// Return the result as success payloadreturn shim.Success([]byte(result))
}
这里看到,我们已经把Init方法加入了Invoke函数中了!
在fabric2.3环境运行:
注意一下,2.0链码的-c后面的格式发生了新变化:
-c '{"Args":["PutPublicInfordiy","PublicKey"]}'等价于 //这里默认第一个参数就是函数名,后面才是开始的参数列表
-c '{"function":"PutPublicInfordiy","Args":["PublicKey"]}'//这里很明显,函数名和参数已经隔开了,更加的清晰化。
这两种链格式效果是一样的!可以自己选择用哪种!
下面是fabric2.3提供的新的链码接口:contractapi
下面实例这个链码:
package chaincodeimport ("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
type Asset struct {ID string `json:"ID"`Color string `json:"color"`Size int `json:"size"`Owner string `json:"owner"`AppraisedValue int `json:"appraisedValue"`
}// 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 Asseterr = 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 assetasset := 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.
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {asset, err := s.ReadAsset(ctx, id)if err != nil {return err}asset.Owner = newOwnerassetJSON, err := json.Marshal(asset)if err != nil {return err}return ctx.GetStub().PutState(id, assetJSON)
}// 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 []*Assetfor resultsIterator.HasNext() {queryResponse, err := resultsIterator.Next()if err != nil {return nil, err}var asset Asseterr = json.Unmarshal(queryResponse.Value, &asset)if err != nil {return nil, err}assets = append(assets, &asset)}return assets, nil
}
首先这里执行完初始化链码之后:
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n basic --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"InitLedger","Args":[]}'
这里记住一个模板:
就是peer chiancode invoke ....... -c '{"function":"函数名","Args":[参数]}'
function就是我们链码里的函数名!
2.0的链码是不需要初始化链码的,1.4版本的是需要的,这里2.0的就按照上面的模板就行,注意,参数这里不管链码里传入上面参数,这里都是用字符串格式,比如,int的4也要写成"4"。
注意,如果你想继续用的1.4的链码也可以,将1.4链码原来的包改成这样就可以在2.0环境中继续使用了!
如果你想使用2.0以上版本的链码,需要用的是这个api:
这里注意,2.0的链码,传参数的时候是不能使用args []string的,如果是1.4的链码是可以的。
这里注意,如果想在2.0链码环境中使用如1.4的传入多个参数 args []string的话,可以用这个api:
function, args := ctx.GetStub().GetFunctionAndParameters()
这里注意,这里的function就是Testargs,后面args直接传入4个args参数即可!
根据返回的args,就是之前在1.4环境中的args!
贴上自己写的链码方法demo:
//-c '{"function":"Testargs","Args":["01","fanwen","dayang","man"]}'
//这里注意args后面传入的参数就是args,function是不需要传入的,不然就会报错"err! number is not 4!",这里是和1.4不一样的地方
func (t *mycc) Testargs(ctx contractapi.TransactionContextInterface) string{function, args := ctx.GetStub().GetFunctionAndParameters()//这里的function就是获取了上面的函数名就是Testargsif len(args) != 4 {return "err! number is not 4!"}stu := Student{StuId: args[0],Name: args[1],Address: args[2],Sex: args[3],}assetJSON, err := json.Marshal(stu)if err != nil {return err.Error()}//根据StuId存入账本err = ctx.GetStub().PutState(stu.StuId, assetJSON)if err != nil{return err.Error()}return "you use the function:" + function + " put in state ok!"}
执行效果:
如果在args输入了超过4个参数:
就会报错,就是我们在链码里自己写的逻辑判断的错误!
这里注意,fabric在2.0版本以上的链码中,可以返回任何数据!比如string,int,float64,或者是自定义的结构体或者结构体指针都可以!
注意:传参数的时候,不管传什么参数,在cli端,都是传入的"内容",也就是字符串形式传入的!
如果是查询操作的话,不需要连接orderer结点,也不需要背书策略,直接用以下模板就行:
peer chaincode query -C mychannel -n fan12 -c '{"function":"GetAll","Args":["args"]}'
当然,如果用invoke也可以!看个人习惯。