From 5f80da096cb160c047a216d23c583cd357cc2253 Mon Sep 17 00:00:00 2001 From: Brett Logan Date: Tue, 7 Jul 2020 16:38:01 -0400 Subject: [PATCH] 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 --- .../asset_transfer_ledger_chaincode.go | 372 +++++++++--------- 1 file changed, 180 insertions(+), 192 deletions(-) diff --git a/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go b/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go index ac6daeb7..c413429c 100644 --- a/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go +++ b/asset-transfer-ledger-queries/chaincode-go/asset_transfer_ledger_chaincode.go @@ -2,89 +2,94 @@ SPDX-License-Identifier: Apache-2.0 */ -// ====CHAINCODE EXECUTION SAMPLES (CLI) ================== +/* +====CHAINCODE EXECUTION SAMPLES (CLI) ================== -// ==== 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","asset2","red","50","tom"]}' -// 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":["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":["DeleteAsset","asset1"]}' +==== Invoke assets ==== +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","4","tom","50"]}' +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":["TransferAssetByColor","blue","jerry"]}' +peer chaincode invoke -C myc1 -n asset_transfer -c '{"Args":["DeleteAsset","asset1"]}' -// ==== Query assets ==== -// 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":["GetAssetHistory","asset1"]}' +==== Query assets ==== +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":["GetAssetHistory","asset1"]}' -// 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":["QueryAssets","{\"selector\":{\"owner\":\"tom\"}}"]}' +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":["QueryAssets","{\"selector\":{\"owner\":\"tom\"}}"]}' -// 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",""]}' +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",""]}' -// 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. -// +INDEXES TO SUPPORT COUCHDB RICH QUERIES -// 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 -// +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 -// 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 +This asset transfer ledger example chaincode demonstrates a packaged +index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json. -// 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\"]}"]}' +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. -// 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\"}"]}' +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. + +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 import ( "encoding/json" "fmt" - "strings" + "log" "time" + "github.com/golang/protobuf/ptypes" "github.com/hyperledger/fabric-chaincode-go/shim" "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 { contractapi.Contract } -type asset struct { - ObjectType 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 +type Asset struct { + DocType string `json:"docType"` //docType is used to distinguish the various types of objects in state database + ID string `json:"ID"` //the field tags are needed to keep case from bouncing around Color string `json:"color"` Size int `json:"size"` Owner string `json:"owner"` @@ -93,69 +98,68 @@ type asset struct { // QueryResult structure used for handling result of query type QueryResult struct { - Record *asset - TxId string `json:"txId"` + Record *Asset + TxId string `json:"txID"` Timestamp time.Time `json:"timestamp"` FetchedRecordsCount int `json:"fetchedRecordsCount"` Bookmark string `json:"bookmark"` } -// CreateAsset - create a new asset, store into chaincode state -func (t *SimpleChaincode) CreateAsset(ctx contractapi.TransactionContextInterface, ID, color, owner string, size, appraisedValue int) error { - - exists, err := t.AssetExists(ctx, ID) +// CreateAsset initializes a new asset in the ledger +func (t *SimpleChaincode) CreateAsset(ctx contractapi.TransactionContextInterface, assetID, color string, size int, owner string, appraisedValue int) error { + exists, err := t.AssetExists(ctx, assetID) if err != nil { - return fmt.Errorf("Failed to get asset: " + err.Error()) - } else if exists { - return fmt.Errorf("This asset already exists: " + ID) + return fmt.Errorf("failed to get asset: %v", err) + } + if exists { + return fmt.Errorf("asset already exists: %s", assetID) } - objectType := "asset" - asset := &asset{ - ObjectType: objectType, - ID: ID, + asset := &Asset{ + DocType: "asset", + ID: assetID, Color: color, Size: size, Owner: owner, AppraisedValue: appraisedValue, } - assetJSON, err := json.Marshal(asset) + assetBytes, err := json.Marshal(asset) if err != nil { return err } - err = ctx.GetStub().PutState(ID, assetJSON) + err = ctx.GetStub().PutState(assetID, assetBytes) if err != nil { return err } - // ==== Index the asset to enable color-based range queries, e.g. return all blue assets ==== - // An 'index' is a normal key/value entry in state. + // Create an index to enable color-based range queries, e.g. return all blue assets. + // 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. // 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~* - indexName := "color~name" - colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(indexName, []string{asset.Color, asset.ID}) + colorNameIndexKey, err := ctx.GetStub().CreateCompositeKey(index, []string{asset.Color, asset.ID}) if err != nil { 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 value := []byte{0x00} return ctx.GetStub().PutState(colorNameIndexKey, value) } -// ReadAsset - read a asset from chaincode state -func (t *SimpleChaincode) ReadAsset(ctx contractapi.TransactionContextInterface, ID string) (*asset, error) { - assetJSON, err := ctx.GetStub().GetState(ID) +// ReadAsset retrieves an asset from the ledger +func (t *SimpleChaincode) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) { + assetBytes, err := ctx.GetStub().GetState(assetID) if err != nil { - return nil, err - } else if assetJSON == nil { - return nil, fmt.Errorf("%s does not exist", ID) + return nil, fmt.Errorf("failed to get asset %s: %v", assetID, err) + } + if assetBytes == nil { + return nil, fmt.Errorf("asset %s does not exist", assetID) } - asset := new(asset) - err = json.Unmarshal(assetJSON, asset) + var asset *Asset + err = json.Unmarshal(assetBytes, &asset) if err != nil { return nil, err } @@ -163,83 +167,61 @@ func (t *SimpleChaincode) ReadAsset(ctx contractapi.TransactionContextInterface, 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 { - - // 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 + asset, err := t.ReadAsset(ctx, assetID) if err != nil { - return fmt.Errorf("Failed to get state for %s", assetID) - } else if assetJSON == nil { - return fmt.Errorf("Asset does not exist %s", assetID) + return err } - var asset asset - err = json.Unmarshal([]byte(assetJSON), &asset) + err = ctx.GetStub().DelState(assetID) 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 { - return fmt.Errorf("Failed to delete state:" + err.Error()) + return err } - // maintain the index - 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. + // Delete index entry 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 { - newOwner = strings.ToLower(newOwner) - - assetAsBytes, err := ctx.GetStub().GetState(assetID) + asset, err := t.ReadAsset(ctx, assetID) if err != nil { - return fmt.Errorf("Failed to get asset:" + err.Error()) - } else if assetAsBytes == nil { - return fmt.Errorf("Asset does not exist") + return err } - assetToTransfer := asset{} - err = json.Unmarshal(assetAsBytes, &assetToTransfer) + asset.Owner = newOwner + assetBytes, err := json.Marshal(asset) 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, assetJSON) + return ctx.GetStub().PutState(assetID, assetBytes) } -// constructQueryResponseFromIterator constructs a JSON array containing query results from -// a given result iterator +// constructQueryResponseFromIterator constructs a slice of query results from the resultsIterator func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) ([]*QueryResult, error) { - - resp := []*QueryResult{} - + var results []*QueryResult for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } - newRecord := new(QueryResult) - err = json.Unmarshal(queryResponse.Value, newRecord) + var result *QueryResult + err = json.Unmarshal(queryResponse.Value, &result) if err != nil { return nil, err } - - resp = append(resp, newRecord) + results = append(results, result) } - return resp, nil + return results, nil } // 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) } -// TransferAssetBasedOnColor will transfer assets of a given color to a certain new owner. -// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. +// TransferAssetByColor will transfer assets of a given color to a certain new owner. +// Uses GetStateByPartialCompositeKey (range query) against color~name 'index'. // 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 // 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. // Example: GetStateByPartialCompositeKey/RangeQuery -func (t *SimpleChaincode) TransferAssetBasedOnColor(ctx contractapi.TransactionContextInterface, color, newOwner string) error { - newOwner = strings.ToLower(newOwner) - - // 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}) +func (t *SimpleChaincode) TransferAssetByColor(ctx contractapi.TransactionContextInterface, color, newOwner string) error { + // Execute a key range query on all keys starting with 'color' + coloredAssetResultsIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(index, []string{color}) if err != nil { - return fmt.Errorf(err.Error()) + return err } defer coloredAssetResultsIterator.Close() - // Iterate through result set and for each asset found, transfer to newOwner - 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 + for coloredAssetResultsIterator.HasNext() { responseRange, err := coloredAssetResultsIterator.Next() 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) if err != nil { - return fmt.Errorf(err.Error()) + return err } if len(compositeKeyParts) > 1 { returnedAssetID := compositeKeyParts[1] - - // 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 + asset, err := t.ReadAsset(ctx, returnedAssetID) 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 } -// 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, // and accepting a single query parameter (owner). // Only available on state databases that support rich query (e.g. CouchDB) // Example: Parameterized rich query 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) } @@ -331,9 +310,8 @@ func (t *SimpleChaincode) QueryAssets(ctx contractapi.TransactionContextInterfac } // 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) { - resultsIterator, err := ctx.GetStub().GetQueryResult(queryString) if err != nil { return nil, err @@ -343,13 +321,17 @@ func getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, q 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. // 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. // Example: Pagination with Range Query -func (t *SimpleChaincode) GetAssetsByRangeWithPagination(ctx contractapi.TransactionContextInterface, startKey, - endKey, bookmark string, pageSize int) ([]*QueryResult, error) { +func (t *SimpleChaincode) GetAssetsByRangeWithPagination( + ctx contractapi.TransactionContextInterface, + startKey, + endKey, + bookmark string, + pageSize int) ([]*QueryResult, error) { resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) 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) // Paginated queries are only valid for read only transactions. // Example: Pagination with Ad hoc Rich Query -func (t *SimpleChaincode) QueryAssetsWithPagination(ctx contractapi.TransactionContextInterface, queryString, - bookmark string, pageSize int) ([]*QueryResult, error) { +func (t *SimpleChaincode) QueryAssetsWithPagination( + ctx contractapi.TransactionContextInterface, + queryString, + bookmark string, + pageSize int) ([]*QueryResult, error) { + return getQueryResultForQueryStringWithPagination(ctx, queryString, int32(pageSize), bookmark) } // getQueryResultForQueryStringWithPagination executes the passed in query string with -// pagination info. 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) { +// 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) { resultsIterator, _, err := ctx.GetStub().GetQueryResultWithPagination(queryString, pageSize, bookmark) if err != nil { @@ -388,30 +378,32 @@ func getQueryResultForQueryStringWithPagination(ctx contractapi.TransactionConte // GetAssetHistory returns the chain of custody for an asset since issuance. func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]QueryResult, error) { - resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID) if err != nil { return nil, err } defer resultsIterator.Close() - records := []QueryResult{} - + var records []QueryResult for resultsIterator.HasNext() { response, err := resultsIterator.Next() if err != nil { return nil, err } - asset := new(asset) - err = json.Unmarshal(response.Value, asset) + var asset *Asset + err = json.Unmarshal(response.Value, &asset) if err != nil { return nil, err } + timestamp, err := ptypes.Timestamp(response.Timestamp) + if err != nil { + return nil, err + } record := QueryResult{ TxId: response.TxId, - Timestamp: time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)), + Timestamp: timestamp, Record: asset, } records = append(records, record) @@ -420,36 +412,36 @@ func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInte return records, nil } -// AssetExists returns true when asset with given ID exists in world state -func (t *SimpleChaincode) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) { - assetJSON, err := ctx.GetStub().GetState(id) +// AssetExists returns true when asset with given ID exists in the ledger. +func (t *SimpleChaincode) AssetExists(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) { + assetBytes, err := ctx.GetStub().GetState(assetID) 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 { - assets := []asset{ - asset{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}, - asset{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400}, - asset{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500}, - asset{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600}, - asset{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700}, - asset{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800}, + assets := []Asset{ + {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}, + {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400}, + {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500}, + {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600}, + {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700}, + {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800}, } for _, asset := range assets { - assetJSON, err := json.Marshal(asset) + assetBytes, err := json.Marshal(asset) if err != nil { return err } - err = ctx.GetStub().PutState(asset.ID, assetJSON) + err = ctx.GetStub().PutState(asset.ID, assetBytes) 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() { - - chaincode, err := contractapi.NewChaincode(new(SimpleChaincode)) - + chaincode, err := contractapi.NewChaincode(&SimpleChaincode{}) if err != nil { - fmt.Printf("Error creating asset chaincode: %s", err.Error()) - return + log.Panicf("Error creating asset chaincode: %v", err) } if err := chaincode.Start(); err != nil { - fmt.Printf("Error starting asset chaincode: %s", err.Error()) - return + log.Panicf("Error starting asset chaincode: %v", err) } }