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