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 }