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 }