Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var CreateAsset = Transaction{ Tag: "createAsset", Label: "Create Asset", Description: "", Method: "POST", MetaTx: true, Args: ArgList{ { Tag: "asset", Description: "List of assets to be created.", DataType: "[]@asset", Required: true, }, }, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { assetList := req["asset"].([]interface{}) responses := []map[string]interface{}{} for _, assetInterface := range assetList { asset := assetInterface.(assets.Asset) res, err := asset.PutNew(stub) if err != nil { return nil, errors.WrapError(err, "failed to write asset to ledger") } responses = append(responses, res) } resBytes, err := json.Marshal(responses) if err != nil { return nil, errors.WrapError(err, "failed to marshal response") } return resBytes, nil }, }
CreateAsset is the transaction which creates a generic asset
var DeleteAsset = Transaction{ Tag: "deleteAsset", Label: "Delete Asset", Method: "DELETE", Description: "", MetaTx: true, Args: ArgList{ { Tag: "key", Description: "Key of the asset to be deleted.", DataType: "@key", Required: true, }, { Tag: "cascade", Description: "Delete all referrers on cascade", DataType: "boolean", }, }, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { key := req["key"].(assets.Key) cascade, ok := req["cascade"].(bool) if !ok { cascade = false } var err error var response []byte if cascade { response, err = key.DeleteCascade(stub) if err != nil { return nil, errors.WrapError(err, "failed to delete asset recursively") } } else { response, err = key.Delete(stub) if err != nil { return nil, errors.WrapError(err, "failed to delete asset") } } return response, nil }, }
DeleteAsset deletes an asset from the blockchain
var GetDataTypes = Transaction{ Tag: "getDataTypes", Label: "Get DataTypes", Description: "GetDataTypes returns the primary data type map", Method: "GET", ReadOnly: true, MetaTx: true, Args: ArgList{}, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { dataTypeMap := assets.DataTypeMap() dataTypeMapBytes, err := json.Marshal(dataTypeMap) if err != nil { return nil, errors.WrapErrorWithStatus(err, "error marshaling data type map", 500) } return dataTypeMapBytes, nil }, }
GetDataTypes returns the primitive data type map
var GetHeader = Transaction{ Tag: "getHeader", Label: "Get Header", Description: "", Method: "GET", ReadOnly: true, MetaTx: true, Args: ArgList{}, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { var err error colorMap := header.Colors nameMap := header.Title orgMSP, err := stub.GetMSPID() if err != nil { return nil, errors.WrapError(err, "failed to get MSP ID") } var colors []string colors, orgExists := colorMap[orgMSP] if !orgExists { colors = colorMap["@default"] } var orgTitle string orgTitle, orgExists = nameMap[orgMSP] if !orgExists { orgTitle = nameMap["@default"] } header := map[string]interface{}{ "name": header.Name, "version": header.Version, "orgMSP": orgMSP, "colors": colors, "orgTitle": orgTitle, "ccToolsVersion": header.CCToolsVersion, } headerBytes, err := json.Marshal(header) if err != nil { return nil, errors.WrapError(err, "failed to marshal header") } return headerBytes, nil }, }
GetHeader returns data in CCHeader
var GetSchema = Transaction{ Tag: "getSchema", Label: "Get Schema", Description: "", Method: "GET", ReadOnly: true, MetaTx: true, Args: ArgList{ { Tag: "assetType", DataType: "string", Description: "The name of the asset type of which you want to fetch the definition. Leave empty to fetch a list of possible types.", }, }, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { var assetTypeName string assetTypeInterface, ok := req["assetType"] if ok { assetTypeName, ok = assetTypeInterface.(string) if !ok { return nil, errors.NewCCError("argument 'assetType' must be a string", 400) } } if assetTypeName != "" { assetTypeDef := assets.FetchAssetType(assetTypeName) if assetTypeDef == nil { errMsg := fmt.Sprintf("asset type named %s does not exist", assetTypeName) return nil, errors.NewCCError(errMsg, 404) } assetDefBytes, err := json.Marshal(assetTypeDef) if err != nil { errMsg := fmt.Sprintf("error marshaling asset definition: %s", err) return nil, errors.NewCCError(errMsg, 500) } return assetDefBytes, nil } assetTypeList := assets.AssetTypeList() // If user requested asset list type assetListElem struct { Tag string `json:"tag"` Label string `json:"label"` Description string `json:"description"` Readers []string `json:"readers,omitempty"` Writers []string `json:"writers"` } var assetList []assetListElem for _, assetTypeDef := range assetTypeList { assetList = append(assetList, assetListElem{ Tag: assetTypeDef.Tag, Label: assetTypeDef.Label, Description: assetTypeDef.Description, Readers: assetTypeDef.Readers, }) } assetListBytes, err := json.Marshal(assetList) if err != nil { return nil, errors.WrapErrorWithStatus(err, "error marshaling asset list", 500) } return assetListBytes, nil }, }
GetSchema returns information about a specific AssetType or a list of every configured AssetType
var ReadAsset = Transaction{ Tag: "readAsset", Label: "Read Asset", Description: "", Method: "GET", MetaTx: true, Args: ArgList{ { Tag: "key", Description: "Key of the asset to be read.", DataType: "@key", Required: true, }, { Tag: "resolve", Description: "Resolve references recursively.", DataType: "boolean", }, }, ReadOnly: true, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { var assetJSON []byte var err error key := req["key"].(assets.Key) resolve, ok := req["resolve"].(bool) if ok && resolve { var asset map[string]interface{} asset, err = key.GetRecursive(stub) if err != nil { return nil, errors.WrapError(err, "failed to read asset from blockchain") } assetJSON, err = json.Marshal(asset) if err != nil { return nil, errors.WrapErrorWithStatus(err, "failed to serialize asset", 500) } } else { assetJSON, err = key.GetBytes(stub) if err != nil { return nil, errors.WrapError(err, "failed to get asset state") } } return assetJSON, nil }, }
ReadAsset fetches an asset from the blockchain
var ReadAssetHistory = Transaction{ Tag: "readAssetHistory", Label: "Read Asset History", Description: "", Method: "GET", MetaTx: true, Args: ArgList{ { Tag: "key", Description: "Key of the asset to be read.", DataType: "@key", Required: true, }, { Tag: "timeTarget", Description: "Optional parameter to retrieve specific version of the asset.", DataType: "datetime", }, }, ReadOnly: true, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { key := req["key"].(assets.Key) timeTarget := req["timeTarget"] historyIterator, err := stub.GetHistoryForKey(key.Key()) if err != nil { return nil, errors.WrapError(err, "failed to read asset from blockchain") } if historyIterator == nil { return nil, errors.NewCCError("history not found", 404) } defer historyIterator.Close() if !historyIterator.HasNext() { return nil, errors.NewCCError("history not found", 404) } if timeTarget == nil { response := make([]map[string]interface{}, 0) for historyIterator.HasNext() { queryResponse, err := historyIterator.Next() if err != nil { return nil, errors.WrapError(err, "error iterating response") } data := make(map[string]interface{}) if !queryResponse.IsDelete { err = json.Unmarshal(queryResponse.Value, &data) if err != nil { return nil, errors.WrapError(err, "failed to unmarshal queryResponse's values") } } data["_txId"] = queryResponse.TxId data["_isDelete"] = queryResponse.IsDelete data["_timestamp"] = queryResponse.Timestamp.AsTime().Format(time.RFC3339) response = append(response, data) } responseJSON, err := json.Marshal(response) if err != nil { return nil, errors.WrapError(err, "error marshaling response") } return responseJSON, nil } else { target := timeTarget.(time.Time) if target.After(time.Now()) { return nil, errors.NewCCError("timeTarget must be in the past", 400) } closestTime := time.Time{} response := make(map[string]interface{}) for historyIterator.HasNext() { queryResponse, err := historyIterator.Next() if err != nil { return nil, errors.WrapError(err, "error iterating response") } timestamp := queryResponse.Timestamp.AsTime() if timestamp.Before(target) && timestamp.After(closestTime) { closestTime = timestamp if !queryResponse.IsDelete { err = json.Unmarshal(queryResponse.Value, &response) if err != nil { return nil, errors.WrapError(err, "failed to unmarshal queryResponse's values") } } response["_txId"] = queryResponse.TxId response["_isDelete"] = queryResponse.IsDelete response["_timestamp"] = timestamp.Format(time.RFC3339) } } responseJSON, err := json.Marshal(response) if err != nil { return nil, errors.WrapError(err, "error marshaling response") } return responseJSON, nil } }, }
ReadAssetHistory fetches an asset key history from the blockchain
var Search = Transaction{ Tag: "search", Label: "Search World State", Description: "", Method: "GET", MetaTx: true, Args: ArgList{ { Tag: "query", Description: "Query string according to CouchDB specification: https://docs.couchdb.org/en/stable/api/database/find.html.", DataType: "@query", }, { Tag: "collection", Description: "Name of the private collection to be searched.", DataType: "string", }, { Tag: "resolve", Description: "Resolve references recursively.", DataType: "boolean", }, }, ReadOnly: true, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { var bookmark string var pageSize int32 var privateCollection string privateCollectionInterface, ok := req["collection"] if ok { privateCollection, ok = privateCollectionInterface.(string) if !ok { return nil, errors.NewCCError("optional argument 'collection' must be a string", 400) } } requestInterface, ok := req["query"] if !ok { return nil, errors.NewCCError("argument 'query' is required", 400) } request, ok := requestInterface.(map[string]interface{}) if !ok { return nil, errors.NewCCError("argument 'query' must be a JSON object", 400) } bookmarkInt, bookmarkExists := request["bookmark"] limit, limitExists := request["limit"] if limitExists { limit64, ok := limit.(float64) if !ok { return nil, errors.NewCCError("limit must be an integer", 400) } pageSize = int32(limit64) } if bookmarkExists { var ok bool bookmark, ok = bookmarkInt.(string) if !ok { return nil, errors.NewCCError("bookmark must be a string", 400) } } delete(request, "bookmark") delete(request, "limit") query, err := json.Marshal(request) if err != nil { return nil, errors.WrapErrorWithStatus(err, "failed marshaling JSON-encoded asset", 500) } queryString := string(query) var resultsIterator shim.StateQueryIteratorInterface var responseMetadata *pb.QueryResponseMetadata if !limitExists { if privateCollection == "" { resultsIterator, err = stub.GetQueryResult(queryString) } else { resultsIterator, err = stub.GetPrivateDataQueryResult(privateCollection, queryString) } } else { if privateCollection != "" { return nil, errors.NewCCError("private data pagination is not implemented", 501) } resultsIterator, responseMetadata, err = stub.GetQueryResultWithPagination(queryString, pageSize, bookmark) } if err != nil { return nil, errors.WrapErrorWithStatus(err, "failed to get query result", 500) } defer resultsIterator.Close() searchResult := make([]map[string]interface{}, 0) for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, errors.WrapErrorWithStatus(err, "error iterating response", 500) } var data map[string]interface{} err = json.Unmarshal(queryResponse.Value, &data) if err != nil { return nil, errors.WrapErrorWithStatus(err, "failed to unmarshal queryResponse values", 500) } resolve, ok := req["resolve"].(bool) if ok && resolve { key, err := assets.NewKey(data) if err != nil { return nil, errors.WrapError(err, "failed to create key object to resolve result") } asset, err := key.GetRecursive(stub) if err != nil { return nil, errors.WrapError(err, "failed to resolve result") } data = asset } searchResult = append(searchResult, data) } response := make(map[string]interface{}) if responseMetadata != nil { response["metadata"] = *responseMetadata } else { response["metadata"] = make(map[string]string) } response["result"] = searchResult responseJSON, err := json.Marshal(response) if err != nil { return nil, errors.WrapErrorWithStatus(err, "error marshaling response", 500) } return responseJSON, nil }, }
Search makes a rich query against CouchDB
var UpdateAsset = Transaction{ Tag: "updateAsset", Label: "Update Asset", Description: "", Method: "PUT", MetaTx: true, Args: ArgList{ { Tag: "update", Description: "Asset key and fields to be updated.", DataType: "@update", Required: true, }, }, Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) { var err error request := req["update"].(map[string]interface{}) key, _ := assets.NewKey(request) exists, err := key.ExistsInLedger(stub) if err != nil { return nil, errors.WrapError(err, "failed to check asset existance in ledger") } if !exists { return nil, errors.NewCCError("asset does not exist", 404) } response, err := key.Update(stub, request) if err != nil { return nil, errors.WrapError(err, "failed to update asset") } resBytes, err := json.Marshal(response) if err != nil { return nil, errors.WrapError(err, "failed to marshal response") } return resBytes, nil }, }
UpdateAsset is the function which updates a generic asset
Functions ¶
func InitHeader ¶
func InitHeader(h Header)
func InitTxList ¶
func InitTxList(l []Transaction)
InitTxList appends GetTx to txList to avoid initialization loop
func Run ¶
func Run(stub shim.ChaincodeStubInterface) ([]byte, errors.ICCError)
Run defines the rules of transaction execution for the chaincode.
func StartupCheck ¶
StartupCheck verifies if tx definitions are properly coded, returning an error if they're not.
Types ¶
type ArgList ¶
type ArgList []Argument
ArgList defines the type for argument list in Transaction object
type Argument ¶
type Argument struct { // Tag is the key of the value on the input map Tag string `json:"tag"` // Label is the name used in frontend Label string `json:"label"` // Description is a simple explanation of the argument Description string `json:"description"` // DataType can assume the following values: // Primary types: "string", "number", "integer", "boolean", "datetime" // Special types: // @asset: any asset type defined in the assets package // @key: key properties for any asset type defined in the assets package // @update: update request for any asset type defined in the assets package // @query: query string according to CouchDB specification: https://docs.couchdb.org/en/2.2.0/api/database/find.html // @object: arbitrary object // ->assetType: the specific asset type as defined by <assetType> in the assets packages // dataType: any specific data type format defined by the chaincode // []type: an array of elements specified by <type> as any of the above valid types // DataType string `json:"dataType"` // Tells if the argument is required Required bool `json:"required"` // Tells if the argument will be used for private data Private bool `json:"private"` }
Argument struct stores the transaction argument info describing this specific input
type Transaction ¶
type Transaction struct { // List of all MSPs allowed to run this transaction. // Regexp is supported by putting '$' before the MSP regexp e.g. []string{`$org\dMSP`}. // Please note this restriction DOES NOT protect ledger data from being // read by unauthorized organizations, this should be done with Private Data. Callers []string `json:"callers,omitempty"` // Tag is how the tx will be called. Tag string `json:"tag"` // Label is the pretty tx name for front-end rendering. Label string `json:"label"` // Description is a simple explanation describing what the tx does. Description string `json:"description"` // Args is a list of argument formats accepted by the tx. Args ArgList `json:"args"` // Method indicates the HTTP method which should be used to call the tx when using an HTTP API. Method string `json:"method"` // ReadOnly indicates that the tx does not alter the world state. ReadOnly bool `json:"readOnly"` // MetaTx indicates that the tx does not encode a business-specific rule, // but an internal process of the chaincode e.g. listing available asset types. MetaTx bool `json:"metaTx"` // Routine is the function called when running the tx. It is where the tx logic can be programmed. Routine func(*sw.StubWrapper, map[string]interface{}) ([]byte, errors.ICCError) `json:"-"` }
Transaction defines the object containing tx definitions
func FetchTx ¶
func FetchTx(txName string) *Transaction
FetchTx returns a pointer to the Transaction object or nil if tx is not found
func (Transaction) GetArgs ¶
func (tx Transaction) GetArgs(stub shim.ChaincodeStubInterface) (map[string]interface{}, errors.ICCError)
GetArgs validates the received arguments and assembles a map with the parsed key/values.