github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/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  	err = stub.PutPrivateData("collectionMarbles", colorNameIndexKey, value)
   195  	if err != nil {
   196  		return shim.Error(err.Error())
   197  	}
   198  
   199  	// ==== Marble saved and indexed. Return success ====
   200  	fmt.Println("- end init marble")
   201  	return shim.Success(nil)
   202  }
   203  
   204  // ===============================================
   205  // readMarble - read a marble from chaincode state
   206  // ===============================================
   207  func (t *MarblesPrivateChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   208  	var name, jsonResp string
   209  	var err error
   210  
   211  	if len(args) != 1 {
   212  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   213  	}
   214  
   215  	name = args[0]
   216  	valAsbytes, err := stub.GetPrivateData("collectionMarbles", name) //get the marble from chaincode state
   217  	if err != nil {
   218  		jsonResp = "{\"Error\":\"Failed to get state for " + name + ": " + err.Error() + "\"}"
   219  		return shim.Error(jsonResp)
   220  	} else if valAsbytes == nil {
   221  		jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}"
   222  		return shim.Error(jsonResp)
   223  	}
   224  
   225  	return shim.Success(valAsbytes)
   226  }
   227  
   228  // ===============================================
   229  // readMarblereadMarblePrivateDetails - read a marble private details from chaincode state
   230  // ===============================================
   231  func (t *MarblesPrivateChaincode) readMarblePrivateDetails(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   232  	var name, jsonResp string
   233  	var err error
   234  
   235  	if len(args) != 1 {
   236  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   237  	}
   238  
   239  	name = args[0]
   240  	valAsbytes, err := stub.GetPrivateData("collectionMarblePrivateDetails", name) //get the marble private details from chaincode state
   241  	if err != nil {
   242  		jsonResp = "{\"Error\":\"Failed to get private details for " + name + ": " + err.Error() + "\"}"
   243  		return shim.Error(jsonResp)
   244  	} else if valAsbytes == nil {
   245  		jsonResp = "{\"Error\":\"Marble private details does not exist: " + name + "\"}"
   246  		return shim.Error(jsonResp)
   247  	}
   248  
   249  	return shim.Success(valAsbytes)
   250  }
   251  
   252  // ===============================================
   253  // getMarbleHash - get marble private data hash for collectionMarbles from chaincode state
   254  // ===============================================
   255  func (t *MarblesPrivateChaincode) getMarbleHash(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   256  	var name, jsonResp string
   257  	var err error
   258  
   259  	if len(args) != 1 {
   260  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   261  	}
   262  
   263  	name = args[0]
   264  	valAsbytes, err := stub.GetPrivateDataHash("collectionMarbles", name)
   265  	if err != nil {
   266  		jsonResp = "{\"Error\":\"Failed to get marble private data hash for " + name + "\"}"
   267  		return shim.Error(jsonResp)
   268  	} else if valAsbytes == nil {
   269  		jsonResp = "{\"Error\":\"Marble private marble data hash does not exist: " + name + "\"}"
   270  		return shim.Error(jsonResp)
   271  	}
   272  
   273  	return shim.Success(valAsbytes)
   274  }
   275  
   276  // ===============================================
   277  // getMarblePrivateDetailsHash - get marble private data hash for collectionMarblePrivateDetails from chaincode state
   278  // ===============================================
   279  func (t *MarblesPrivateChaincode) getMarblePrivateDetailsHash(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   280  	var name, jsonResp string
   281  	var err error
   282  
   283  	if len(args) != 1 {
   284  		return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
   285  	}
   286  
   287  	name = args[0]
   288  	valAsbytes, err := stub.GetPrivateDataHash("collectionMarblePrivateDetails", name)
   289  	if err != nil {
   290  		jsonResp = "{\"Error\":\"Failed to get marble private details hash for " + name + ": " + err.Error() + "\"}"
   291  		return shim.Error(jsonResp)
   292  	} else if valAsbytes == nil {
   293  		jsonResp = "{\"Error\":\"Marble private details hash does not exist: " + name + "\"}"
   294  		return shim.Error(jsonResp)
   295  	}
   296  
   297  	return shim.Success(valAsbytes)
   298  }
   299  
   300  // ==================================================
   301  // delete - remove a marble key/value pair from state
   302  // ==================================================
   303  func (t *MarblesPrivateChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   304  	fmt.Println("- start delete marble")
   305  
   306  	type marbleDeleteTransientInput struct {
   307  		Name string `json:"name"`
   308  	}
   309  
   310  	if len(args) != 0 {
   311  		return shim.Error("Incorrect number of arguments. Private marble name must be passed in transient map.")
   312  	}
   313  
   314  	transMap, err := stub.GetTransient()
   315  	if err != nil {
   316  		return shim.Error("Error getting transient: " + err.Error())
   317  	}
   318  
   319  	marbleDeleteJsonBytes, ok := transMap["marble_delete"]
   320  	if !ok {
   321  		return shim.Error("marble_delete must be a key in the transient map")
   322  	}
   323  
   324  	if len(marbleDeleteJsonBytes) == 0 {
   325  		return shim.Error("marble_delete value in the transient map must be a non-empty JSON string")
   326  	}
   327  
   328  	var marbleDeleteInput marbleDeleteTransientInput
   329  	err = json.Unmarshal(marbleDeleteJsonBytes, &marbleDeleteInput)
   330  	if err != nil {
   331  		return shim.Error("Failed to decode JSON of: " + string(marbleDeleteJsonBytes))
   332  	}
   333  
   334  	if len(marbleDeleteInput.Name) == 0 {
   335  		return shim.Error("name field must be a non-empty string")
   336  	}
   337  
   338  	// to maintain the color~name index, we need to read the marble first and get its color
   339  	valAsbytes, err := stub.GetPrivateData("collectionMarbles", marbleDeleteInput.Name) //get the marble from chaincode state
   340  	if err != nil {
   341  		return shim.Error("Failed to get state for " + marbleDeleteInput.Name)
   342  	} else if valAsbytes == nil {
   343  		return shim.Error("Marble does not exist: " + marbleDeleteInput.Name)
   344  	}
   345  
   346  	var marbleToDelete marble
   347  	err = json.Unmarshal([]byte(valAsbytes), &marbleToDelete)
   348  	if err != nil {
   349  		return shim.Error("Failed to decode JSON of: " + string(valAsbytes))
   350  	}
   351  
   352  	// delete the marble from state
   353  	err = stub.DelPrivateData("collectionMarbles", marbleDeleteInput.Name)
   354  	if err != nil {
   355  		return shim.Error("Failed to delete state:" + err.Error())
   356  	}
   357  
   358  	// Also delete the marble from the color~name index
   359  	indexName := "color~name"
   360  	colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleToDelete.Color, marbleToDelete.Name})
   361  	if err != nil {
   362  		return shim.Error(err.Error())
   363  	}
   364  	err = stub.DelPrivateData("collectionMarbles", colorNameIndexKey)
   365  	if err != nil {
   366  		return shim.Error("Failed to delete state:" + err.Error())
   367  	}
   368  
   369  	// Finally, delete private details of marble
   370  	err = stub.DelPrivateData("collectionMarblePrivateDetails", marbleDeleteInput.Name)
   371  	if err != nil {
   372  		return shim.Error(err.Error())
   373  	}
   374  
   375  	return shim.Success(nil)
   376  }
   377  
   378  // ===========================================================
   379  // transfer a marble by setting a new owner name on the marble
   380  // ===========================================================
   381  func (t *MarblesPrivateChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   382  
   383  	fmt.Println("- start transfer marble")
   384  
   385  	type marbleTransferTransientInput struct {
   386  		Name  string `json:"name"`
   387  		Owner string `json:"owner"`
   388  	}
   389  
   390  	if len(args) != 0 {
   391  		return shim.Error("Incorrect number of arguments. Private marble data must be passed in transient map.")
   392  	}
   393  
   394  	transMap, err := stub.GetTransient()
   395  	if err != nil {
   396  		return shim.Error("Error getting transient: " + err.Error())
   397  	}
   398  
   399  	marbleOwnerJsonBytes, ok := transMap["marble_owner"]
   400  	if !ok {
   401  		return shim.Error("marble_owner must be a key in the transient map")
   402  	}
   403  
   404  	if len(marbleOwnerJsonBytes) == 0 {
   405  		return shim.Error("marble_owner value in the transient map must be a non-empty JSON string")
   406  	}
   407  
   408  	var marbleTransferInput marbleTransferTransientInput
   409  	err = json.Unmarshal(marbleOwnerJsonBytes, &marbleTransferInput)
   410  	if err != nil {
   411  		return shim.Error("Failed to decode JSON of: " + string(marbleOwnerJsonBytes))
   412  	}
   413  
   414  	if len(marbleTransferInput.Name) == 0 {
   415  		return shim.Error("name field must be a non-empty string")
   416  	}
   417  	if len(marbleTransferInput.Owner) == 0 {
   418  		return shim.Error("owner field must be a non-empty string")
   419  	}
   420  
   421  	marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleTransferInput.Name)
   422  	if err != nil {
   423  		return shim.Error("Failed to get marble:" + err.Error())
   424  	} else if marbleAsBytes == nil {
   425  		return shim.Error("Marble does not exist: " + marbleTransferInput.Name)
   426  	}
   427  
   428  	marbleToTransfer := marble{}
   429  	err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse()
   430  	if err != nil {
   431  		return shim.Error(err.Error())
   432  	}
   433  	marbleToTransfer.Owner = marbleTransferInput.Owner //change the owner
   434  
   435  	marbleJSONasBytes, _ := json.Marshal(marbleToTransfer)
   436  	err = stub.PutPrivateData("collectionMarbles", marbleToTransfer.Name, marbleJSONasBytes) //rewrite the marble
   437  	if err != nil {
   438  		return shim.Error(err.Error())
   439  	}
   440  
   441  	fmt.Println("- end transferMarble (success)")
   442  	return shim.Success(nil)
   443  }
   444  
   445  // ===========================================================================================
   446  // getMarblesByRange performs a range query based on the start and end keys provided.
   447  
   448  // Read-only function results are not typically submitted to ordering. If the read-only
   449  // results are submitted to ordering, or if the query is used in an update transaction
   450  // and submitted to ordering, then the committing peers will re-execute to guarantee that
   451  // result sets are stable between endorsement time and commit time. The transaction is
   452  // invalidated by the committing peers if the result set has changed between endorsement
   453  // time and commit time.
   454  // Therefore, range queries are a safe option for performing update transactions based on query results.
   455  // ===========================================================================================
   456  func (t *MarblesPrivateChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   457  
   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  }