zoukankan      html  css  js  c++  java
  • Hyperledger fabric 链码篇GO(四)

    Hyperledger fabric 链码篇GO(四)

    fabric中的链码也就是我们区块链所认知的智能合约,fabric中可由nodejs,java,go编写,本篇只针对GO语言编写链码。将详细介绍链码编写所必须的函数以及相关功能函数。

    1、常识

    • 链码的包名指定

      // xxx.go
      package main
      
    • 必须要引入的包

      import(
          "github.com/hyperledger/fabric/core/chaincode/shim"
          pb "github.com/hyperledger/fabric/protos/peer"
          //pb为别名的意思
      )
      
    • 链码的书写要求

      //自定义结构体,类,基于这个类实现接口函数
      type Test struct{
      	//空着即可
      }
      
      
    • 链码API查询

      //go语言的链码包shim
      https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim
      

    注意事项

    要注意put写入的数据状态不会立刻得到获取,因为put只是执行链码的模拟交易,并不会真正将状态保存在账本中,必须经过orderer达成共识之后,将数据状态保存在区块中,然后保存在各peer节点的账本中

    2、函数及链码基本结构

    • success

      // Success ... status:200
      //链码执行后返回成功信息
      func Success(payload []byte) pb.Response {
      	return pb.Response{
      		Status:  OK,
      		Payload: payload,
      	}
      }
      
    • error

      // Error ...status:500
      //链码执行后返回错误信息
      func Error(msg string) pb.Response {
      	return pb.Response{
      		Status:  ERROR,
      		Message: msg,
      	}
      }
      

    2.1、基本结构

    下面是编写一个链码的基本框架,由空结构体,Init函数和Invoke函数,主函数组成

    package main
    //引入必要的包
    import (
    	"fmt"
    	"strconv"
    	"github.com/hyperledger/fabric/core/chaincode/shim"
        //别名pb
    	pb "github.com/hyperledger/fabric/protos/peer"
    )
    // 声明一个结构体,必备,为空
    type SimpleChaincode struct {
    }
    //添加Init函数
    func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
    		//实现链码初始化或升级时的处理逻辑
           //编写时可灵活使用stub中的API
    }
    //添加Invoke函数
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    	//实现链码运行中被调用或查询时的处理逻辑
        //编写时可灵活使用stub中的API
    }
    //主函数,需要调用shim.Start()方法启动链码
    func main() {
    	err := shim.Start(new(SimpleChaincode))
    	if err != nil {
    		fmt.Printf("Error starting Simple chaincode: %s", err)
    	}
    }
    

    2.2、Init函数

    Init方法是系统初始化方法,当执行命令peer chaincode instantiate实例化chaincode时会调用该方法,同时命令中-c选项后面内容为会作为参数传入Init方法中。

    #shell 命令
    $ peer chaincode instantiate -o orderer.example.com:7050  -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100", "b", "100"]}' 
    

    Args中共有5个参数,第一个为固定值,后面4个为参数,如果传入数据太多,可以采用json等数据格式

    func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
    		 _ ,args := stub.GetFunctionAndParameters()
        return shim.Success ([]byte("success init!!!"))
    }
    

    2.3、Invoke函数

    Invoke方法的主要作用为写入数据,比如发起交易等,在执行命令 peer chaincode invoke 时系统会调用该方法,并把-c后的参数传入invoke方法中。

    参数原理和init类似

    3、账本操作API

    ChaincodeStubInterface

    3.1、参数解析API

    含义:调用链码时需要给被调用的目标函数/方法传递参数,与参数解析相关的API 提供了获取这些参数(包括被调用的目标函数/方法名称)的方法

    GetFunctionAndParameters

    • 获取客户端传入的参数GetFunctionAndParameters()
    //源码
    func (s *ChaincodeStub) GetFunctionAndParameters() (function string, params []string) {
    	allargs := s.GetStringArgs()
    	function = ""
    	params = []string{}
    	if len(allargs) >= 1 {
            //返回时第一个为函数名,后面为其他参数
    		function = allargs[0]
    		params = allargs[1:]
    	}
    	return
    }
    //执行命令
    peer chaincode invoke -o orderer.example.com:7050  -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["invoke","a", "b", "10"]}' 
    //示例代码
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
        _ ,args := stub.GetFunctionAndParameters()
        //获取执行命令中的参数a,b,10
        var a_parm = args[0]
        var b_parm = args[1]
        var c_parm = args[2]
        return shim.Success ([]byte("success invoke!!!"))
    }
    

    3.2、账本数据状态操作API

    含义:该类型API提供了对账本数据状态进行操作的方法,包括对状态数据的查询以及事务处理

    GetState

    • GetState(key string) ([]byte, error) :根据指定的key查询相应的数据状态

      func (s *ChaincodeStub) GetState(key string) ([]byte, error) {
      	// Access public data by setting the collection to empty string
      	collection := ""
      	return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID)
      }
      //示例代码
      func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
          keyvalue, err := stub.GetState("user1")
          return shim.Success(keyvalue)
      }
      

    PutState

    • PutState(key string, value []byte) error:根据指定的key,将相应的value保存在分类账本中

      func (s *ChaincodeStub) PutState(key string, value []byte) error {
      	if key == "" {
      		return errors.New("key must not be an empty string")
      	}
      	// Access public data by setting the collection to empty string
      	collection := ""
      	return s.handler.handlePutState(collection, key, value, s.ChannelID, s.TxID)
      }
      //示例代码
      func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
          stub.PutState("user1",[]byte("putvalue"))
          return shim.Success([]byte("sucess invoke putstate"))
      }
      

    DelState

    • DelState(key string) error:根据指定的key将对应的数据状态删除

      func (s *ChaincodeStub) DelState(key string) error {
      	// Access public data by setting the collection to empty string
      	collection := ""
      	return s.handler.handleDelState(collection, key, s.ChannelID, s.TxID)
      }
      func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
          err :=stub.DelState("user1")
          return shim.Success("delete success")
      }
      

    GetStateByRange

    • GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error):根据指定的开始key和结束key,查询范围内的所有数据状态,注意,结束key对应的数据状态不包含在返回的结果集中

      func (s *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) {
      	if startKey == "" {
      		startKey = emptyKeySubstitute
      	}
      	if err := validateSimpleKeys(startKey, endKey); err != nil {
      		return nil, err
      	}
      	collection := ""
      
      	// ignore QueryResponseMetadata as it is not applicable for a range query without pagination
      	iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil)
      	return iterator, err
      }
      //示例代码
      func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
          startKey = "startKey"
          endKey = "endKey"
          //根据范围查询,得到StateQueryIteratorInterface迭代器接口
          keyIter , err := stub.getStateByRange(startKey,endKey)
          //关闭迭代器接口
          defer keyIter.close()
          var keys []string
          for keyIter.HasNext(){            //如果有下一个节点
              //得到下一个键值对
              response, iterErr := keysIter.Next()
              if iterErr != nil{
              	return shim.Error(fmt.Sprintf("find an error  %s",iterErr))
              }
              keys = append(keys,response.Key)            //存储键值到数组中
          }
          //遍历key数组
          for key, value := range keys{
              fmt.Printf("key %d contains %s",key ,value)
          }
          //编码keys数组为json格式
          jsonKeys ,err = json.Marshal(keys)
          if err != nil{
              return shim.Error(fmt.Sprintf("data marshal json error:  %s",err))
          }
          //将编码之后的json字符串传递给客户端
          return shim.Success(jsonKeys)
      }
      

    GetHistoryForKey

    • GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error):根据指定的key查询所有的历史记录信息

      func (s *ChaincodeStub) GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) {
      	response, err := s.handler.handleGetHistoryForKey(key, s.ChannelID, s.TxID)
      	if err != nil {
      		return nil, err
      	}
      	return &HistoryQueryIterator{CommonIterator: &CommonIterator{s.handler, s.ChannelID, s.TxID, response, 0}}, nil
      }
      
      //示例代码
      func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
          
          keyIter , err := stub.GetHistoryForKey("user1")
          if err != nil{
                  return shim.Error(fmt.Sprintf("GetHistoryForKey error:  %s",err))
              }
          //关闭迭代器接口
          defer keyIter.close()
          var keys []string
          for keyIter.HasNext(){            //如果有下一个节点
              //得到下一个键值对
              response, iterErr := keysIter.Next()
              if iterErr != nil{
              	return shim.Error(fmt.Sprintf("find an error  %s",iterErr))
              }
              //交易编号
              txid := response.TxId 
              //交易的值
              txvalue = response.Value
              //当前交易的状态
              txstatus = response.IsDelete	
              //交易发生的时间戳
              txtimestamp = response.Timestamp
              keys = append(keys,txid)            //存储键值到数组中
          }
          //编码keys数组为json格式
          jsonKeys ,err = json.Marshal(keys)
          if err != nil{
              return shim.Error(fmt.Sprintf("data marshal json error:  %s",err))
          }
          //将编码之后的json字符串传递给客户端
          return shim.Success(jsonKeys)
      }
      

    CreateCompositeKey

    • CreateCompositeKey(objectType string, attributes []string) (string, error):创建一个复合键

      func (s *ChaincodeStub) CreateCompositeKey(objectType string, attributes []string) (string, error) {
      	return CreateCompositeKey(objectType, attributes)
      }
      
      func CreateCompositeKey(objectType string, attributes []string) (string, error) {
      	if err := validateCompositeKeyAttribute(objectType); err != nil {
      		return "", err
      	}
      	ck := compositeKeyNamespace + objectType + string(minUnicodeRuneValue)
      	for _, att := range attributes {
      		if err := validateCompositeKeyAttribute(att); err != nil {
      			return "", err
      		}
      		ck += att + string(minUnicodeRuneValue)
      	}
      	return ck, nil
      }
      

    SplitCompositeKey

    • SplitCompositeKey(compositeKey string) (string, []string, error):对指定的复合键进行分割

      func (s *ChaincodeStub) SplitCompositeKey(compositeKey string) (string, []string, error) {
      	return splitCompositeKey(compositeKey)
      }
      
      func splitCompositeKey(compositeKey string) (string, []string, error) {
      	componentIndex := 1
      	components := []string{}
      	for i := 1; i < len(compositeKey); i++ {
      		if compositeKey[i] == minUnicodeRuneValue {
      			components = append(components, compositeKey[componentIndex:i])
      			componentIndex = i + 1
      		}
      	}
      	return components[0], components[1:], nil
      }
      

    GetQueryResult

    • GetQueryResult(query string) (StateQueryIteratorInterface, error) :对状态数据库进行富查询,目前支持富查询的只有CouchDB

      func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) {
      	// Access public data by setting the collection to empty string
      	collection := ""
      	// ignore QueryResponseMetadata as it is not applicable for a rich query without pagination
      	iterator, _, err := s.handleGetQueryResult(collection, query, nil)
      
      	return iterator, err
      }
      

    InvokeChaincode

    • InvokeChaincode(chaincodeName string, args [][]byte, channel string):调用其他链码

      //链码名,传递参数,通道名
      func (s *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response {
      	// Internally we handle chaincode name as a composite name
      	if channel != "" {
      		chaincodeName = chaincodeName + "/" + channel
      	}
      	return s.handler.handleInvokeChaincode(chaincodeName, args, s.ChannelID, s.TxID)
      }
      //示例代码
      func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
          //设置参数,a向b转账11
          trans := [][]byte{[]byte("invoke"),[]byte("a"),[]byte("b"),[]byte("11")}
          //调用chaincode
          response := stub.InvokeChaincode("mycc",trans,"mychannel")
          //判断是否操作成功
          if  response.Status != shim.OK {
              errStr := fmt.Sprintf("Invoke failed ,error : %s ", response.Payload)
              return shim.Error(errStr)
          }
          return shim.Success([]byte"转账成功")
      }
      

    3.3、交易信息API

    含义:获取提交的交易信息的相关API

    GetTxID

    • GetTxID() string :返回交易提案中指定的交易ID

      func (s *ChaincodeStub) GetTxID() string {
      	return s.TxID
      }
      

    GetChannelID

    • GetChannelID() string:返回交易提案中指定通道的ID

      func (s *ChaincodeStub) GetChannelID() string {
      	return s.ChannelID
      }
      

    GetTxTimestamp

    • GetTxTimestamp() (*timestamp.Timestamp, error):返回交易创建的时间戳,这个时间戳时peer接收到交易的具体时间

      func (s *ChaincodeStub) GetTxTimestamp() (*timestamp.Timestamp, error) {
      	hdr := &common.Header{}
      	if err := proto.Unmarshal(s.proposal.Header, hdr); err != nil {
      		return nil, fmt.Errorf("error unmarshaling Header: %s", err)
      	}
      
      	chdr := &common.ChannelHeader{}
      	if err := proto.Unmarshal(hdr.ChannelHeader, chdr); err != nil {
      		return nil, fmt.Errorf("error unmarshaling ChannelHeader: %s", err)
      	}
      
      	return chdr.GetTimestamp(), nil
      }
      

    GetBinding

    • GetBinding() ([]byte, error):返回交易的绑定信息,如一些临时性信息,以避免重复性攻击

      func (s *ChaincodeStub) GetBinding() ([]byte, error) {
      	return s.binding, nil
      }
      

    GetSignedProposal

    • GetSignedProposal() (*pb.SignedProposal, error):返回与交易提案相关的签名身份信息

      func (s *ChaincodeStub) GetSignedProposal() (*pb.SignedProposal, error) {
      	return s.signedProposal, nil
      }
      

      GetCreator

    • GetCreator() ([]byte, error) :返回该交易提交者的身份信息

      func (s *ChaincodeStub) GetCreator() ([]byte, error) {
      	return s.creator, nil
      }
      

    GetTransient

    • GetTransient() (map[string][]byte, error):返回交易中不会被写至账本中的一些临时性信息

      func (s *ChaincodeStub) GetTransient() (map[string][]byte, error) {
      	return s.transient, nil
      }
      

    3.4、事件处理API

    含义:与事件处理相关的API

    SetEvent

    • SetEvent(name string, payload []byte) error:设置事件,包括事件名称和内容

      // SetEvent documentation can be found in interfaces.go
      func (s *ChaincodeStub) SetEvent(name string, payload []byte) error {
      	if name == "" {
      		return errors.New("event name can not be empty string")
      	}
      	s.chaincodeEvent = &pb.ChaincodeEvent{EventName: name, Payload: payload}
      	return nil
      }
      

    3.5、对PrivateData操作的API

    含义:hyperledger fabric在1.2.0版本中新增的对私有数据操作的相关API

    //根据指定key,从指定的私有数据集中查询对应的私有数据
    func (s *ChaincodeStub) GetPrivateData(collection string, key string) ([]byte, error)
    //根据给定的部分组合键的集合,查询给定的私有状态
    func (s *ChaincodeStub) GetPrivateDataByPartialCompositeKey(collection, objectType string, attributes []string) (StateQueryIteratorInterface, error)
    //根据指定的开始key和结束key查询范围内的私有数据(不包括结束key)
    func (s *ChaincodeStub) GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error)
    //根据指定的查询字符串执行富查询
    func (s *ChaincodeStub) GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)
    //将指定的key与value保存到私有数据集中
    func (s *ChaincodeStub) PutPrivateData(collection string, key string, value []byte) error
    //根据key删除相应数据
    func (s *ChaincodeStub) DelPrivateData(collection string, key string) error
    

    4、背书策略

    背书的过程就是一笔交易被确认的过程,而背书策略就是用来指示相关的参与方如何对交易进行确认。背书策略的设置是通过部署链码实例化时instantiate命令中的-P参数来设置的

    peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "AND ('Org1MSP.member','Org2MSP.member')"
    

    此命令表示需要组织1和组织2中任意一个用户共同来参与交易的确认并且同意,这样的交易才能生效并被记录到去区块链中

    #也可以指定背书节点,使用peerAddresses来指定背书节点
    peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["invoke","a","b","10"]}'
    
    • 背书规则示例1

      #需要组织中任意用户共同参与背书
      "AND ('Org1MSP.member',  'Org2MSP.member')"
      
    • 背书规则示例2

      #只要组织中任意一个用户验证即可
      "OR ('Org1MSP.member',  'Org2MSP.member')"
      
    • 背书规则示例3

      #两种方法让交易生效
      #    1、组织1中有成员验证成功
      #    2、组织2和组织3中有成员共同参与验证成功
      "OR ('Org1MSP.member', AND ('Org2MSP.member',  'Org3MSP.member'))"
      

    注意:背书规则只针对写入数据的操作进行校验,对于查询类的背书不操作

    5、应用示例

    设计了一个链码,实现了资产管理,转账交易的功能,主要实现了以下几个函数

    • init函数

      初始化链码,设定账户名称及资产额度

    • invoke函数

      实现函数的识别,并转到不同的函数功能

    • del函数

      删除账户

    • find函数

      查看账户额度

    • set函数

      根据账户名称设定要存入的金额

    • get函数

      根据账户名称设定要取出的金额

    package main
    
    import (
    	"fmt"
    	"github.com/hyperledger/fabric/core/chaincode/shim"
    	"github.com/hyperledger/fabric/protos/peer"
    	"strconv"
    )
    
    type AssetChaincode struct {
    	//empty
    }
    
    //Init function
    func (t *AssetChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
    	_, args := stub.GetFunctionAndParameters()
    	if len(args) != 4 {
    		return shim.Error("Must four initialization parameters, representing name and money ,respectively")
    	}
    	var a = args[0]
    	var acount = args[1]
    	var b = args[2]
    	var bcount = args[3]
    	if len(a) < 2 {
    		return shim.Error("the length of name must not less than 2")
    	}
    	if len(b) < 2 {
    		return shim.Error("the length of name must not less than 2")
    	}
    	_, err := strconv.Atoi(acount)
    	if err != nil {
    		return shim.Error(a + " account is false")
    	}
    	_, err = strconv.Atoi(bcount)
    	if err != nil {
    		return shim.Error(b + " account is false")
    	}
    
    	err = stub.PutState(a, []byte(acount))
    	if err != nil {
    		return shim.Error(a + " Occuring error when saving the data")
    	}
    	err = stub.PutState(b, []byte(bcount))
    	if err != nil {
    		return shim.Error(b + " Occuring error when saving the data")
    	}
    	return shim.Success([]byte("Init success"))
    }
    
    //Invoke function
    func (t *AssetChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    	fun, args := stub.GetFunctionAndParameters()
    	if fun == "set" {
    		return set(stub, args)
    	} else if fun == "get" {
    		return get(stub, args)
    	} else if fun == "payment" {
    		return payment(stub, args)
    	} else if fun == "del" {
    		return del(stub, args)
    	} else if fun == "find" {
    		return find(stub, args)
    	}
    	return shim.Error("not have this ability")
    }
    
    //find function
    func find(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    	if len(args) != 1 {
    		return shim.Error("the number of parameters must one")
    	}
    	result, err := stub.GetState(args[0])
    	if err != nil {
    		return shim.Error("occor error when reading the data")
    	}
    	if result == nil {
    		return shim.Error("no data by the key")
    	}
    	return shim.Success(result)
    }
    
    //payment function
    func payment(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    	if len(args) != 3 {
    		return shim.Error("payment format error")
    	}
    
    	var source, target string
    	source = args[0]
    	target = args[1]
    	asset, err := strconv.Atoi(args[2])
    	if err != nil {
    		return shim.Error("transfer amount atoi failed")
    	}
    	sourceS, err := stub.GetState(source)
    	if err != nil {
    		return shim.Error("query source asset failed")
    	}
    	targetS, err := stub.GetState(target)
    	if err != nil {
    		return shim.Error("query target asset failed")
    	}
    	sourceasset, err := strconv.Atoi(string(sourceS))
    	if err != nil {
    		return shim.Error("source asset transform int failed")
    	}
    	targetasset, err := strconv.Atoi(string(targetS))
    	if err != nil {
    		return shim.Error("target asset transform int failed")
    	}
    	if sourceasset < asset {
    		return shim.Error("source asset don't have enough balance")
    	}
    	sourceasset -= asset
    	targetasset += asset
    	err = stub.PutState(source, []byte(strconv.Itoa(sourceasset)))
    	if err != nil {
    		return shim.Error("after save payment soure asset failed")
    	}
    	err = stub.PutState(target, []byte(strconv.Itoa(targetasset)))
    	if err != nil {
    		return shim.Error("after save payment target asset failed")
    	}
    	return shim.Success([]byte("payment success"))
    }
    
    //delete function
    func del(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    	if len(args)!=1{
    		return shim.Error("elete account format error")
    	}
    	err := stub.DelState(args[0])
    	if err!= nil{
    		return shim.Error("delete account error")
    	}
    	return shim.Success([]byte("delete account success"+args[0]))
    }
    
    //set function
    func set(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    	if len(args) != 2 {
    		return shim.Error("set account asset format error")
    	}
    	result, err := stub.GetState(args[0])
    	if err != nil {
    		return shim.Error("occor error when reading the data")
    	}
    	if result == nil {
    		return shim.Error("no data by the key")
    	}
    	asset,err := strconv.Atoi(string(result))
    	if err!= nil{
    		return shim.Error("transfrom account balance error")
    	}
    	val,err := strconv.Atoi(string(args[1]))
    	if err!= nil{
    		return shim.Error("transfrom set balance error")
    	}
    	val += asset
    	err = stub.PutState(args[0],[]byte(strconv.Itoa(val)))
    	if err != nil {
    		return shim.Error("save balance error")
    	}
    	return shim.Success([]byte("set asset success!"))
    }
    
    //get function
    func get(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    	if len(args) != 2 {
    		return shim.Error("t account asset format error")
    	}
    	result, err := stub.GetState(args[0])
    	if err != nil {
    		return shim.Error("occor error when reading the data")
    	}
    	if result == nil {
    		return shim.Error("no data by the key")
    	}
    	asset,err := strconv.Atoi(string(result))
    	if err!= nil{
    		return shim.Error("transfrom account balance error")
    	}
    	val,err := strconv.Atoi(string(args[1]))
    	if err!= nil{
    		return shim.Error("transfrom set balance error")
    	}
    	if asset<val{
    		return shim.Error("not have enough asset")
    	}
    	asset -= val
    	err = stub.PutState(args[0],[]byte(strconv.Itoa(asset)))
    	if err != nil {
    		return shim.Error("save balance error")
    	}
    	return shim.Success([]byte("get asset success!"))
    }
    
    //main function
    func main() {
    	err := shim.Start(new(AssetChaincode))
    	if err != nil {
    		fmt.Printf("start assetchaincode error!,the message is :%s", err)
    	}
    }
    
    
  • 相关阅读:
    YII 数据插入 save() 方法
    springboot md5 加密
    yii 在model中实现连表查询
    yii 获取登录用户的信息
    yii 在GridView中怎样添加html代码
    yii 返回json数据
    Qt 错误:QMainWindow: No such file or directory 解决方法
    Qt 错误:QtGui/QApplication在Qt5没有这个文件
    VMware Ubuntu 虚拟机安装 VMwareTools (VMware虚拟机如何与主机互相复制文件)
    VMware15、Ubuntu19.04、安装教程(图文步骤)
  • 原文地址:https://www.cnblogs.com/liuhui5599/p/14187593.html
Copyright © 2011-2022 走看看