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 }