transactions

package
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2021 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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

View Source
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

View Source
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

View Source
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

View Source
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

View Source
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

View Source
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

View Source
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

View Source
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

Run defines the rules of transaction execution for the chaincode.

func StartupCheck

func StartupCheck() errors.ICCError

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

func (ArgList) GetArgDef

func (l ArgList) GetArgDef(tag string) *Argument

GetArgDef fetches arg definition for arg with given tag

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 Header struct {
	Name           string
	Version        string
	Colors         map[string][]string
	Title          map[string]string
	CCToolsVersion string
}

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 TxList

func TxList() []Transaction

TxList returns a copy of the txList variable

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL