Writing Your First Chaincode

What is Chaincode?

Chaincode is a program, written in Go, Node.js, or Java that implements a prescribed interface. Chaincode runs in a separate process from the peer and initializes and manages the ledger state through transactions submitted by applications.

A chaincode typically handles business logic agreed to by members of the network, so it similar to a “smart contract”. A chaincode can be invoked to update or query the ledger in a proposal transaction. Given the appropriate permission, a chaincode may invoke another chaincode, either in the same channel or in different channels, to access its state. Note that, if the called chaincode is on a different channel from the calling chaincode, only read query is allowed. That is, the called chaincode on a different channel is only a Query, which does not participate in state validation checks in subsequent commit phase.

In the following sections, we will explore chaincode through the eyes of an application developer. We’ll present a asset-transfer chaincode sample walkthrough, and the purpose of each method in the Fabric Contract API. If you are a network operator who is deploying a chaincode to running network, visit the Deploying a smart contract to a channel tutorial and the Fabric chaincode lifecycle concept topic.

This tutorial provides an overview of the high level APIs provided by the Fabric Contract API. To learn more about developing smart contracts using the Fabric contract API, visit the developapps/smartcontract topic.

Fabric Contract API

The fabric-contract-api provides the contract interface, a high level API for application developers to implement Smart Contracts. Within Hyperledger Fabric, Smart Contracts are also known as Chaincode. Working with this API provides a high level entry point to writing business logic. Documentation of the Fabric Contract API for different languages can be found at the links below:

Note that when using the contract api, each chaincode function that is called is passed a transaction context “ctx”, from which you can get the chaincode stub (GetStub() ), which has functions to access the ledger (e.g. GetState() ) and make requests to update the ledger (e.g. PutState() ). You can learn more at the language-specific links below.

In this tutorial using Go chaincode, we will demonstrate the use of these APIs by implementing a asset-transfer chaincode application that manages simple “assets”.

Asset Transfer Chaincode

Our application is a basic sample chaincode to initialize a ledger with assets, create, read, update, and delete assets, check to see if an asset exists, and transfer assets from one owner to another.

Choosing a Location for the Code

If you haven’t been doing programming in Go, you may want to make sure that you have Go installed and your system properly configured. We assume you are using a version that supports modules.

Now, you will want to create a directory for your chaincode application.

To keep things simple, let’s use the following command:

  1. // atcc is shorthand for asset transfer chaincode
  2. mkdir atcc && cd atcc

Now, let’s create the module and the source file that we’ll fill in with code:

  1. go mod init atcc
  2. touch atcc.go

Housekeeping

First, let’s start with some housekeeping. As with every chaincode, it implements the fabric-contract-api interface, so let’s add the Go import statements for the necessary dependencies for our chaincode. We’ll import the fabric contract api package and define our SmartContract.

  1. package main
  2. import (
  3. "fmt"
  4. "encoding/json"
  5. "log"
  6. "github.com/hyperledger/fabric-contract-api-go/contractapi"
  7. )
  8. // SmartContract provides functions for managing an Asset
  9. type SmartContract struct {
  10. contractapi.Contract
  11. }

Next, let’s add a struct Asset to represent simple assets on the ledger. Note the JSON annotations, which will be used to marshal the asset to JSON which is stored on the ledger. JSON though is not a deterministic data format - the order of elements can change, whilst still representing the same data semantically. The challenge, therefore, is to be able to generate a consistent set of JSON. Below is also shown a good approach to achieve consistency which consists of creating an asset object struct following alphabetic order.

  1. // Asset describes basic details of what makes up a simple asset
  2. // Insert struct field in alphabetic order => to achieve determinism accross languages
  3. // golang keeps the order when marshal to json but doesn't order automatically
  4. type Asset struct {
  5. AppraisedValue int `json:"AppraisedValue"`
  6. Color string `json:"Color"`
  7. ID string `json:"ID"`
  8. Owner string `json:"Owner"`
  9. Size int `json:"Size"`
  10. }

Initializing the Chaincode

