github.com/inklabsfoundation/inkchain@v0.17.1-0.20181025012015-c3cef8062f19/examples/marbles/marbles_chaincode.go (about) 1 /* 2 Licensed to the Apache Software Foundation (ASF) under one 3 or more contributor license agreements. See the NOTICE file 4 distributed with this work for additional information 5 regarding copyright ownership. The ASF licenses this file 6 to you under the Apache License, Version 2.0 (the 7 "License"); you may not use this file except in compliance 8 with the License. You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, 13 software distributed under the License is distributed on an 14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 KIND, either express or implied. See the License for the 16 specific language governing permissions and limitations 17 under the License. 18 */ 19 20 // ====CHAINCODE EXECUTION SAMPLES (CLI) ================== 21 22 // ==== Invoke marbles ==== 23 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble1","blue","35","tom"]}' 24 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble2","red","50","tom"]}' 25 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble3","blue","70","tom"]}' 26 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarble","marble2","jerry"]}' 27 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarblesBasedOnColor","blue","jerry"]}' 28 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["delete","marble1"]}' 29 30 // ==== Query marbles ==== 31 // peer chaincode query -C myc1 -n marbles -c '{"Args":["readMarble","marble1"]}' 32 // peer chaincode query -C myc1 -n marbles -c '{"Args":["getMarblesByRange","marble1","marble3"]}' 33 // peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' 34 35 // Rich Query (Only supported if CouchDB is used as state database): 36 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' 37 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' 38 39 //The following examples demonstrate creating indexes on CouchDB 40 //Example hostname:port configurations 41 // 42 //Docker or vagrant environments: 43 // http://couchdb:5984/ 44 // 45 //Inside couchdb docker container 46 // http://127.0.0.1:5984/ 47 48 // Index for chaincodeid, docType, owner. 49 // Note that docType and owner fields must be prefixed with the "data" wrapper 50 // chaincodeid must be added for all queries 51 // 52 // Definition for use with Fauxton interface 53 // {"index":{"fields":["chaincodeid","data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} 54 // 55 // example curl definition for use with command line 56 // curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"chaincodeid\",\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1/_index 57 // 58 59 // Index for chaincodeid, docType, owner, size (descending order). 60 // Note that docType, owner and size fields must be prefixed with the "data" wrapper 61 // chaincodeid must be added for all queries 62 // 63 // Definition for use with Fauxton interface 64 // {"index":{"fields":[{"data.size":"desc"},{"chaincodeid":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"} 65 // 66 // example curl definition for use with command line 67 // curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"chaincodeid\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1/_index 68 69 // Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): 70 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' 71 72 // Rich Query with index design doc specified only (Only supported if CouchDB is used as state database): 73 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":{\"$eq\":\"marble\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}' 74 75 package main 76 77 import ( 78 "bytes" 79 "encoding/json" 80 "fmt" 81 "strconv" 82 "strings" 83 "time" 84 85 "github.com/inklabsfoundation/inkchain/core/chaincode/shim" 86 pb "github.com/inklabsfoundation/inkchain/protos/peer" 87 ) 88 89 // SimpleChaincode example simple Chaincode implementation 90 type SimpleChaincode struct { 91 } 92 93 type marble struct { 94 ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database 95 Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around 96 Color string `json:"color"` 97 Size int `json:"size"` 98 Owner string `json:"owner"` 99 } 100 101 // =================================================================================== 102 // Main 103 // =================================================================================== 104 func main() { 105 err := shim.Start(new(SimpleChaincode)) 106 if err != nil { 107 fmt.Printf("Error starting Simple chaincode: %s", err) 108 } 109 } 110 111 // Init initializes chaincode 112 // =========================== 113 func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 114 return shim.Success(nil) 115 } 116 117 // Invoke - Our entry point for Invocations 118 // ======================================== 119 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 120 function, args := stub.GetFunctionAndParameters() 121 fmt.Println("invoke is running " + function) 122 123 // Handle different functions 124 if function == "initMarble" { //create a new marble 125 return t.initMarble(stub, args) 126 } else if function == "transferMarble" { //change owner of a specific marble 127 return t.transferMarble(stub, args) 128 } else if function == "transferMarblesBasedOnColor" { //transfer all marbles of a certain color 129 return t.transferMarblesBasedOnColor(stub, args) 130 } else if function == "delete" { //delete a marble 131 return t.delete(stub, args) 132 } else if function == "readMarble" { //read a marble 133 return t.readMarble(stub, args) 134 } else if function == "queryMarblesByOwner" { //find marbles for owner X using rich query 135 return t.queryMarblesByOwner(stub, args) 136 } else if function == "queryMarbles" { //find marbles based on an ad hoc rich query 137 return t.queryMarbles(stub, args) 138 } else if function == "getHistoryForMarble" { //get history of values for a marble 139 return t.getHistoryForMarble(stub, args) 140 } else if function == "getMarblesByRange" { //get marbles based on range query 141 return t.getMarblesByRange(stub, args) 142 } 143 144 fmt.Println("invoke did not find func: " + function) //error 145 return shim.Error("Received unknown function invocation") 146 } 147 148 // ============================================================ 149 // initMarble - create a new marble, store into chaincode state 150 // ============================================================ 151 func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 152 var err error 153 154 // 0 1 2 3 155 // "asdf", "blue", "35", "bob" 156 if len(args) != 4 { 157 return shim.Error("Incorrect number of arguments. Expecting 4") 158 } 159 160 // ==== Input sanitation ==== 161 fmt.Println("- start init marble") 162 if len(args[0]) <= 0 { 163 return shim.Error("1st argument must be a non-empty string") 164 } 165 if len(args[1]) <= 0 { 166 return shim.Error("2nd argument must be a non-empty string") 167 } 168 if len(args[2]) <= 0 { 169 return shim.Error("3rd argument must be a non-empty string") 170 } 171 if len(args[3]) <= 0 { 172 return shim.Error("4th argument must be a non-empty string") 173 } 174 marbleName := args[0] 175 color := strings.ToLower(args[1]) 176 owner := strings.ToLower(args[3]) 177 size, err := strconv.Atoi(args[2]) 178 if err != nil { 179 return shim.Error("3rd argument must be a numeric string") 180 } 181 182 // ==== Check if marble already exists ==== 183 marbleAsBytes, err := stub.GetState(marbleName) 184 if err != nil { 185 return shim.Error("Failed to get marble: " + err.Error()) 186 } else if marbleAsBytes != nil { 187 fmt.Println("This marble already exists: " + marbleName) 188 return shim.Error("This marble already exists: " + marbleName) 189 } 190 191 // ==== Create marble object and marshal to JSON ==== 192 objectType := "marble" 193 marble := &marble{objectType, marbleName, color, size, owner} 194 marbleJSONasBytes, err := json.Marshal(marble) 195 if err != nil { 196 return shim.Error(err.Error()) 197 } 198 //Alternatively, build the marble json string manually if you don't want to use struct marshalling 199 //marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}` 200 //marbleJSONasBytes := []byte(str) 201 202 // === Save marble to state === 203 err = stub.PutState(marbleName, marbleJSONasBytes) 204 if err != nil { 205 return shim.Error(err.Error()) 206 } 207 208 // ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ==== 209 // An 'index' is a normal key/value entry in state. 210 // The key is a composite key, with the elements that you want to range query on listed first. 211 // In our case, the composite key is based on indexName~color~name. 212 // This will enable very efficient state range queries based on composite keys matching indexName~color~* 213 indexName := "color~name" 214 colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name}) 215 if err != nil { 216 return shim.Error(err.Error()) 217 } 218 // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. 219 // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value 220 value := []byte{0x00} 221 stub.PutState(colorNameIndexKey, value) 222 223 // ==== Marble saved and indexed. Return success ==== 224 fmt.Println("- end init marble") 225 return shim.Success(nil) 226 } 227 228 // =============================================== 229 // readMarble - read a marble from chaincode state 230 // =============================================== 231 func (t *SimpleChaincode) readMarble(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.GetState(name) //get the marble from chaincode state 241 if err != nil { 242 jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}" 243 return shim.Error(jsonResp) 244 } else if valAsbytes == nil { 245 jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}" 246 return shim.Error(jsonResp) 247 } 248 249 return shim.Success(valAsbytes) 250 } 251 252 // ================================================== 253 // delete - remove a marble key/value pair from state 254 // ================================================== 255 func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { 256 var jsonResp string 257 var marbleJSON marble 258 if len(args) != 1 { 259 return shim.Error("Incorrect number of arguments. Expecting 1") 260 } 261 marbleName := args[0] 262 263 // to maintain the color~name index, we need to read the marble first and get its color 264 valAsbytes, err := stub.GetState(marbleName) //get the marble from chaincode state 265 if err != nil { 266 jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}" 267 return shim.Error(jsonResp) 268 } else if valAsbytes == nil { 269 jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}" 270 return shim.Error(jsonResp) 271 } 272 273 err = json.Unmarshal([]byte(valAsbytes), &marbleJSON) 274 if err != nil { 275 jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}" 276 return shim.Error(jsonResp) 277 } 278 279 err = stub.DelState(marbleName) //remove the marble from chaincode state 280 if err != nil { 281 return shim.Error("Failed to delete state:" + err.Error()) 282 } 283 284 // maintain the index 285 indexName := "color~name" 286 colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name}) 287 if err != nil { 288 return shim.Error(err.Error()) 289 } 290 291 // Delete index entry to state. 292 err = stub.DelState(colorNameIndexKey) 293 if err != nil { 294 return shim.Error("Failed to delete state:" + err.Error()) 295 } 296 return shim.Success(nil) 297 } 298 299 // =========================================================== 300 // transfer a marble by setting a new owner name on the marble 301 // =========================================================== 302 func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 303 304 // 0 1 305 // "name", "bob" 306 if len(args) < 2 { 307 return shim.Error("Incorrect number of arguments. Expecting 2") 308 } 309 310 marbleName := args[0] 311 newOwner := strings.ToLower(args[1]) 312 fmt.Println("- start transferMarble ", marbleName, newOwner) 313 314 marbleAsBytes, err := stub.GetState(marbleName) 315 if err != nil { 316 return shim.Error("Failed to get marble:" + err.Error()) 317 } else if marbleAsBytes == nil { 318 return shim.Error("Marble does not exist") 319 } 320 321 marbleToTransfer := marble{} 322 err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse() 323 if err != nil { 324 return shim.Error(err.Error()) 325 } 326 marbleToTransfer.Owner = newOwner //change the owner 327 328 marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) 329 err = stub.PutState(marbleName, marbleJSONasBytes) //rewrite the marble 330 if err != nil { 331 return shim.Error(err.Error()) 332 } 333 334 fmt.Println("- end transferMarble (success)") 335 return shim.Success(nil) 336 } 337 338 // =========================================================================================== 339 // getMarblesByRange performs a range query based on the start and end keys provided. 340 341 // Read-only function results are not typically submitted to ordering. If the read-only 342 // results are submitted to ordering, or if the query is used in an update transaction 343 // and submitted to ordering, then the committing peers will re-execute to guarantee that 344 // result sets are stable between endorsement time and commit time. The transaction is 345 // invalidated by the committing peers if the result set has changed between endorsement 346 // time and commit time. 347 // Therefore, range queries are a safe option for performing update transactions based on query results. 348 // =========================================================================================== 349 func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { 350 351 if len(args) < 2 { 352 return shim.Error("Incorrect number of arguments. Expecting 2") 353 } 354 355 startKey := args[0] 356 endKey := args[1] 357 358 resultsIterator, err := stub.GetStateByRange(startKey, endKey) 359 if err != nil { 360 return shim.Error(err.Error()) 361 } 362 defer resultsIterator.Close() 363 364 // buffer is a JSON array containing QueryResults 365 var buffer bytes.Buffer 366 buffer.WriteString("[") 367 368 bArrayMemberAlreadyWritten := false 369 for resultsIterator.HasNext() { 370 queryResponse, err := resultsIterator.Next() 371 if err != nil { 372 return shim.Error(err.Error()) 373 } 374 // Add a comma before array members, suppress it for the first array member 375 if bArrayMemberAlreadyWritten == true { 376 buffer.WriteString(",") 377 } 378 buffer.WriteString("{\"Key\":") 379 buffer.WriteString("\"") 380 buffer.WriteString(queryResponse.Key) 381 buffer.WriteString("\"") 382 383 buffer.WriteString(", \"Record\":") 384 // Record is a JSON object, so we write as-is 385 buffer.WriteString(string(queryResponse.Value)) 386 buffer.WriteString("}") 387 bArrayMemberAlreadyWritten = true 388 } 389 buffer.WriteString("]") 390 391 fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) 392 393 return shim.Success(buffer.Bytes()) 394 } 395 396 // ==== Example: GetStateByPartialCompositeKey/RangeQuery ========================================= 397 // transferMarblesBasedOnColor will transfer marbles of a given color to a certain new owner. 398 // Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. 399 // Committing peers will re-execute range queries to guarantee that result sets are stable 400 // between endorsement time and commit time. The transaction is invalidated by the 401 // committing peers if the result set has changed between endorsement time and commit time. 402 // Therefore, range queries are a safe option for performing update transactions based on query results. 403 // =========================================================================================== 404 func (t *SimpleChaincode) transferMarblesBasedOnColor(stub shim.ChaincodeStubInterface, args []string) pb.Response { 405 406 // 0 1 407 // "color", "bob" 408 if len(args) < 2 { 409 return shim.Error("Incorrect number of arguments. Expecting 2") 410 } 411 412 color := args[0] 413 newOwner := strings.ToLower(args[1]) 414 fmt.Println("- start transferMarblesBasedOnColor ", color, newOwner) 415 416 // Query the color~name index by color 417 // This will execute a key range query on all keys starting with 'color' 418 coloredMarbleResultsIterator, err := stub.GetStateByPartialCompositeKey("color~name", []string{color}) 419 if err != nil { 420 return shim.Error(err.Error()) 421 } 422 defer coloredMarbleResultsIterator.Close() 423 424 // Iterate through result set and for each marble found, transfer to newOwner 425 var i int 426 for i = 0; coloredMarbleResultsIterator.HasNext(); i++ { 427 // Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key 428 responseRange, err := coloredMarbleResultsIterator.Next() 429 if err != nil { 430 return shim.Error(err.Error()) 431 } 432 433 // get the color and name from color~name composite key 434 objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) 435 if err != nil { 436 return shim.Error(err.Error()) 437 } 438 returnedColor := compositeKeyParts[0] 439 returnedMarbleName := compositeKeyParts[1] 440 fmt.Printf("- found a marble from index:%s color:%s name:%s\n", objectType, returnedColor, returnedMarbleName) 441 442 // Now call the transfer function for the found marble. 443 // Re-use the same function that is used to transfer individual marbles 444 response := t.transferMarble(stub, []string{returnedMarbleName, newOwner}) 445 // if the transfer failed break out of loop and return error 446 if response.Status != shim.OK { 447 return shim.Error("Transfer failed: " + response.Message) 448 } 449 } 450 451 responsePayload := fmt.Sprintf("Transferred %d %s marbles to %s", i, color, newOwner) 452 fmt.Println("- end transferMarblesBasedOnColor: " + responsePayload) 453 return shim.Success([]byte(responsePayload)) 454 } 455 456 // =======Rich queries ========================================================================= 457 // Two examples of rich queries are provided below (parameterized query and ad hoc query). 458 // Rich queries pass a query string to the state database. 459 // Rich queries are only supported by state database implementations 460 // that support rich query (e.g. CouchDB). 461 // The query string is in the syntax of the underlying state database. 462 // With rich queries there is no guarantee that the result set hasn't changed between 463 // endorsement time and commit time, aka 'phantom reads'. 464 // Therefore, rich queries should not be used in update transactions, unless the 465 // application handles the possibility of result set changes between endorsement and commit time. 466 // Rich queries can be used for point-in-time queries against a peer. 467 // ============================================================================================ 468 469 // ===== Example: Parameterized rich query ================================================= 470 // queryMarblesByOwner queries for marbles based on a passed in owner. 471 // This is an example of a parameterized query where the query logic is baked into the chaincode, 472 // and accepting a single query parameter (owner). 473 // Only available on state databases that support rich query (e.g. CouchDB) 474 // ========================================================================================= 475 func (t *SimpleChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response { 476 477 // 0 478 // "bob" 479 if len(args) < 1 { 480 return shim.Error("Incorrect number of arguments. Expecting 1") 481 } 482 483 owner := strings.ToLower(args[0]) 484 485 queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"marble\",\"owner\":\"%s\"}}", owner) 486 487 queryResults, err := getQueryResultForQueryString(stub, queryString) 488 if err != nil { 489 return shim.Error(err.Error()) 490 } 491 return shim.Success(queryResults) 492 } 493 494 // ===== Example: Ad hoc rich query ======================================================== 495 // queryMarbles uses a query string to perform a query for marbles. 496 // Query string matching state database syntax is passed in and executed as is. 497 // Supports ad hoc queries that can be defined at runtime by the client. 498 // If this is not desired, follow the queryMarblesForOwner example for parameterized queries. 499 // Only available on state databases that support rich query (e.g. CouchDB) 500 // ========================================================================================= 501 func (t *SimpleChaincode) queryMarbles(stub shim.ChaincodeStubInterface, args []string) pb.Response { 502 503 // 0 504 // "queryString" 505 if len(args) < 1 { 506 return shim.Error("Incorrect number of arguments. Expecting 1") 507 } 508 509 queryString := args[0] 510 511 queryResults, err := getQueryResultForQueryString(stub, queryString) 512 if err != nil { 513 return shim.Error(err.Error()) 514 } 515 return shim.Success(queryResults) 516 } 517 518 // ========================================================================================= 519 // getQueryResultForQueryString executes the passed in query string. 520 // Result set is built and returned as a byte array containing the JSON results. 521 // ========================================================================================= 522 func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { 523 524 fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) 525 526 resultsIterator, err := stub.GetQueryResult(queryString) 527 if err != nil { 528 return nil, err 529 } 530 defer resultsIterator.Close() 531 532 // buffer is a JSON array containing QueryRecords 533 var buffer bytes.Buffer 534 buffer.WriteString("[") 535 536 bArrayMemberAlreadyWritten := false 537 for resultsIterator.HasNext() { 538 queryResponse, err := resultsIterator.Next() 539 if err != nil { 540 return nil, err 541 } 542 // Add a comma before array members, suppress it for the first array member 543 if bArrayMemberAlreadyWritten == true { 544 buffer.WriteString(",") 545 } 546 buffer.WriteString("{\"Key\":") 547 buffer.WriteString("\"") 548 buffer.WriteString(queryResponse.Key) 549 buffer.WriteString("\"") 550 551 buffer.WriteString(", \"Record\":") 552 // Record is a JSON object, so we write as-is 553 buffer.WriteString(string(queryResponse.Value)) 554 buffer.WriteString("}") 555 bArrayMemberAlreadyWritten = true 556 } 557 buffer.WriteString("]") 558 559 fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) 560 561 return buffer.Bytes(), nil 562 } 563 564 func (t *SimpleChaincode) getHistoryForMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 565 566 if len(args) < 1 { 567 return shim.Error("Incorrect number of arguments. Expecting 1") 568 } 569 570 marbleName := args[0] 571 572 fmt.Printf("- start getHistoryForMarble: %s\n", marbleName) 573 574 resultsIterator, err := stub.GetHistoryForKey(marbleName) 575 if err != nil { 576 return shim.Error(err.Error()) 577 } 578 defer resultsIterator.Close() 579 580 // buffer is a JSON array containing historic values for the marble 581 var buffer bytes.Buffer 582 buffer.WriteString("[") 583 584 bArrayMemberAlreadyWritten := false 585 for resultsIterator.HasNext() { 586 response, err := resultsIterator.Next() 587 if err != nil { 588 return shim.Error(err.Error()) 589 } 590 // Add a comma before array members, suppress it for the first array member 591 if bArrayMemberAlreadyWritten == true { 592 buffer.WriteString(",") 593 } 594 buffer.WriteString("{\"TxId\":") 595 buffer.WriteString("\"") 596 buffer.WriteString(response.TxId) 597 buffer.WriteString("\"") 598 599 buffer.WriteString(", \"Value\":") 600 // if it was a delete operation on given key, then we need to set the 601 //corresponding value null. Else, we will write the response.Value 602 //as-is (as the Value itself a JSON marble) 603 if response.IsDelete { 604 buffer.WriteString("null") 605 } else { 606 buffer.WriteString(string(response.Value)) 607 } 608 609 buffer.WriteString(", \"Timestamp\":") 610 buffer.WriteString("\"") 611 buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String()) 612 buffer.WriteString("\"") 613 614 buffer.WriteString(", \"IsDelete\":") 615 buffer.WriteString("\"") 616 buffer.WriteString(strconv.FormatBool(response.IsDelete)) 617 buffer.WriteString("\"") 618 619 buffer.WriteString("}") 620 bArrayMemberAlreadyWritten = true 621 } 622 buffer.WriteString("]") 623 624 fmt.Printf("- getHistoryForMarble returning:\n%s\n", buffer.String()) 625 626 return shim.Success(buffer.Bytes()) 627 }