github.com/suchongming/fabric@v2.1.1+incompatible/integration/chaincode/marbles_private/chaincode.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package marbles_private
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/json"
    12  	"fmt"
    13  
    14  	"github.com/hyperledger/fabric-chaincode-go/shim"
    15  	pb "github.com/hyperledger/fabric-protos-go/peer"
    16  )
    17  
    18  // MarblesPrivateChaincode example Chaincode implementation
    19  type MarblesPrivateChaincode struct {
    20  }
    21  
    22  type marble struct {
    23  	ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database
    24  	Name       string `json:"name"`    //the fieldtags are needed to keep case from bouncing around
    25  	Color      string `json:"color"`
    26  	Size       int    `json:"size"`
    27  	Owner      string `json:"owner"`
    28  }
    29  
    30  type marblePrivateDetails struct {
    31  	ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database
    32  	Name       string `json:"name"`    //the fieldtags are needed to keep case from bouncing around
    33  	Price      int    `json:"price"`
    34  }
    35  
    36  // Init initializes chaincode
    37  // ===========================
    38  func (t *MarblesPrivateChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
    39  	return shim.Success(nil)
    40  }
    41  
    42  // Invoke - Our entry point for Invocations
    43  // ========================================
    44  func (t *MarblesPrivateChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    45  	function, args := stub.GetFunctionAndParameters()
    46  	fmt.Println("invoke is running " + function)
    47  
    48  	// Handle different functions
    49  	switch function {
    50  	case "initMarble":
    51  		//create a new marble
    52  		return t.initMarble(stub, args)
    53  	case "readMarble":
    54  		//read a marble
    55  		return t.readMarble(stub, args)
    56  	case "readMarblePrivateDetails":
    57  		//read a marble private details
    58  		return t.readMarblePrivateDetails(stub, args)
    59  	case "transferMarble":
    60  		//change owner of a specific marble
    61  		return t.transferMarble(stub, args)
    62  	case "delete":
    63  		//delete a marble
    64  		return t.delete(stub, args)
    65  	case "getMarblesByRange":
    66  		//get marbles based on range query
    67  		return t.getMarblesByRange(stub, args)
    68  	case "getMarbleHash":
    69  		// get private data hash for collectionMarbles
    70  		return t.getMarbleHash(stub, args)
    71  	case "getMarblePrivateDetailsHash":
    72  		// get private data hash for collectionMarblePrivateDetails
    73  		return t.getMarblePrivateDetailsHash(stub, args)
    74  	default:
    75  		//error
    76  		fmt.Println("invoke did not find func: " + function)
    77  		return shim.Error("Received unknown function invocation")
    78  	}
    79  }
    80  
    81  // ============================================================
    82  // initMarble - create a new marble, store into chaincode state
    83  // ============================================================
    84  func (t *MarblesPrivateChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    85  	var err error
    86  
    87  	type marbleTransientInput struct {
    88  		Name  string `json:"name"` //the fieldtags are needed to keep case from bouncing around
    89  		Color string `json:"color"`
    90  		Size  int    `json:"size"`
    91  		Owner string `json:"owner"`
    92  		Price int    `json:"price"`
    93  	}
    94  
    95  	// ==== Input sanitation ====
    96  	fmt.Println("- start init marble")
    97  
    98  	if len(args) != 0 {
    99  		return shim.Error("Incorrect number of arguments. Private marble data must be passed in transient map.")
   100  	}
   101  
   102  	transMap, err := stub.GetTransient()
   103  	if err != nil {
   104  		return shim.Error("Error getting transient: " + err.Error())
   105  	}
   106  
   107  	marbleJsonBytes, ok := transMap["marble"]
   108  	if !ok {
   109  		return shim.Error("marble must be a key in the transient map")
   110  	}
   111  
   112  	if len(marbleJsonBytes) == 0 {
   113  		return shim.Error("marble value in the transient map must be a non-empty JSON string")
   114  	}
   115  
   116  	var marbleInput marbleTransientInput
   117  	err = json.Unmarshal(marbleJsonBytes, &marbleInput)
   118  	if err != nil {
   119  		return shim.Error("Failed to decode JSON of: " + string(marbleJsonBytes))
   120  	}
   121  
   122  	if len(marbleInput.Name) == 0 {
   123  		return shim.Error("name field must be a non-empty string")
   124  	}
   125  	if len(marbleInput.Color) == 0 {
   126  		return shim.Error("color field must be a non-empty string")
   127  	}
   128  	if marbleInput.Size <= 0 {
   129  		return shim.Error("size field must be a positive integer")
   130  	}
   131  	if len(marbleInput.Owner) == 0 {
   132  		return shim.Error("owner field must be a non-empty string")
   133  	}
   134  	if marbleInput.Price <= 0 {
   135  		return shim.Error("price field must be a positive integer")
   136  	}
   137  
   138  	// ==== Check if marble already exists ====
   139  	marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleInput.Name)
   140  	if err != nil {
   141  		return shim.Error("Failed to get marble: " + err.Error())
   142  	} else if marbleAsBytes != nil {
   143  		fmt.Println("This marble already exists: " + marbleInput.Name)
   144  		return shim.Error("This marble already exists: " + marbleInput.Name)
   145  	}
   146  
   147  	// ==== Create marble object and marshal to JSON ====
   148  	marble := &marble{
   149  		ObjectType: "marble",
   150  		Name:       marbleInput.Name,
   151  		Color:      marbleInput.Color,
   152  		Size:       marbleInput.Size,
   153  		Owner:      marbleInput.Owner,
   154  	}
   155  	marbleJSONasBytes, err := json.Marshal(marble)
   156  	if err != nil {
   157  		return shim.Error(err.Error())
   158  	}
   159  
   160  	// === Save marble to state ===
   161  	err = stub.PutPrivateData("collectionMarbles", marbleInput.Name, marbleJSONasBytes)
   162  	if err != nil {
   163  		return shim.Error(err.Error())
   164  	}
   165  
   166  	// ==== Create marble private details object with price, marshal to JSON, and save to state ====
   167  	marblePrivateDetails := &marblePrivateDetails{
   168  		ObjectType: "marblePrivateDetails",
   169  		Name:       marbleInput.Name,
   170  		Price:      marbleInput.Price,
   171  	}
   172  	marblePrivateDetailsBytes, err := json.Marshal(marblePrivateDetails)
   173  	if err != nil {
   174  		return shim.Error(err.Error())
   175  	}
   176  	err = stub.PutPrivateData("collectionMarblePrivateDetails", marbleInput.Name, marblePrivateDetailsBytes)
   177  	if err != nil {
   178  		return shim.Error(err.Error())
   179  	}
   180  
   181  	//  ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ====
   182  	//  An 'index' is a normal key/value entry in state.
   183  	//  The key is a composite key, with the elements that you want to range query on listed first.
   184  	//  In our case, the composite key is based on indexName~color~name.
   185  	//  This will enable very efficient state range queries based on composite keys matching indexName~color~*
   186  	indexName := "color~name"
   187  	colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name})
   188  	if err != nil {
   189  		return shim.Error(err.Error())
   190  	}
   191  	//  Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.
   192  	//  Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
   193  	value := []byte{0x00}
   194  	stub.PutPrivateData("collectionMarbles", colorNameIndexKey, value)
   195  
   196  	// ==== Marble saved and indexed. Return success ====
   197  	fmt.Println("- end init marble")
   198  	return shim.Success(nil)
   199  }
   200  
   201  // ===============================================
   202  // readMarble - read a marble from chaincode state
   203  // ===============================================
   204  func (t *MarblesPrivateChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   205  	var name, jsonResp string
   206  	var err error
   207  
   208  	if len(args) != 1 {
   209  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   210  	}
   211  
   212  	name = args[0]
   213  	valAsbytes, err := stub.GetPrivateData("collectionMarbles", name) //get the marble from chaincode state
   214  	if err != nil {
   215  		jsonResp = "{\"Error\":\"Failed to get state for " + name + ": " + err.Error() + "\"}"
   216  		return shim.Error(jsonResp)
   217  	} else if valAsbytes == nil {
   218  		jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}"
   219  		return shim.Error(jsonResp)
   220  	}
   221  
   222  	return shim.Success(valAsbytes)
   223  }
   224  
   225  // ===============================================
   226  // readMarblereadMarblePrivateDetails - read a marble private details from chaincode state
   227  // ===============================================
   228  func (t *MarblesPrivateChaincode) readMarblePrivateDetails(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   229  	var name, jsonResp string
   230  	var err error
   231  
   232  	if len(args) != 1 {
   233  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   234  	}
   235  
   236  	name = args[0]
   237  	valAsbytes, err := stub.GetPrivateData("collectionMarblePrivateDetails", name) //get the marble private details from chaincode state
   238  	if err != nil {
   239  		jsonResp = "{\"Error\":\"Failed to get private details for " + name + ": " + err.Error() + "\"}"
   240  		return shim.Error(jsonResp)
   241  	} else if valAsbytes == nil {
   242  		jsonResp = "{\"Error\":\"Marble private details does not exist: " + name + "\"}"
   243  		return shim.Error(jsonResp)
   244  	}
   245  
   246  	return shim.Success(valAsbytes)
   247  }
   248  
   249  // ===============================================
   250  // getMarbleHash - get marble private data hash for collectionMarbles from chaincode state
   251  // ===============================================
   252  func (t *MarblesPrivateChaincode) getMarbleHash(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   253  	var name, jsonResp string
   254  	var err error
   255  
   256  	if len(args) != 1 {
   257  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   258  	}
   259  
   260  	name = args[0]
   261  	valAsbytes, err := stub.GetPrivateDataHash("collectionMarbles", name)
   262  	if err != nil {
   263  		jsonResp = "{\"Error\":\"Failed to get marble private data hash for " + name + "\"}"
   264  		return shim.Error(jsonResp)
   265  	} else if valAsbytes == nil {
   266  		jsonResp = "{\"Error\":\"Marble private marble data hash does not exist: " + name + "\"}"
   267  		return shim.Error(jsonResp)
   268  	}
   269  
   270  	return shim.Success(valAsbytes)
   271  }
   272  
   273  // ===============================================
   274  // getMarblePrivateDetailsHash - get marble private data hash for collectionMarblePrivateDetails from chaincode state
   275  // ===============================================
   276  func (t *MarblesPrivateChaincode) getMarblePrivateDetailsHash(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   277  	var name, jsonResp string
   278  	var err error
   279  
   280  	if len(args) != 1 {
   281  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   282  	}
   283  
   284  	name = args[0]
   285  	valAsbytes, err := stub.GetPrivateDataHash("collectionMarblePrivateDetails", name)
   286  	if err != nil {
   287  		jsonResp = "{\"Error\":\"Failed to get marble private details hash for " + name + ": " + err.Error() + "\"}"
   288  		return shim.Error(jsonResp)
   289  	} else if valAsbytes == nil {
   290  		jsonResp = "{\"Error\":\"Marble private details hash does not exist: " + name + "\"}"
   291  		return shim.Error(jsonResp)
   292  	}
   293  
   294  	return shim.Success(valAsbytes)
   295  }
   296  
   297  // ==================================================
   298  // delete - remove a marble key/value pair from state
   299  // ==================================================
   300  func (t *MarblesPrivateChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   301  	fmt.Println("- start delete marble")
   302  
   303  	type marbleDeleteTransientInput struct {
   304  		Name string `json:"name"`
   305  	}
   306  
   307  	if len(args) != 0 {
   308  		return shim.Error("Incorrect number of arguments. Private marble name must be passed in transient map.")
   309  	}
   310  
   311  	transMap, err := stub.GetTransient()
   312  	if err != nil {
   313  		return shim.Error("Error getting transient: " + err.Error())
   314  	}
   315  
   316  	marbleDeleteJsonBytes, ok := transMap["marble_delete"]
   317  	if !ok {
   318  		return shim.Error("marble_delete must be a key in the transient map")
   319  	}
   320  
   321  	if len(marbleDeleteJsonBytes) == 0 {
   322  		return shim.Error("marble_delete value in the transient map must be a non-empty JSON string")
   323  	}
   324  
   325  	var marbleDeleteInput marbleDeleteTransientInput
   326  	err = json.Unmarshal(marbleDeleteJsonBytes, &marbleDeleteInput)
   327  	if err != nil {
   328  		return shim.Error("Failed to decode JSON of: " + string(marbleDeleteJsonBytes))
   329  	}
   330  
   331  	if len(marbleDeleteInput.Name) == 0 {
   332  		return shim.Error("name field must be a non-empty string")
   333  	}
   334  
   335  	// to maintain the color~name index, we need to read the marble first and get its color
   336  	valAsbytes, err := stub.GetPrivateData("collectionMarbles", marbleDeleteInput.Name) //get the marble from chaincode state
   337  	if err != nil {
   338  		return shim.Error("Failed to get state for " + marbleDeleteInput.Name)
   339  	} else if valAsbytes == nil {
   340  		return shim.Error("Marble does not exist: " + marbleDeleteInput.Name)
   341  	}
   342  
   343  	var marbleToDelete marble
   344  	err = json.Unmarshal([]byte(valAsbytes), &marbleToDelete)
   345  	if err != nil {
   346  		return shim.Error("Failed to decode JSON of: " + string(valAsbytes))
   347  	}
   348  
   349  	// delete the marble from state
   350  	err = stub.DelPrivateData("collectionMarbles", marbleDeleteInput.Name)
   351  	if err != nil {
   352  		return shim.Error("Failed to delete state:" + err.Error())
   353  	}
   354  
   355  	// Also delete the marble from the color~name index
   356  	indexName := "color~name"
   357  	colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleToDelete.Color, marbleToDelete.Name})
   358  	if err != nil {
   359  		return shim.Error(err.Error())
   360  	}
   361  	err = stub.DelPrivateData("collectionMarbles", colorNameIndexKey)
   362  	if err != nil {
   363  		return shim.Error("Failed to delete state:" + err.Error())
   364  	}
   365  
   366  	// Finally, delete private details of marble
   367  	err = stub.DelPrivateData("collectionMarblePrivateDetails", marbleDeleteInput.Name)
   368  	if err != nil {
   369  		return shim.Error(err.Error())
   370  	}
   371  
   372  	return shim.Success(nil)
   373  }
   374  
   375  // ===========================================================
   376  // transfer a marble by setting a new owner name on the marble
   377  // ===========================================================
   378  func (t *MarblesPrivateChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   379  
   380  	fmt.Println("- start transfer marble")
   381  
   382  	type marbleTransferTransientInput struct {
   383  		Name  string `json:"name"`
   384  		Owner string `json:"owner"`
   385  	}
   386  
   387  	if len(args) != 0 {
   388  		return shim.Error("Incorrect number of arguments. Private marble data must be passed in transient map.")
   389  	}
   390  
   391  	transMap, err := stub.GetTransient()
   392  	if err != nil {
   393  		return shim.Error("Error getting transient: " + err.Error())
   394  	}
   395  
   396  	marbleOwnerJsonBytes, ok := transMap["marble_owner"]
   397  	if !ok {
   398  		return shim.Error("marble_owner must be a key in the transient map")
   399  	}
   400  
   401  	if len(marbleOwnerJsonBytes) == 0 {
   402  		return shim.Error("marble_owner value in the transient map must be a non-empty JSON string")
   403  	}
   404  
   405  	var marbleTransferInput marbleTransferTransientInput
   406  	err = json.Unmarshal(marbleOwnerJsonBytes, &marbleTransferInput)
   407  	if err != nil {
   408  		return shim.Error("Failed to decode JSON of: " + string(marbleOwnerJsonBytes))
   409  	}
   410  
   411  	if len(marbleTransferInput.Name) == 0 {
   412  		return shim.Error("name field must be a non-empty string")
   413  	}
   414  	if len(marbleTransferInput.Owner) == 0 {
   415  		return shim.Error("owner field must be a non-empty string")
   416  	}
   417  
   418  	marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleTransferInput.Name)
   419  	if err != nil {
   420  		return shim.Error("Failed to get marble:" + err.Error())
   421  	} else if marbleAsBytes == nil {
   422  		return shim.Error("Marble does not exist: " + marbleTransferInput.Name)
   423  	}
   424  
   425  	marbleToTransfer := marble{}
   426  	err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse()
   427  	if err != nil {
   428  		return shim.Error(err.Error())
   429  	}
   430  	marbleToTransfer.Owner = marbleTransferInput.Owner //change the owner
   431  
   432  	marbleJSONasBytes, _ := json.Marshal(marbleToTransfer)
   433  	err = stub.PutPrivateData("collectionMarbles", marbleToTransfer.Name, marbleJSONasBytes) //rewrite the marble
   434  	if err != nil {
   435  		return shim.Error(err.Error())
   436  	}
   437  
   438  	fmt.Println("- end transferMarble (success)")
   439  	return shim.Success(nil)
   440  }
   441  
   442  // ===========================================================================================
   443  // getMarblesByRange performs a range query based on the start and end keys provided.
   444  
   445  // Read-only function results are not typically submitted to ordering. If the read-only
   446  // results are submitted to ordering, or if the query is used in an update transaction
   447  // and submitted to ordering, then the committing peers will re-execute to guarantee that
   448  // result sets are stable between endorsement time and commit time. The transaction is
   449  // invalidated by the committing peers if the result set has changed between endorsement
   450  // time and commit time.
   451  // Therefore, range queries are a safe option for performing update transactions based on query results.
   452  // ===========================================================================================
   453  func (t *MarblesPrivateChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   454  
   455  	if len(args) < 2 {
   456  		return shim.Error("Incorrect number of arguments. Expecting 2")
   457  	}
   458  
   459  	startKey := args[0]
   460  	endKey := args[1]
   461  
   462  	resultsIterator, err := stub.GetPrivateDataByRange("collectionMarbles", startKey, endKey)
   463  	if err != nil {
   464  		return shim.Error(err.Error())
   465  	}
   466  	defer resultsIterator.Close()
   467  
   468  	// buffer is a JSON array containing QueryResults
   469  	var buffer bytes.Buffer
   470  	buffer.WriteString("[")
   471  
   472  	bArrayMemberAlreadyWritten := false
   473  	for resultsIterator.HasNext() {
   474  		queryResponse, err := resultsIterator.Next()
   475  		if err != nil {
   476  			return shim.Error(err.Error())
   477  		}
   478  		// Add a comma before array members, suppress it for the first array member
   479  		if bArrayMemberAlreadyWritten {
   480  			buffer.WriteString(",")
   481  		}
   482  
   483  		buffer.WriteString(
   484  			fmt.Sprintf(
   485  				`{"Key":"%s", "Record":%s}`,
   486  				queryResponse.Key, queryResponse.Value,
   487  			),
   488  		)
   489  		bArrayMemberAlreadyWritten = true
   490  	}
   491  	buffer.WriteString("]")
   492  
   493  	fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String())
   494  
   495  	return shim.Success(buffer.Bytes())
   496  }