Next, we’ll implement the InitLedger function to populate the ledger with some initial data.

  1. // InitLedger adds a base set of assets to the ledger
  2. func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
  3. assets := []Asset{
  4. {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
  5. {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
  6. {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
  7. {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
  8. {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
  9. {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
  10. }
  11. for _, asset := range assets {
  12. assetJSON, err := json.Marshal(asset)
  13. if err != nil {
  14. return err
  15. }
  16. err = ctx.GetStub().PutState(asset.ID, assetJSON)
  17. if err != nil {
  18. return fmt.Errorf("failed to put to world state. %v", err)
  19. }
  20. }
  21. return nil
  22. }

Next, we write a function to create an asset on the ledger that does not yet exist. When writing chaincode, it is a good idea to check for the existence of something on the ledger prior to taking an action on it, as is demonstrated in the CreateAsset function below.

  1. // CreateAsset issues a new asset to the world state with given details.
  2. func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
  3. exists, err := s.AssetExists(ctx, id)
  4. if err != nil {
  5. return err
  6. }
  7. if exists {
  8. return fmt.Errorf("the asset %s already exists", id)
  9. }
  10. asset := Asset{
  11. ID: id,
  12. Color: color,
  13. Size: size,
  14. Owner: owner,
  15. AppraisedValue: appraisedValue,
  16. }
  17. assetJSON, err := json.Marshal(asset)
  18. if err != nil {
  19. return err
  20. }
  21. return ctx.GetStub().PutState(id, assetJSON)
  22. }

Now that we have populated the ledger with some initial assets and created an asset, let’s write a function ReadAsset that allows us to read an asset from the ledger.

  1. // ReadAsset returns the asset stored in the world state with given id.
  2. func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
  3. assetJSON, err := ctx.GetStub().GetState(id)
  4. if err != nil {
  5. return nil, fmt.Errorf("failed to read from world state: %v", err)
  6. }
  7. if assetJSON == nil {
  8. return nil, fmt.Errorf("the asset %s does not exist", id)
  9. }
  10. var asset Asset
  11. err = json.Unmarshal(assetJSON, &asset)
  12. if err != nil {
  13. return nil, err
  14. }
  15. return &asset, nil
  16. }

Now that we have assets on our ledger we can interact with, let’s write a chaincode function UpdateAsset that allows us to update attributes of the asset that we are allowed to change.

  1. // UpdateAsset updates an existing asset in the world state with provided parameters.
  2. func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
  3. exists, err := s.AssetExists(ctx, id)
  4. if err != nil {
  5. return err
  6. }
  7. if !exists {
  8. return fmt.Errorf("the asset %s does not exist", id)
  9. }
  10. // overwriting original asset with new asset
  11. asset := Asset{
  12. ID: id,
  13. Color: color,
  14. Size: size,
  15. Owner: owner,
  16. AppraisedValue: appraisedValue,
  17. }
  18. assetJSON, err := json.Marshal(asset)
  19. if err != nil {
  20. return err
  21. }
  22. return ctx.GetStub().PutState(id, assetJSON)
  23. }

There may be cases where we need the ability to delete an asset from the ledger, so let’s write a DeleteAsset function to handle that requirement.

  1. // DeleteAsset deletes an given asset from the world state.
  2. func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
  3. exists, err := s.AssetExists(ctx, id)
  4. if err != nil {
  5. return err
  6. }
  7. if !exists {
  8. return fmt.Errorf("the asset %s does not exist", id)
  9. }
  10. return ctx.GetStub().DelState(id)
  11. }

We said earlier that is was a good idea to check to see if an asset exists before taking an action on it, so let’s write a function called AssetExists to implement that requirement.

  1. // AssetExists returns true when asset with given ID exists in world state
  2. func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
  3. assetJSON, err := ctx.GetStub().GetState(id)
  4. if err != nil {
  5. return false, fmt.Errorf("failed to read from world state: %v", err)
  6. }
  7. return assetJSON != nil, nil
  8. }

Next, we’ll write a function we’ll call TransferAsset that enables the transfer of an asset from one owner to another.

  1. // TransferAsset updates the owner field of asset with given id in world state.
  2. func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
  3. asset, err := s.ReadAsset(ctx, id)
  4. if err != nil {
  5. return err
  6. }
  7. asset.Owner = newOwner
  8. assetJSON, err := json.Marshal(asset)
  9. if err != nil {
  10. return err
  11. }
  12. return ctx.GetStub().PutState(id, assetJSON)
  13. }

Let’s write a function we’ll call GetAllAssets that enables the querying of the ledger to return all of the assets on the ledger.

  1. // GetAllAssets returns all assets found in world state
  2. func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
  3. // range query with empty string for startKey and endKey does an
  4. // open-ended query of all assets in the chaincode namespace.
  5. resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
  6. if err != nil {
  7. return nil, err
  8. }
  9. defer resultsIterator.Close()
  10. var assets []*Asset
  11. for resultsIterator.HasNext() {
  12. queryResponse, err := resultsIterator.Next()
  13. if err != nil {
  14. return nil, err
  15. }
  16. var asset Asset
  17. err = json.Unmarshal(queryResponse.Value, &asset)
  18. if err != nil {
  19. return nil, err
  20. }
  21. assets = append(assets, &asset)
  22. }
  23. return assets, nil
  24. }

