diff --git a/chaincode/marbles02_private/go/marbles_chaincode_private.go b/chaincode/marbles02_private/go/marbles_chaincode_private.go index 385687c087..055d6f36de 100644 --- a/chaincode/marbles02_private/go/marbles_chaincode_private.go +++ b/chaincode/marbles02_private/go/marbles_chaincode_private.go @@ -6,18 +6,28 @@ SPDX-License-Identifier: Apache-2.0 // ====CHAINCODE EXECUTION SAMPLES (CLI) ================== -// ==== Invoke marbles ==== -// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["initMarble","marble1","blue","35","tom","99"]}' -// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["initMarble","marble2","red","50","tom","102"]}' -// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["initMarble","marble3","blue","70","tom","103"]}' -// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["transferMarble","marble2","jerry"]}' -// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["delete","marble1"]}' - -// ==== Query marbles ==== +// ==== Invoke marbles, pass private data as base64 encoded bytes in transient map ==== +// +// export MARBLE=$(echo -n "{\"name\":\"marble1\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64) +// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}" +// +// export MARBLE=$(echo -n "{\"name\":\"marble2\",\"color\":\"red\",\"size\":50,\"owner\":\"tom\",\"price\":102}" | base64) +// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}" +// +// export MARBLE=$(echo -n "{\"name\":\"marble3\",\"color\":\"blue\",\"size\":70,\"owner\":\"tom\",\"price\":103}" | base64) +// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}" +// +// export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"jerry\"}" | base64) +// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["transferMarble"]}' --transient "{\"marble_owner\":\"$MARBLE_OWNER\"}" +// +// export MARBLE_DELETE=$(echo -n "{\"name\":\"marble1\"}" | base64) +// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["delete"]}' --transient "{\"marble_delete\":\"$MARBLE_DELETE\"}" + +// ==== Query marbles, since queries are not recorded on chain we don't need to hide private data in transient map ==== // peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarble","marble1"]}' // peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}' -// peer chaincode query -C mychannel -n marblesp -c '{"Args":["getMarblesByRange","marble1","marble3"]}' - +// peer chaincode query -C mychannel -n marblesp -c '{"Args":["getMarblesByRange","marble1","marble4"]}' +// // Rich Query (Only supported if CouchDB is used as state database): // peer chaincode query -C mychannel -n marblesp -c '{"Args":["queryMarblesByOwner","tom"]}' // peer chaincode query -C mychannel -n marblesp -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' @@ -84,7 +94,6 @@ import ( "bytes" "encoding/json" "fmt" - "strconv" "strings" "github.com/hyperledger/fabric/core/chaincode/shim" @@ -145,9 +154,6 @@ func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { case "transferMarble": //change owner of a specific marble return t.transferMarble(stub, args) - case "transferMarblesBasedOnColor": - //transfer all marbles of a certain color - return t.transferMarblesBasedOnColor(stub, args) case "delete": //delete a marble return t.delete(stub, args) @@ -173,75 +179,95 @@ func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { var err error - // 0-name 1-color 2-size 3-owner 4-price - // "asdf", "blue", "35", "bob", "99" - if len(args) != 5 { - return shim.Error("Incorrect number of arguments. Expecting 5") + type marbleTransientInput struct { + Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around + Color string `json:"color"` + Size int `json:"size"` + Owner string `json:"owner"` + Price int `json:"price"` } // ==== Input sanitation ==== fmt.Println("- start init marble") - if len(args[0]) == 0 { - return shim.Error("1st argument must be a non-empty string") - } - if len(args[1]) == 0 { - return shim.Error("2nd argument must be a non-empty string") + + if len(args) != 0 { + return shim.Error("Incorrect number of arguments. Private marble data must be passed in transient map.") } - if len(args[2]) == 0 { - return shim.Error("3rd argument must be a non-empty string") + + transMap, err := stub.GetTransient() + if err != nil { + return shim.Error("Error getting transient: " + err.Error()) } - if len(args[3]) == 0 { - return shim.Error("4th argument must be a non-empty string") + + if _, ok := transMap["marble"]; !ok { + return shim.Error("marble must be a key in the transient map") } - if len(args[4]) == 0 { - return shim.Error("5th argument must be a non-empty string") + + if len(transMap["marble"]) == 0 { + return shim.Error("marble value in the transient map must be a non-empty JSON string") } - marbleName := args[0] - color := strings.ToLower(args[1]) - owner := strings.ToLower(args[3]) - size, err := strconv.Atoi(args[2]) + + var marbleInput marbleTransientInput + err = json.Unmarshal(transMap["marble"], &marbleInput) if err != nil { - return shim.Error("3rd argument must be a numeric string") + return shim.Error("Failed to decode JSON of: " + string(transMap["marble"])) } - price, err := strconv.Atoi(args[4]) - if err != nil { - return shim.Error("5th argument must be a numeric string") + + if len(marbleInput.Name) == 0 { + return shim.Error("name field must be a non-empty string") + } + if len(marbleInput.Color) == 0 { + return shim.Error("color field must be a non-empty string") + } + if marbleInput.Size <= 0 { + return shim.Error("size field must be a positive integer") + } + if len(marbleInput.Owner) == 0 { + return shim.Error("owner field must be a non-empty string") + } + if marbleInput.Price <= 0 { + return shim.Error("price field must be a positive integer") } // ==== Check if marble already exists ==== - marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleName) + marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleInput.Name) if err != nil { return shim.Error("Failed to get marble: " + err.Error()) } else if marbleAsBytes != nil { - fmt.Println("This marble already exists: " + marbleName) - return shim.Error("This marble already exists: " + marbleName) + fmt.Println("This marble already exists: " + marbleInput.Name) + return shim.Error("This marble already exists: " + marbleInput.Name) } - // ==== Create marble object and marshal to JSON ==== - objectType := "marble" - marble := &marble{objectType, marbleName, color, size, owner} + // ==== Create marble object, marshal to JSON, and save to state ==== + marble := &marble{ + ObjectType: "marble", + Name: marbleInput.Name, + Color: marbleInput.Color, + Size: marbleInput.Size, + Owner: marbleInput.Owner, + } marbleJSONasBytes, err := json.Marshal(marble) if err != nil { return shim.Error(err.Error()) } - //Alternatively, build the marble json string manually if you don't want to use struct marshalling - //marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}` - //marbleJSONasBytes := []byte(str) // === Save marble to state === - err = stub.PutPrivateData("collectionMarbles", marbleName, marbleJSONasBytes) + err = stub.PutPrivateData("collectionMarbles", marbleInput.Name, marbleJSONasBytes) if err != nil { return shim.Error(err.Error()) } - // ==== Save marble private details ==== - objectType = "marblePrivateDetails" - marblePrivateDetails := &marblePrivateDetails{objectType, marbleName, price} + // ==== Create marble private details object with price, marshal to JSON, and save to state ==== + marblePrivateDetails := &marblePrivateDetails{ + ObjectType: "marblePrivateDetails", + Name: marbleInput.Name, + Price: marbleInput.Price, + } marblePrivateDetailsBytes, err := json.Marshal(marblePrivateDetails) if err != nil { return shim.Error(err.Error()) } - err = stub.PutPrivateData("collectionMarblePrivateDetails", marbleName, marblePrivateDetailsBytes) + err = stub.PutPrivateData("collectionMarblePrivateDetails", marbleInput.Name, marblePrivateDetailsBytes) if err != nil { return shim.Error(err.Error()) } @@ -318,49 +344,72 @@ func (t *SimpleChaincode) readMarblePrivateDetails(stub shim.ChaincodeStubInterf // delete - remove a marble key/value pair from state // ================================================== func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { - var jsonResp string - var marbleJSON marble - if len(args) != 1 { - return shim.Error("Incorrect number of arguments. Expecting 1") + fmt.Println("- start delete marble") + + type marbleDeleteTransientInput struct { + Name string `json:"name"` + } + + if len(args) != 0 { + return shim.Error("Incorrect number of arguments. Private marble name must be passed in transient map.") + } + + transMap, err := stub.GetTransient() + if err != nil { + return shim.Error("Error getting transient: " + err.Error()) + } + + if _, ok := transMap["marble_delete"]; !ok { + return shim.Error("marble_delete must be a key in the transient map") + } + + if len(transMap["marble_delete"]) == 0 { + return shim.Error("marble_delete value in the transient map must be a non-empty JSON string") + } + + var marbleDeleteInput marbleDeleteTransientInput + err = json.Unmarshal(transMap["marble_delete"], &marbleDeleteInput) + if err != nil { + return shim.Error("Failed to decode JSON of: " + string(transMap["marble_delete"])) + } + + if len(marbleDeleteInput.Name) == 0 { + return shim.Error("name field must be a non-empty string") } - marbleName := args[0] // to maintain the color~name index, we need to read the marble first and get its color - valAsbytes, err := stub.GetPrivateData("collectionMarbles", marbleName) //get the marble from chaincode state + valAsbytes, err := stub.GetPrivateData("collectionMarbles", marbleDeleteInput.Name) //get the marble from chaincode state if err != nil { - jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}" - return shim.Error(jsonResp) + return shim.Error("Failed to get state for " + marbleDeleteInput.Name) } else if valAsbytes == nil { - jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}" - return shim.Error(jsonResp) + return shim.Error("Marble does not exist: " + marbleDeleteInput.Name) } - err = json.Unmarshal([]byte(valAsbytes), &marbleJSON) + var marbleToDelete marble + err = json.Unmarshal([]byte(valAsbytes), &marbleToDelete) if err != nil { - jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}" - return shim.Error(jsonResp) + return shim.Error("Failed to decode JSON of: " + string(valAsbytes)) } - err = stub.DelPrivateData("collectionMarbles", marbleName) //remove the marble from chaincode state + // delete the marble from state + err = stub.DelPrivateData("collectionMarbles", marbleDeleteInput.Name) if err != nil { return shim.Error("Failed to delete state:" + err.Error()) } - // maintain the index + // Also delete the marble from the color~name index indexName := "color~name" - colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name}) + colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleToDelete.Color, marbleToDelete.Name}) if err != nil { return shim.Error(err.Error()) } - - // Delete index entry to state. err = stub.DelPrivateData("collectionMarbles", colorNameIndexKey) if err != nil { return shim.Error("Failed to delete state:" + err.Error()) } - // Delete private details of marble - err = stub.DelPrivateData("collectionMarblePrivateDetails", marbleName) + // Finally, delete private details of marble + err = stub.DelPrivateData("collectionMarblePrivateDetails", marbleDeleteInput.Name) if err != nil { return shim.Error(err.Error()) } @@ -373,21 +422,48 @@ func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string // =========================================================== func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { - // 0 1 - // "name", "bob" - if len(args) < 2 { - return shim.Error("Incorrect number of arguments. Expecting 2") + fmt.Println("- start transfer marble") + + type marbleTransferTransientInput struct { + Name string `json:"name"` + Owner string `json:"owner"` } - marbleName := args[0] - newOwner := strings.ToLower(args[1]) - fmt.Println("- start transferMarble ", marbleName, newOwner) + if len(args) != 0 { + return shim.Error("Incorrect number of arguments. Private marble data must be passed in transient map.") + } - marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleName) + transMap, err := stub.GetTransient() + if err != nil { + return shim.Error("Error getting transient: " + err.Error()) + } + + if _, ok := transMap["marble_owner"]; !ok { + return shim.Error("marble_owner must be a key in the transient map") + } + + if len(transMap["marble_owner"]) == 0 { + return shim.Error("marble_owner value in the transient map must be a non-empty JSON string") + } + + var marbleTransferInput marbleTransferTransientInput + err = json.Unmarshal(transMap["marble_owner"], &marbleTransferInput) + if err != nil { + return shim.Error("Failed to decode JSON of: " + string(transMap["marble_owner"])) + } + + if len(marbleTransferInput.Name) == 0 { + return shim.Error("name field must be a non-empty string") + } + if len(marbleTransferInput.Owner) == 0 { + return shim.Error("owner field must be a non-empty string") + } + + marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleTransferInput.Name) if err != nil { return shim.Error("Failed to get marble:" + err.Error()) } else if marbleAsBytes == nil { - return shim.Error("Marble does not exist") + return shim.Error("Marble does not exist: " + marbleTransferInput.Name) } marbleToTransfer := marble{} @@ -395,10 +471,10 @@ func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args if err != nil { return shim.Error(err.Error()) } - marbleToTransfer.Owner = newOwner //change the owner + marbleToTransfer.Owner = marbleTransferInput.Owner //change the owner marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) - err = stub.PutPrivateData("collectionMarbles", marbleName, marbleJSONasBytes) //rewrite the marble + err = stub.PutPrivateData("collectionMarbles", marbleToTransfer.Name, marbleJSONasBytes) //rewrite the marble if err != nil { return shim.Error(err.Error()) } @@ -465,66 +541,6 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar return shim.Success(buffer.Bytes()) } -// ==== Example: GetStateByPartialCompositeKey/RangeQuery ========================================= -// transferMarblesBasedOnColor will transfer marbles of a given color to a certain new owner. -// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. -// Committing peers will re-execute range queries to guarantee that result sets are stable -// between endorsement time and commit time. The transaction is invalidated by the -// committing peers if the result set has changed between endorsement time and commit time. -// Therefore, range queries are a safe option for performing update transactions based on query results. -// =========================================================================================== -func (t *SimpleChaincode) transferMarblesBasedOnColor(stub shim.ChaincodeStubInterface, args []string) pb.Response { - - // 0 1 - // "color", "bob" - if len(args) < 2 { - return shim.Error("Incorrect number of arguments. Expecting 2") - } - - color := args[0] - newOwner := strings.ToLower(args[1]) - fmt.Println("- start transferMarblesBasedOnColor ", color, newOwner) - - // Query the color~name index by color - // This will execute a key range query on all keys starting with 'color' - coloredMarbleResultsIterator, err := stub.GetPrivateDataByPartialCompositeKey("collectionMarbles", "color~name", []string{color}) - if err != nil { - return shim.Error(err.Error()) - } - defer coloredMarbleResultsIterator.Close() - - // Iterate through result set and for each marble found, transfer to newOwner - var i int - for i = 0; coloredMarbleResultsIterator.HasNext(); i++ { - // Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key - responseRange, err := coloredMarbleResultsIterator.Next() - if err != nil { - return shim.Error(err.Error()) - } - - // get the color and name from color~name composite key - objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) - if err != nil { - return shim.Error(err.Error()) - } - returnedColor := compositeKeyParts[0] - returnedMarbleName := compositeKeyParts[1] - fmt.Printf("- found a marble from index:%s color:%s name:%s\n", objectType, returnedColor, returnedMarbleName) - - // Now call the transfer function for the found marble. - // Re-use the same function that is used to transfer individual marbles - response := t.transferMarble(stub, []string{returnedMarbleName, newOwner}) - // if the transfer failed break out of loop and return error - if response.Status != shim.OK { - return shim.Error("Transfer failed: " + response.Message) - } - } - - responsePayload := fmt.Sprintf("Transferred %d %s marbles to %s", i, color, newOwner) - fmt.Println("- end transferMarblesBasedOnColor: " + responsePayload) - return shim.Success([]byte(responsePayload)) -} - // =======Rich queries ========================================================================= // Two examples of rich queries are provided below (parameterized query and ad hoc query). // Rich queries pass a query string to the state database.