github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/integration/chaincode/marbles_private/chaincode.go (about)

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