Refactor Asset Query Chaincode Into Idiomatic Go

Rewrites the chaincod in idiomatic Go and cleans up
the general implementation.

A future commit should push the chaincode logic itself
into a separate package as chaincode cannot be tested
when the logic is part of the main package.

Signed-off-by: Brett Logan <brett.t.logan@ibm.com>
This commit is contained in:
Brett Logan 2020-07-07 16:38:01 -04:00 committed by denyeart
parent 621a2c263e
commit 5f80da096c

View file

@ -2,89 +2,94 @@
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
*/ */
// ====CHAINCODE EXECUTION SAMPLES (CLI) ================== /*
====CHAINCODE EXECUTION SAMPLES (CLI) ==================
// ==== Invoke assets ==== ==== Invoke assets ====
// peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset1","blue","35","tom"]}' peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset1","blue","5","tom","35"]}'
// peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset2","red","50","tom"]}' peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset2","red","4","tom","50"]}'
// peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset3","blue","70","tom"]}' peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["CreateAsset","asset3","blue","6","tom","70"]}'
// peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["TransferAsset","asset2","jerry"]}' peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["TransferAsset","asset2","jerry"]}'
// peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["TransferAssetBasedOnColor","blue","jerry"]}' peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["TransferAssetByColor","blue","jerry"]}'
// peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["DeleteAsset","asset1"]}' peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["DeleteAsset","asset1"]}'
// ==== Query assets ==== ==== Query assets ====
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["ReadAsset","asset1"]}' peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["ReadAsset","asset1"]}'
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["GetAssetsByRange","asset1","asset3"]}' peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["GetAssetsByRange","asset1","asset3"]}'
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["GetAssetHistory","asset1"]}' peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["GetAssetHistory","asset1"]}'
// Rich Query (Only supported if CouchDB is used as state database): Rich Query (Only supported if CouchDB is used as state database):
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssetsByOwner","tom"]}' peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssetsByOwner","tom"]}'
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"owner\":\"tom\"}}"]}' peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"owner\":\"tom\"}}"]}'
// Rich Query with Pagination (Only supported if CouchDB is used as state database): Rich Query with Pagination (Only supported if CouchDB is used as state database):
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssetsWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}' peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssetsWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}'
// INDEXES TO SUPPORT COUCHDB RICH QUERIES INDEXES TO SUPPORT COUCHDB RICH QUERIES
//
// Indexes in CouchDB are required in order to make JSON queries efficient and are required for
// any JSON query with a sort. Indexes may be packaged alongside
// chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own
// text file with extension *.json with the index definition formatted in JSON following the
// CouchDB index JSON syntax as documented at:
// http://docs.couchdb.org/en/2.3.1/api/database/find.html#db-index
//
// This asset transfer ledger example chaincode demonstrates a packaged
// index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json.
//
// If you have access to the your peer's CouchDB state database in a development environment,
// you may want to iteratively test various indexes in support of your chaincode queries. You
// can use the CouchDB Fauxton interface or a command line curl utility to create and update
// indexes. Then once you finalize an index, include the index definition alongside your
// chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment
// to managed environments.
//
// In the examples below you can find index definitions that support asset transfer ledger
// chaincode queries, along with the syntax that you can use in development environments
// to create the indexes in the CouchDB Fauxton interface or a curl command line utility.
//
// Index for docType, owner. Indexes in CouchDB are required in order to make JSON queries efficient and are required for
// any JSON query with a sort. Indexes may be packaged alongside
// Example curl command line to define index in the CouchDB channel_chaincode database chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index text file with extension *.json with the index definition formatted in JSON following the
// CouchDB index JSON syntax as documented at:
http://docs.couchdb.org/en/2.3.1/api/database/find.html#db-index
// Index for docType, owner, size (descending order). This asset transfer ledger example chaincode demonstrates a packaged
// index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json.
// Example curl command line to define index in the CouchDB channel_chaincode database
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index
// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): If you have access to the your peer's CouchDB state database in a development environment,
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' you may want to iteratively test various indexes in support of your chaincode queries. You
can use the CouchDB Fauxton interface or a command line curl utility to create and update
indexes. Then once you finalize an index, include the index definition alongside your
chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment
to managed environments.
// Rich Query with index design doc specified only (Only supported if CouchDB is used as state database): In the examples below you can find index definitions that support asset transfer ledger
// peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":{\"$eq\":\"asset\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}' chaincode queries, along with the syntax that you can use in development environments
to create the indexes in the CouchDB Fauxton interface or a curl command line utility.
Index for docType, owner.
Example curl command line to define index in the CouchDB channel_chaincode database
curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index
Index for docType, owner, size (descending order).
Example curl command line to define index in the CouchDB channel_chaincode database:
curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_assets/_index
Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database):
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":\"asset\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}'
Rich Query with index design doc specified only (Only supported if CouchDB is used as state database):
peer chaincode query -C myc1 -n asset_transfer -c '{"Args":["QueryAssets","{\"selector\":{\"docType\":{\"$eq\":\"asset\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}'
*/
package main package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "log"
"time" "time"
"github.com/golang/protobuf/ptypes"
"github.com/hyperledger/fabric-chaincode-go/shim" "github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi" "github.com/hyperledger/fabric-contract-api-go/contractapi"
) )
// SimpleChaincode example simple Chaincode implementation const index = "color~name"
// SimpleChaincode implements the fabric-contract-api-go programming model
type SimpleChaincode struct { type SimpleChaincode struct {
contractapi.Contract contractapi.Contract
} }
type asset struct { type Asset struct {
ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database DocType string `json:"docType"` //docType is used to distinguish the various types of objects in state database
ID string `json:"ID"` //the fieldtags are needed to keep case from bouncing around ID string `json:"ID"` //the field tags are needed to keep case from bouncing around
Color string `json:"color"` Color string `json:"color"`
Size int `json:"size"` Size int `json:"size"`
Owner string `json:"owner"` Owner string `json:"owner"`
@ -93,69 +98,68 @@ type asset struct {
// QueryResult structure used for handling result of query // QueryResult structure used for handling result of query
type QueryResult struct { type QueryResult struct {
Record *asset Record *Asset
TxId string `json:"txId"` TxId string `json:"txID"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
FetchedRecordsCount int `json:"fetchedRecordsCount"` FetchedRecordsCount int `json:"fetchedRecordsCount"`
Bookmark string `json:"bookmark"` Bookmark string `json:"bookmark"`
} }
// CreateAsset - create a new asset, store into chaincode state // CreateAsset initializes a new asset in the ledger
func (t *SimpleChaincode) CreateAsset(ctx contractapi.TransactionContextInterface, ID, color, owner string, size, appraisedValue int) error { func (t *SimpleChaincode) CreateAsset(ctx contractapi.TransactionContextInterface, assetID, color string, size int, owner string, appraisedValue int) error {
exists, err := t.AssetExists(ctx, assetID)
exists, err := t.AssetExists(ctx, ID)
if err != nil { if err != nil {
return fmt.Errorf("Failed to get asset: " + err.Error()) return fmt.Errorf("failed to get asset: %v", err)
} else if exists { }
return fmt.Errorf("This asset already exists: " + ID) if exists {
return fmt.Errorf("asset already exists: %s", assetID)
} }
objectType := "asset" asset := &Asset{
asset := &asset{ DocType: "asset",
ObjectType: objectType, ID: assetID,
ID: ID,
Color: color, Color: color,
Size: size, Size: size,
Owner: owner, Owner: owner,
AppraisedValue: appraisedValue, AppraisedValue: appraisedValue,
} }
assetJSON, err := json.Marshal(asset) assetBytes, err := json.Marshal(asset)
if err != nil { if err != nil {
return err return err
} }
err = ctx.GetStub().PutState(ID, assetJSON) err = ctx.GetStub().PutState(assetID, assetBytes)
if err != nil { if err != nil {
return err return err
} }
// ==== Index the asset to enable color-based range queries, e.g. return all blue assets ==== // Create an index to enable color-based range queries, e.g. return all blue assets.
// An 'index' is a normal key/value entry in state. // An 'index' is a normal key-value entry in the ledger.
// The key is a composite key, with the elements that you want to range query on listed first. // The key is a composite key, with the elements that you want to range query on listed first.
// In our case, the composite key is based on indexName~color~name. // In our case, the composite key is based on indexName~color~name.
// This will enable very efficient state range queries based on composite keys matching indexName~color~* // This will enable very efficient state range queries based on composite keys matching indexName~color~*
indexName := "color~name" colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(index, []string{asset.Color, asset.ID})
colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(indexName, []string{asset.Color, asset.ID})
if err != nil { if err != nil {
return err return err
} }
// Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the asset. // Save index entry to world state. Only the key name is needed, no need to store a duplicate copy of the asset.
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
value := []byte{0x00} value := []byte{0x00}
return ctx.GetStub().PutState(colorNameIndexKey, value) return ctx.GetStub().PutState(colorNameIndexKey, value)
} }
// ReadAsset - read a asset from chaincode state // ReadAsset retrieves an asset from the ledger
func (t *SimpleChaincode) ReadAsset(ctx contractapi.TransactionContextInterface, ID string) (*asset, error) { func (t *SimpleChaincode) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(ID) assetBytes, err := ctx.GetStub().GetState(assetID)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to get asset %s: %v", assetID, err)
} else if assetJSON == nil { }
return nil, fmt.Errorf("%s does not exist", ID) if assetBytes == nil {
return nil, fmt.Errorf("asset %s does not exist", assetID)
} }
asset := new(asset) var asset *Asset
err = json.Unmarshal(assetJSON, asset) err = json.Unmarshal(assetBytes, &asset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -163,83 +167,61 @@ func (t *SimpleChaincode) ReadAsset(ctx contractapi.TransactionContextInterface,
return asset, nil return asset, nil
} }
// DeleteAsset - remove a asset key/value pair from state // DeleteAsset removes an asset key-value pair from the ledger
func (t *SimpleChaincode) DeleteAsset(ctx contractapi.TransactionContextInterface, assetID string) error { func (t *SimpleChaincode) DeleteAsset(ctx contractapi.TransactionContextInterface, assetID string) error {
asset, err := t.ReadAsset(ctx, assetID)
// to maintain the color~name index, we need to read the asset first and get its color
assetJSON, err := ctx.GetStub().GetState(assetID) //get the asset from chaincode state
if err != nil { if err != nil {
return fmt.Errorf("Failed to get state for %s", assetID) return err
} else if assetJSON == nil {
return fmt.Errorf("Asset does not exist %s", assetID)
} }
var asset asset err = ctx.GetStub().DelState(assetID)
err = json.Unmarshal([]byte(assetJSON), &asset)
if err != nil { if err != nil {
return fmt.Errorf("Failed to decode JSON of %s", assetID) return fmt.Errorf("failed to delete asset %s: %v", assetID, err)
} }
err = ctx.GetStub().DelState(assetID) //remove the asset from chaincode state colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(index, []string{asset.Color, asset.ID})
if err != nil { if err != nil {
return fmt.Errorf("Failed to delete state:" + err.Error()) return err
} }
// maintain the index // Delete index entry
indexName := "color~name"
colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(indexName, []string{asset.Color, asset.ID})
if err != nil {
return fmt.Errorf(err.Error())
}
// Delete index entry to state.
return ctx.GetStub().DelState(colorNameIndexKey) return ctx.GetStub().DelState(colorNameIndexKey)
} }
// TransferAsset transfers a asset by setting a new owner name on the asset // TransferAsset transfers an asset by setting a new owner name on the asset
func (t *SimpleChaincode) TransferAsset(ctx contractapi.TransactionContextInterface, assetID, newOwner string) error { func (t *SimpleChaincode) TransferAsset(ctx contractapi.TransactionContextInterface, assetID, newOwner string) error {
newOwner = strings.ToLower(newOwner) asset, err := t.ReadAsset(ctx, assetID)
assetAsBytes, err := ctx.GetStub().GetState(assetID)
if err != nil { if err != nil {
return fmt.Errorf("Failed to get asset:" + err.Error()) return err
} else if assetAsBytes == nil {
return fmt.Errorf("Asset does not exist")
} }
assetToTransfer := asset{} asset.Owner = newOwner
err = json.Unmarshal(assetAsBytes, &assetToTransfer) assetBytes, err := json.Marshal(asset)
if err != nil { if err != nil {
return fmt.Errorf(err.Error()) return err
} }
assetToTransfer.Owner = newOwner //change the owner
assetJSON, _ := json.Marshal(assetToTransfer) return ctx.GetStub().PutState(assetID, assetBytes)
return ctx.GetStub().PutState(assetID, assetJSON)
} }
// constructQueryResponseFromIterator constructs a JSON array containing query results from // constructQueryResponseFromIterator constructs a slice of query results from the resultsIterator
// a given result iterator
func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) ([]*QueryResult, error) { func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) ([]*QueryResult, error) {
var results []*QueryResult
resp := []*QueryResult{}
for resultsIterator.HasNext() { for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next() queryResponse, err := resultsIterator.Next()
if err != nil { if err != nil {
return nil, err return nil, err
} }
newRecord := new(QueryResult) var result *QueryResult
err = json.Unmarshal(queryResponse.Value, newRecord) err = json.Unmarshal(queryResponse.Value, &result)
if err != nil { if err != nil {
return nil, err return nil, err
} }
results = append(results, result)
resp = append(resp, newRecord)
} }
return resp, nil return results, nil
} }
// GetAssetsByRange performs a range query based on the start and end keys provided. // GetAssetsByRange performs a range query based on the start and end keys provided.
@ -260,48 +242,46 @@ func (t *SimpleChaincode) GetAssetsByRange(ctx contractapi.TransactionContextInt
return constructQueryResponseFromIterator(resultsIterator) return constructQueryResponseFromIterator(resultsIterator)
} }
// TransferAssetBasedOnColor will transfer assets of a given color to a certain new owner. // TransferAssetByColor will transfer assets of a given color to a certain new owner.
// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. // Uses GetStateByPartialCompositeKey (range query) against color~name 'index'.
// Committing peers will re-execute range queries to guarantee that result sets are stable // Committing peers will re-execute range queries to guarantee that result sets are stable
// between endorsement time and commit time. The transaction is invalidated by the // 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. // 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. // Therefore, range queries are a safe option for performing update transactions based on query results.
// Example: GetStateByPartialCompositeKey/RangeQuery // Example: GetStateByPartialCompositeKey/RangeQuery
func (t *SimpleChaincode) TransferAssetBasedOnColor(ctx contractapi.TransactionContextInterface, color, newOwner string) error { func (t *SimpleChaincode) TransferAssetByColor(ctx contractapi.TransactionContextInterface, color, newOwner string) error {
newOwner = strings.ToLower(newOwner) // Execute a key range query on all keys starting with 'color'
coloredAssetResultsIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(index, []string{color})
// Query the color~name index by color
// This will execute a key range query on all keys starting with 'color'
coloredAssetResultsIterator, err := ctx.GetStub().GetStateByPartialCompositeKey("color~name", []string{color})
if err != nil { if err != nil {
return fmt.Errorf(err.Error()) return err
} }
defer coloredAssetResultsIterator.Close() defer coloredAssetResultsIterator.Close()
// Iterate through result set and for each asset found, transfer to newOwner for coloredAssetResultsIterator.HasNext() {
var i int
for i = 0; coloredAssetResultsIterator.HasNext(); i++ {
// Note that we don't get the value (2nd return variable), we'll just get the asset name from the composite key
responseRange, err := coloredAssetResultsIterator.Next() responseRange, err := coloredAssetResultsIterator.Next()
if err != nil { if err != nil {
return fmt.Errorf(err.Error()) return err
} }
// get the color and name from color~name composite key
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(responseRange.Key) _, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(responseRange.Key)
if err != nil { if err != nil {
return fmt.Errorf(err.Error()) return err
} }
if len(compositeKeyParts) > 1 { if len(compositeKeyParts) > 1 {
returnedAssetID := compositeKeyParts[1] returnedAssetID := compositeKeyParts[1]
asset, err := t.ReadAsset(ctx, returnedAssetID)
// Now call the transfer function for the found asset.
// Re-use the same function that is used to transfer individual assets
err = t.TransferAsset(ctx, returnedAssetID, newOwner)
// if the transfer failed break out of loop and return error
if err != nil { if err != nil {
return fmt.Errorf("Transfer failed: %v", err) return err
}
asset.Owner = newOwner
assetBytes, err := json.Marshal(asset)
if err != nil {
return err
}
err = ctx.GetStub().PutState(returnedAssetID, assetBytes)
if err != nil {
return fmt.Errorf("transfer failed for asset %s: %v", returnedAssetID, err)
} }
} }
} }
@ -309,14 +289,13 @@ func (t *SimpleChaincode) TransferAssetBasedOnColor(ctx contractapi.TransactionC
return nil return nil
} }
// QueryAssetsByOwner queries for assets based on a passed in owner. // QueryAssetsByOwner queries for assets based on the owners name.
// This is an example of a parameterized query where the query logic is baked into the chaincode, // This is an example of a parameterized query where the query logic is baked into the chaincode,
// and accepting a single query parameter (owner). // and accepting a single query parameter (owner).
// Only available on state databases that support rich query (e.g. CouchDB) // Only available on state databases that support rich query (e.g. CouchDB)
// Example: Parameterized rich query // Example: Parameterized rich query
func (t *SimpleChaincode) QueryAssetsByOwner(ctx contractapi.TransactionContextInterface, owner string) ([]*QueryResult, error) { func (t *SimpleChaincode) QueryAssetsByOwner(ctx contractapi.TransactionContextInterface, owner string) ([]*QueryResult, error) {
queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"asset\",\"owner\":\"%s\"}}", owner) queryString := fmt.Sprintf(`{"selector":{"docType":"asset","owner":"%s"}}`, owner)
return getQueryResultForQueryString(ctx, queryString) return getQueryResultForQueryString(ctx, queryString)
} }
@ -331,9 +310,8 @@ func (t *SimpleChaincode) QueryAssets(ctx contractapi.TransactionContextInterfac
} }
// getQueryResultForQueryString executes the passed in query string. // getQueryResultForQueryString executes the passed in query string.
// Result set is built and returned as a byte array containing the JSON results. // The result set is built and returned as a byte array containing the JSON results.
func getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, queryString string) ([]*QueryResult, error) { func getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, queryString string) ([]*QueryResult, error) {
resultsIterator, err := ctx.GetStub().GetQueryResult(queryString) resultsIterator, err := ctx.GetStub().GetQueryResult(queryString)
if err != nil { if err != nil {
return nil, err return nil, err
@ -343,13 +321,17 @@ func getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, q
return constructQueryResponseFromIterator(resultsIterator) return constructQueryResponseFromIterator(resultsIterator)
} }
// GetAssetsByRangeWithPagination performs a range query based on the start & end key, // GetAssetsByRangeWithPagination performs a range query based on the start and end key,
// page size and a bookmark. // page size and a bookmark.
// The number of fetched records will be equal to or lesser than the page size. // The number of fetched records will be equal to or lesser than the page size.
// Paginated range queries are only valid for read only transactions. // Paginated range queries are only valid for read only transactions.
// Example: Pagination with Range Query // Example: Pagination with Range Query
func (t *SimpleChaincode) GetAssetsByRangeWithPagination(ctx contractapi.TransactionContextInterface, startKey, func (t *SimpleChaincode) GetAssetsByRangeWithPagination(
endKey, bookmark string, pageSize int) ([]*QueryResult, error) { ctx contractapi.TransactionContextInterface,
startKey,
endKey,
bookmark string,
pageSize int) ([]*QueryResult, error) {
resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark)
if err != nil { if err != nil {
@ -368,14 +350,22 @@ func (t *SimpleChaincode) GetAssetsByRangeWithPagination(ctx contractapi.Transac
// Only available on state databases that support rich query (e.g. CouchDB) // Only available on state databases that support rich query (e.g. CouchDB)
// Paginated queries are only valid for read only transactions. // Paginated queries are only valid for read only transactions.
// Example: Pagination with Ad hoc Rich Query // Example: Pagination with Ad hoc Rich Query
func (t *SimpleChaincode) QueryAssetsWithPagination(ctx contractapi.TransactionContextInterface, queryString, func (t *SimpleChaincode) QueryAssetsWithPagination(
bookmark string, pageSize int) ([]*QueryResult, error) { ctx contractapi.TransactionContextInterface,
queryString,
bookmark string,
pageSize int) ([]*QueryResult, error) {
return getQueryResultForQueryStringWithPagination(ctx, queryString, int32(pageSize), bookmark) return getQueryResultForQueryStringWithPagination(ctx, queryString, int32(pageSize), bookmark)
} }
// getQueryResultForQueryStringWithPagination executes the passed in query string with // getQueryResultForQueryStringWithPagination executes the passed in query string with
// pagination info. Result set is built and returned as a byte array containing the JSON results. // pagination info. The result set is built and returned as a byte array containing the JSON results.
func getQueryResultForQueryStringWithPagination(ctx contractapi.TransactionContextInterface, queryString string, pageSize int32, bookmark string) ([]*QueryResult, error) { func getQueryResultForQueryStringWithPagination(
ctx contractapi.TransactionContextInterface,
queryString string,
pageSize int32,
bookmark string) ([]*QueryResult, error) {
resultsIterator, _, err := ctx.GetStub().GetQueryResultWithPagination(queryString, pageSize, bookmark) resultsIterator, _, err := ctx.GetStub().GetQueryResultWithPagination(queryString, pageSize, bookmark)
if err != nil { if err != nil {
@ -388,30 +378,32 @@ func getQueryResultForQueryStringWithPagination(ctx contractapi.TransactionConte
// GetAssetHistory returns the chain of custody for an asset since issuance. // GetAssetHistory returns the chain of custody for an asset since issuance.
func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]QueryResult, error) { func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]QueryResult, error) {
resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID) resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resultsIterator.Close() defer resultsIterator.Close()
records := []QueryResult{} var records []QueryResult
for resultsIterator.HasNext() { for resultsIterator.HasNext() {
response, err := resultsIterator.Next() response, err := resultsIterator.Next()
if err != nil { if err != nil {
return nil, err return nil, err
} }
asset := new(asset) var asset *Asset
err = json.Unmarshal(response.Value, asset) err = json.Unmarshal(response.Value, &asset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
timestamp, err := ptypes.Timestamp(response.Timestamp)
if err != nil {
return nil, err
}
record := QueryResult{ record := QueryResult{
TxId: response.TxId, TxId: response.TxId,
Timestamp: time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)), Timestamp: timestamp,
Record: asset, Record: asset,
} }
records = append(records, record) records = append(records, record)
@ -420,36 +412,36 @@ func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInte
return records, nil return records, nil
} }
// AssetExists returns true when asset with given ID exists in world state // AssetExists returns true when asset with given ID exists in the ledger.
func (t *SimpleChaincode) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) { func (t *SimpleChaincode) AssetExists(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id) assetBytes, err := ctx.GetStub().GetState(assetID)
if err != nil { if err != nil {
return false, fmt.Errorf("Failed to read from world state. %s", err.Error()) return false, fmt.Errorf("failed to read asset %s from world state. %v", assetID, err)
} }
return assetJSON != nil, nil return assetBytes != nil, nil
} }
// InitLedger creates sample assets in the ledger // InitLedger creates the initial set of assets in the ledger.
func (t *SimpleChaincode) InitLedger(ctx contractapi.TransactionContextInterface) error { func (t *SimpleChaincode) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []asset{ assets := []Asset{
asset{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}, {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
asset{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400}, {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
asset{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500}, {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
asset{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600}, {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
asset{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700}, {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
asset{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800}, {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
} }
for _, asset := range assets { for _, asset := range assets {
assetJSON, err := json.Marshal(asset) assetBytes, err := json.Marshal(asset)
if err != nil { if err != nil {
return err return err
} }
err = ctx.GetStub().PutState(asset.ID, assetJSON) err = ctx.GetStub().PutState(asset.ID, assetBytes)
if err != nil { if err != nil {
return fmt.Errorf("Failed to put to world state. %s", err.Error()) return fmt.Errorf("failed to put to world state. %v", err)
} }
} }
@ -457,16 +449,12 @@ func (t *SimpleChaincode) InitLedger(ctx contractapi.TransactionContextInterface
} }
func main() { func main() {
chaincode, err := contractapi.NewChaincode(&SimpleChaincode{})
chaincode, err := contractapi.NewChaincode(new(SimpleChaincode))
if err != nil { if err != nil {
fmt.Printf("Error creating asset chaincode: %s", err.Error()) log.Panicf("Error creating asset chaincode: %v", err)
return
} }
if err := chaincode.Start(); err != nil { if err := chaincode.Start(); err != nil {
fmt.Printf("Error starting asset chaincode: %s", err.Error()) log.Panicf("Error starting asset chaincode: %v", err)
return
} }
} }