Transactions
GoLedger CC-Tools transactions represent the GoLang methods that can modify the assets within the Blockchain ledger (Hyperledger Fabric Channel)
Hyperledger Fabric assets can only be created or modified by executing chaincode transactions inside endorsing peers.
Goledger CC-Tools library has a range of pre-defined transactions:
CreateAsset
- creation of a new assetUpdateAsset
- update an existing assetDeleteAsset
- removing an asset from the current ledger stateReadAsset
- read the asset in its last ledger statusReadAssetHistory
- history of an asset's ledger statusSearch
- asset listing
GoLedger CC-Tools library enables the creation of custom transactions.
cc-tools-demo
repository provides 3 example transactions:
CreateNewLibrary
- create a new asset of type LibraryGetBooksByAuthor
- returns assets Book given an Author nameGetNumberOfBooksFromLibrary
- returns the number of assets Book inside asset LibraryUpdateBookTenant
- update field currentTenant inside asset Book
The definition of assets is done in the chaincode/txdefs folder for GoLedger CC-Tools library.
The of files from the txdefs folder is shown below:
chaincode/
txdefs/ # transations folder
createNewLibrary.go # library creation
getBooksByAuthor.go # returns books by an author
getNumberOfBooksFromLibrary.go # returns the number of books at the library asset
updateBookTenant.go # changes de tenant of book
txList.go # list of custom transactions
Transaction definition
A custom transaction construction is done by creating a file inside the chaincode/txdefs folder
An asset has the following fields:
Tag
: string field to define the transaction name internally referenced by the code and by the Rest Api endpoints. It cannot have spaces or special characters. If it has the same name as a predefined transaction (createAsset, updateAsset, deleteAsset, readAsset, readAssetHistory or search), this transaction will be replaced by the one in the txdefs directory.Label
: string field to define the label to be used by external applications. Free text.Description
: asset description string field to be used by external applications. Free text.Method
: Rest API method type. It can be POST, GET, PUT or DELETE.Callers
: used to define which organizations can call this transaction.Args
: arguments. It has its own fields.Routine
: transaction source code.ReadOnly
: boolean field to indicate that the tx does not alter the world state.MetaTx
: boolean field to indicate that the tx does not encode a business-specific rule, but an internal process of the chaincode e.g. listing available asset types.
Transaction argument definition
A transaction has a set of customizable input arguments.
An argument has the following fields:
Tag
: string field to define the argument name internally referenced by the code and by the Rest Api endpoints. It cannot have spaces or special characters.Label
: string field to define the label to be used by external applications. Free text.Description
: asset description string field to be used by external applications. Free text.Required
: identifies if the argument is required. Boolean field.DataType
: property type. CC-Tools has the following default types: string, number, datetime, boolean and @object.Private
: boolean field to indicate that argument will be used for private data.
StubWrapper
The main purpose of the StubWrapper is to provide additional functionalities and simplify the development of chaincodes. The StubWrapper maintains a WriteSet to ensure that modifications made during the execution of a chaincode are properly reflected when querying the ledger state. Even if these changes have not been confirmed on the ledger yet, the StubWrapper records the pending modifications in the WriteSet. This allows subsequent queries to utilize the WriteSet to return the updated data, ensuring consistency and accuracy of information during the execution of the chaincode. The same applies to private data.
Transaction examples
cc-tools-demo
repository has the following custom transaction configuration:
chaincode/
txdefs/ # transations folder
createNewLibrary.go # library creation
getBooksByAuthor.go # returns books by an author
getNumberOfBooksFromLibrary.go # returns the number of books at the library asset
updateBookTenant.go # changes de tenant of book
txList.go # list of custom transactions
In addition to the files for each transaction that can be used by the Goledger CC-Tools library they also must be registered inside txList.go file
The definition of the CreateNewLibrary transaction is as follows:
var CreateNewLibrary = tx.Transaction{
Tag: "createNewLibrary",
Label: "Create New Library",
Description: "Create a New Library",
Method: "POST",
Callers: []string{"$org3MSP"}, // Only org3 can call this transaction
Args: []tx.Argument{
{
Tag: "name",
Label: "Name",
Description: "Name of the library",
DataType: "string",
Required: true,
},
},
Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) {
name, _ := req["name"].(string)
libraryMap := make(map[string]interface{})
libraryMap["@assetType"] = "library"
libraryMap["name"] = name
libraryAsset, err := assets.NewAsset(libraryMap)
if err != nil {
return nil, errors.WrapError(err, "Failed to create a new asset")
}
// Save the new library on channel
_, err = libraryAsset.PutNew(stub)
if err != nil {
return nil, errors.WrapError(err, "Error saving asset on blockchain")
}
// Marshal asset back to JSON format
libraryJSON, nerr := json.Marshal(libraryAsset)
if nerr != nil {
return nil, errors.WrapError(nil, "failed to encode asset to JSON format")
}
// Marshall message to be logged
logMsg, ok := json.Marshal(fmt.Sprintf("New library name: %s", name))
if ok != nil {
return nil, errors.WrapError(nil, "failed to encode asset to JSON format")
}
// Call event to log the message
events.CallEvent(stub, "createLibraryLog", logMsg)
return libraryJSON, nil
},
}
According to the description above, CreateNewLibrary transaction has the following characteristics:
- Only org3 can call this method
- POST method for Rest Api
- Argument name of type string is required.
- The transaction uses the NewAsset function to prepare a new asset (keys, etc) and the PutNew function to create the asset in the channel.
- A log event for the type
createLibraryLog
is called with the created library name
The definition of the GetBooksByAuthor transaction is as follows:
var GetBooksByAuthor = tx.Transaction{
Tag: "getBooksByAuthor",
Label: "Get Books by the Author Name",
Description: "Return all the books from an author",
Method: "GET",
Callers: []string{"$org1MSP", "$org2MSP", "$orgMSP"}, // Only org1 and org2 can call this transaction
Args: []tx.Argument{
{
Tag: "authorName",
Label: "Author Name",
Description: "Author Name",
DataType: "string",
Required: true,
},
{
Tag: "limit",
Label: "Limit",
Description: "Limit",
DataType: "number",
},
},
Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) {
authorName, _ := req["authorName"].(string)
limit, hasLimit := req["limit"].(float64)
if hasLimit && limit <= 0 {
return nil, errors.NewCCError("limit must be greater than 0", 400)
}
// Prepare couchdb query
query := map[string]interface{}{
"selector": map[string]interface{}{
"@assetType": "book",
"author": authorName,
},
}
if hasLimit {
query["limit"] = limit
}
var err error
response, err := assets.Search(stub, query, "", true)
if err != nil {
return nil, errors.WrapErrorWithStatus(err, "error searching for book's author", 500)
}
responseJSON, err := json.Marshal(response)
if err != nil {
return nil, errors.WrapErrorWithStatus(err, "error marshaling response", 500)
}
return responseJSON, nil
},
}
- Only org1 and org2 can call this method.
- GET method for Rest Api.
- Argument authorName of type string is required.
- Argument limit of type number is optional.
- The transaction uses the Search function to query Book assets from the ledger, filtering by author.
The definition of the UpdateBookTenant transaction is as follows:
var UpdateBookTenant = tx.Transaction{
Tag: "updateBookTenant",
Label: "Update Book Tenant",
Description: "Change the tenant of a book",
Method: "PUT",
Callers: []string{`$org\dMSP`}, // Any orgs can call this transaction
Args: []tx.Argument{
{
Tag: "book",
Label: "Book",
Description: "Book",
DataType: "->book",
Required: true,
},
{
Tag: "tenant",
Label: "tenant",
Description: "New tenant of the book",
DataType: "->person",
},
},
Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) {
bookKey, ok := req["book"].(assets.Key)
if !ok {
return nil, errors.WrapError(nil, "Parameter book must be an asset")
}
tenantKey, ok := req["tenant"].(assets.Key)
if !ok {
return nil, errors.WrapError(nil, "Parameter tenant must be an asset")
}
// Returns Book from channel
bookAsset, err := bookKey.Get(stub)
if err != nil {
return nil, errors.WrapError(err, "failed to get asset from the ledger")
}
bookMap := (map[string]interface{})(*bookAsset)
// Returns person from channel
tenantAsset, err := tenantKey.Get(stub)
if err != nil {
return nil, errors.WrapError(err, "failed to get asset from the ledger")
}
tenantMap := (map[string]interface{})(*tenantAsset)
updatedTenantKey := make(map[string]interface{})
updatedTenantKey["@assetType"] = "person"
updatedTenantKey["@key"] = tenantMap["@key"]
// Update data
bookMap["currentTenant"] = updatedTenantKey
bookMap, err = bookAsset.Update(stub, bookMap)
if err != nil {
return nil, errors.WrapError(err, "failed to update asset")
}
// Marshal asset back to JSON format
bookJSON, nerr := json.Marshal(bookMap)
if nerr != nil {
return nil, errors.WrapError(err, "failed to marshal response")
}
return bookJSON, nil
},
}
- Any organization that starts with "org" followed by a number (org1, org2, org3, org4, etc) can call this method.
- PUT method for Rest Api
- Argument book of type Book is mandatory.
- Argument person of type Person required.
- The transaction uses the Get function to read the the assets Book and Person information from the ledger
- The transaction uses the Update function to updates the asset information Book inside the ledger
The definition of the GetNumberOfBooksFromLibrary transaction is as follows:
var GetNumberOfBooksFromLibrary = tx.Transaction{
Tag: "getNumberOfBooksFromLibrary",
Label: "Get Number Of Books From Library",
Description: "Return the number of books of a library",
Method: "GET",
Callers: []string{"$org2MSP", "$orgMSP"}, // Only org2 can call this transactions
Args: []tx.Argument{
{
Tag: "library",
Label: "Library",
Description: "Library",
DataType: "->library",
Required: true,
},
},
Routine: func(stub *sw.StubWrapper, req map[string]interface{}) ([]byte, errors.ICCError) {
libraryKey, _ := req["library"].(assets.Key)
// Returns Library from channel
libraryMap, err := libraryKey.GetMap(stub)
if err != nil {
return nil, errors.WrapError(err, "failed to get asset from the ledger")
}
numberOfBooks := 0
books, ok := libraryMap["books"].([]interface{})
if ok {
numberOfBooks = len(books)
}
returnMap := make(map[string]interface{})
returnMap["numberOfBooks"] = numberOfBooks
// Marshal asset back to JSON format
returnJSON, nerr := json.Marshal(returnMap)
if nerr != nil {
return nil, errors.WrapError(err, "failed to marshal response")
}
return returnJSON, nil
},
}
- Only org2 can call this method.
- GET method for Rest Api
- Argument library of type Library is required.
- The transaction uses the Get function to read Library asset information from the ledger
Transaction list definition
The registration of the transactions that will be used by GoLedger CC-Tools library must be also done inside chaincode/txList.go file
var txList = []tx.Transaction{
tx.CreateAsset,
tx.UpdateAsset,
tx.DeleteAsset,
txdefs.CreateNewLibrary,
txdefs.GetNumberOfBooksFromLibrary,
txdefs.UpdateBookTenant,
txdefs.GetBooksByAuthor,
}
META-INF
Indexes allow a database to be queried without having to examine every row with every query, making them run faster and more efficiently. It's mandatory to create indexes for fields that will be used for sorted queries.
The JSON index files must be located under the path META-INF/statedb/couchdb/indexes which is located inside the directory where the chaincode resides. For example:
{
"index":{
"fields":[
{"published": "asc"}
]
},
"ddoc":"indexListarBooksAscDoc",
"name":"indexListarBooksAsc",
"type":"json"
}
See more on Create an index.