Clone the Repository
First, let’s clone GoFabric template chaincode.
git clone https://github.com/goledgerdev/template-cc.git
This chaincode is the basic source code with the main features of GoFabric’s chaincode design.
This source code is divided in two segments:
chaincode → this folder provides the source code of the Hyperledger Fabric Smart Contract (HLF chaincode),
with the data mapping of the assets and the transactions. This source code automatically imports the cc-tools
library.
rest-server → this folder provides de source code of the Hyperledger Fabric client, with all the endpoints
The Source Code
Now, considerer the following folders:
chaincode
├── assettypes # information that will be shared and processed by the transactions
│
├── customAssets.go
│
├── sampleBook.go
│
├── samplePerson.go
│
└── sampleSecret.go
├── txdefs # transactions code for the Hyperledger Fabric network
├── borrowBook.go
│
└── getHeader.go
Asset Definitions
Inside the assetTypes folders there are 2 examples: sampleBook and samplePerson. They represent the definition of
a book and a person, not suprisingly. Now let’s check the code for each one.
The samplePerson source-code has the following structure:
package assettypes
import (
"fmt"
"github.com/goledgerdev/cc-tools/assets"
)
// SamplePerson is the main demo asset for the template
var SamplePerson = assets.AssetType{
Tag: "samplePerson",
Label: "Sample Person",
Description: "",
Props: []assets.AssetProp{
{
Tag: "cpf",
Label: "CPF",
DataType: "cpf",
},
{
Tag: "name",
Label: "Asset Name",
Required: true,
IsKey: true,
DataType: "string",
Validate: func(name interface{}) error {
nameStr := name.(string)
if nameStr == "" {
return fmt.Errorf("name must be non-empty")
}
return nil
},
},
{
Tag: "readerScore",
Label: "Reader Score",
Required: true,
DataType: "number",
Writers: []string{`org1MSP`},
},
},
}
Each asset can several properties, here samplePerson has cpf, name and readerScore.
Property Definitions
Each property may have the following fields:
- Tag: Rest API json reference (required)
- Label: label to be shown in the app. (required)
- DataType: property data type. (required)
- IsKey: boolean field (true/false) to setup a key property (if only one property is defined as key, the asset will have a primary key, otherwise a composite key).
- Required: boolean field (true/false) to make property mandatory. If no Required field is defined, it defaults to false and the property will be optional.
- Writers: organizations array that have permissions to create/update the property. Each organization should end with ‘MSP’. Ex: ‘org1MSP’.
The sampleBook source code has the following structure:
package assettypes
import "github.com/goledgerdev/cc-tools/assets"
// SampleBook is the demo subAsset for the template
var SampleBook = assets.AssetType{
Tag: "sampleBook",
Label: "Sample Book",
Description: "",
Props: []assets.AssetProp{
{
Tag: "title",
Label: "Book Title",
Required: true,
IsKey: true,
DataType: "string",
},
{
Tag: "author",
Label: "Book Author",
Required: true,
IsKey: true,
DataType: "string",
},
{
Tag: "currentTenant",
Label: "Current Tenant",
DataType: "->samplePerson",
},
{
Tag: "genres",
Label: "Genres",
DataType: "[]string",
},
{
Tag: "published",
Label: "Publishment Date",
DataType: "datetime",
},
},
}
Supported Data Types
The DataType field can be defined with the following options:
string: text format. Ex: "John Smith"
number: float64 format. Ex: 200.34
datetime: date and hour in ISO 8601 format. Ex: 2021-03-12T19:23Z
boolean: boolean format. Ex: true || false
->{asset}: reference to a asset. Ex: ->samplePerson
[]->{asset}: reference to an asset list. Ex: []->sampleBook
Transaction Definitions
One of the best things about Chaincode Tools is that after defining the assets like we did above, all of them already have fully capable CRUD endpoints, included in the API, and if that’s enough for you, you don’t really need to define any transactions.
Now, if you wanted to write your own transactions that could be easily done. Below there is an example of a borrow book transaction that defines e person as the current tenant of a book.
package txdefs
import (
"encoding/json"
"github.com/goledgerdev/cc-tools/assets"
"github.com/goledgerdev/cc-tools/errors"
tx "github.com/goledgerdev/cc-tools/transactions"
"github.com/hyperledger/fabric/core/chaincode/shim"
)
// BorrowBook borrows a book
var BorrowBook = tx.Transaction{
Tag: "borrowBook",
Label: "Borrow Book",
Description: "",
Method: "POST",
MetaTx: true,
Args: []tx.Argument{
{
Tag: "bookTitle",
Description: "Book Name",
DataType: "string",
Required: true,
},
{
Tag: "bookAuthor",
Description: "Book Author",
DataType: "string",
Required: true,
},
{
Tag: "personName",
Description: "Person Name",
DataType: "string",
Required: true,
},
},
Routine: func(stub shim.ChaincodeStubInterface, req map[string]interface{}) ([]byte, errors.ICCError) {
bookTitle, ok := req["bookTitle"].(string)
if !ok {
return nil, errors.NewCCError("Failed to get bookName parameter", 500)
}
bookAuthor, ok := req["bookAuthor"].(string)
if !ok {
return nil, errors.NewCCError("Failed to get bookAuthor parameter", 500)
}
personName, ok := req["personName"].(assets.Key)
if !ok {
return nil, errors.NewCCError("Failed to get personName parameter", 500)
}
person, err := personName.Get(stub)
if err != nil {
return nil, errors.NewCCError(err.Message(), 500)
}
bookMap := map[string]interface{}{
"@assetType": "sampleBook",
"author": bookAuthor,
"title": bookTitle,
"currentTenant": person,
}
book, err := assets.NewAsset(bookMap)
if err != nil {
return nil, errors.WrapError(err, "failed to create asset")
}
res, err := assets.PutRecursive(stub, book)
if err != nil {
return nil, errors.WrapError(err, "failed to write asset to the ledger")
}
resBytes, e := json.Marshal(res)
if e != nil {
return nil, errors.WrapError(e, "failed to marshal response")
}
return resBytes, nil
},
}
Transactions like this will automatically become available in the API once defined.