Note

The full chaincode sample below is presented as a way to to keep this tutorial as clear and straightforward as possible. In a real-world implementation, it is likely that packages will be segmented where a main package imports the chaincode package to allow for easy unit testing. To see what this looks like, see the asset-transfer Go chaincode in fabric-samples. If you look at assetTransfer.go, you will see that it contains package main and imports package chaincode defined in smartcontract.go and located at fabric-samples/asset-transfer-basic/chaincode-go/chaincode/.

Pulling it All Together

Finally, we need to add the main function, which will call the ContractChaincode.Start function. Here’s the whole chaincode program source.

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "github.com/hyperledger/fabric-contract-api-go/contractapi"
  7. )
  8. // SmartContract provides functions for managing an Asset
  9. type SmartContract struct {
  10. contractapi.Contract
  11. }
  12. // Asset describes basic details of what makes up a simple asset
  13. type Asset struct {
  14. ID string `json:"ID"`
  15. Color string `json:"color"`
  16. Size int `json:"size"`
  17. Owner string `json:"owner"`
  18. AppraisedValue int `json:"appraisedValue"`
  19. }
  20. // InitLedger adds a base set of assets to the ledger
  21. func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
  22. assets := []Asset{
  23. {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
  24. {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
  25. {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
  26. {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
  27. {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
  28. {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
  29. }
  30. for _, asset := range assets {
  31. assetJSON, err := json.Marshal(asset)
  32. if err != nil {
  33. return err
  34. }
  35. err = ctx.GetStub().PutState(asset.ID, assetJSON)
  36. if err != nil {
  37. return fmt.Errorf("failed to put to world state. %v", err)
  38. }
  39. }
  40. return nil
  41. }
  42. // CreateAsset issues a new asset to the world state with given details.
  43. func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
  44. exists, err := s.AssetExists(ctx, id)
  45. if err != nil {
  46. return err
  47. }
  48. if exists {
  49. return fmt.Errorf("the asset %s already exists", id)
  50. }
  51. asset := Asset{
  52. ID: id,
  53. Color: color,
  54. Size: size,
  55. Owner: owner,
  56. AppraisedValue: appraisedValue,
  57. }
  58. assetJSON, err := json.Marshal(asset)
  59. if err != nil {
  60. return err
  61. }
  62. return ctx.GetStub().PutState(id, assetJSON)
  63. }
  64. // ReadAsset returns the asset stored in the world state with given id.
  65. func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
  66. assetJSON, err := ctx.GetStub().GetState(id)
  67. if err != nil {
  68. return nil, fmt.Errorf("failed to read from world state: %v", err)
  69. }
  70. if assetJSON == nil {
  71. return nil, fmt.Errorf("the asset %s does not exist", id)
  72. }
  73. var asset Asset
  74. err = json.Unmarshal(assetJSON, &asset)
  75. if err != nil {
  76. return nil, err
  77. }
  78. return &asset, nil
  79. }
  80. // UpdateAsset updates an existing asset in the world state with provided parameters.
  81. func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
  82. exists, err := s.AssetExists(ctx, id)
  83. if err != nil {
  84. return err
  85. }
  86. if !exists {
  87. return fmt.Errorf("the asset %s does not exist", id)
  88. }
  89. // overwriting original asset with new asset
  90. asset := Asset{
  91. ID: id,
  92. Color: color,
  93. Size: size,
  94. Owner: owner,
  95. AppraisedValue: appraisedValue,
  96. }
  97. assetJSON, err := json.Marshal(asset)
  98. if err != nil {
  99. return err
  100. }
  101. return ctx.GetStub().PutState(id, assetJSON)
  102. }
  103. // DeleteAsset deletes an given asset from the world state.
  104. func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
  105. exists, err := s.AssetExists(ctx, id)
  106. if err != nil {
  107. return err
  108. }
  109. if !exists {
  110. return fmt.Errorf("the asset %s does not exist", id)
  111. }
  112. return ctx.GetStub().DelState(id)
  113. }
  114. // AssetExists returns true when asset with given ID exists in world state
  115. func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
  116. assetJSON, err := ctx.GetStub().GetState(id)
  117. if err != nil {
  118. return false, fmt.Errorf("failed to read from world state: %v", err)
  119. }
  120. return assetJSON != nil, nil
  121. }
  122. // TransferAsset updates the owner field of asset with given id in world state.
  123. func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
  124. asset, err := s.ReadAsset(ctx, id)
  125. if err != nil {
  126. return err
  127. }
  128. asset.Owner = newOwner
  129. assetJSON, err := json.Marshal(asset)
  130. if err != nil {
  131. return err
  132. }
  133. return ctx.GetStub().PutState(id, assetJSON)
  134. }
  135. // GetAllAssets returns all assets found in world state
  136. func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
  137. // range query with empty string for startKey and endKey does an
  138. // open-ended query of all assets in the chaincode namespace.
  139. resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
  140. if err != nil {
  141. return nil, err
  142. }
  143. defer resultsIterator.Close()
  144. var assets []*Asset
  145. for resultsIterator.HasNext() {
  146. queryResponse, err := resultsIterator.Next()
  147. if err != nil {
  148. return nil, err
  149. }
  150. var asset Asset
  151. err = json.Unmarshal(queryResponse.Value, &asset)
  152. if err != nil {
  153. return nil, err
  154. }
  155. assets = append(assets, &asset)
  156. }
  157. return assets, nil
  158. }
  159. func main() {
  160. assetChaincode, err := contractapi.NewChaincode(&SmartContract{})
  161. if err != nil {
  162. log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
  163. }
  164. if err := assetChaincode.Start(); err != nil {
  165. log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
  166. }
  167. }

