code: func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response {
  
  	if len(args) < 2 {
  		return shim.Error("Incorrect number of arguments. Expecting 2")
  	}
  
  	startKey := args[0]
  	endKey := args[1]
  
  	resultsIterator, err := stub.GetStateByRange(startKey, endKey)
  	if err != nil {
  		return shim.Error(err.Error())
  	}
  	defer resultsIterator.Close()
  
  	buffer, err := constructQueryResponseFromIterator(resultsIterator)
  	if err != nil {
  		return shim.Error(err.Error())
  	}
  
  	fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String())
  
  	return shim.Success(buffer.Bytes())
  }
docs: // ===========================================================================================
  // getMarblesByRange performs a range query based on the start and end keys provided.
  // Read-only function results are not typically submitted to ordering. If the read-only
  // results are submitted to ordering, or if the query is used in an update transaction
  // and submitted to ordering, then the committing peers will re-execute to guarantee that
  // result sets are stable between endorsement time and commit time. The transaction is
  // invalidated by the committing peers if the result set has changed between endorsement
  // time and commit time.
  // Therefore, range queries are a safe option for performing update transactions based on query results.
  // ===========================================================================================