Chaincode access control

Chaincode can utilize the client (submitter) certificate for access control decisions with ctx.GetStub().GetCreator(). Additionally the Fabric Contract API provides extension APIs that extract client identity from the submitter’s certificate that can be used for access control decisions, whether that is based on client identity itself, or the org identity, or on a client identity attribute.

For example an asset that is represented as a key/value may include the client’s identity as part of the value (for example as a JSON attribute indicating that asset owner), and only this client may be authorized to make updates to the key/value in the future. The client identity library extension APIs can be used within chaincode to retrieve this submitter information to make such access control decisions.

Managing external dependencies for chaincode written in Go

Your Go chaincode depends on Go packages (like the chaincode shim) that are not part of the standard library. The source to these packages must be included in your chaincode package when it is installed to a peer. If you have structured your chaincode as a module, the easiest way to do this is to “vendor” the dependencies with go mod vendor before packaging your chaincode.

  1. go mod tidy
  2. go mod vendor

This places the external dependencies for your chaincode into a local vendor directory.

Once dependencies are vendored in your chaincode directory, peer chaincode package and peer chaincode install operations will then include code associated with the dependencies into the chaincode package.

JSON determinism

Being able to predictably handle data formats is critical, and also the ability to search the data held within the blockchain.

Technical Problem

The format of the data that is stored in Fabric is at the discretion of the user. The lowest level API accepts a byte array and stores that - what this represents is not a concern to Fabric. The important thing is when simulating transactions, given the same inputs chaincode gives the same byte array. Otherwise, the endorsements may not all match and the transaction will either not be submitted or will be invalidated.

JSON is often used as the data format to store data on the ledger, and is required if using CouchDB queries.

JSON though is not a deterministic data format - the order of elements can change, whilst still representing the same data semantically. The challenge, therefore, is to be able to generate a consistent set of JSON.

A solution

Generate a consistent set of JSON across multiple languages. Each language have different features and libraries that you can use to convert an object to JSON. The best approach to achieve determinism across different languages is to choose a canonical way as a common guideline to format JSON. In order to get a consistent hash across languages you can format JSON in alphabetic order.

Golang

In Golang the encoding/json package is utilized to serialise a Struct Object into JSON. More specifically the Marshal function is used, the latter marshals maps in sorted key order and keeps structs in the order that the fields are declared. Since structs are marshaled in field declaration order, follow alphabetic order when defining a new structure.

Node.js

In Javascript, when serialising object into JSON, the function JSON.stringify() is commonly used. However, to achieve consistent results, a deterministic version of JSON.stringify() is needed; in this way it is possible to get a consistent hash from stringified results. json-stringify-deterministic is a good library to do so and can be used combined with sort-keys-recursive to attain alphabetic order too. Here for a more in-depth tutorial.

Java

Java provides several libraries to serialize an object into a JSON string. However not all of them provide consistency and ordering. The Gson library, for example, does not provide any consistency and should therefore be avoided for this application. On the other hand, the Genson library is a good fit for our purpose as it produces consistent JSON in alphabetic oreder.

You can find a good exemplification of this practise on the asset-transfer-basic chaincodes.

Note

This is only one of the many approaches which we think can be effective. When serialising you can utilise various methods to achieve consistency; nevertheless, considering the different characteristics of the programming languages used in Fabric, the alphabetic approach represents an easy and efficient solution to the problem. In conclusion, feel free to employ a different method if it best suites your needs. P.S. Don’t forget to let us know in the comments if you used a different approach.