Hyperledger fabric smart contract writing (2) ledger operations

1. What is Chaincode

Chaincode is a program written in Go, Node.js, or Java that implements a prescribed interface. Chaincode runs in a process independent of peers, initializing and managing ledger state through transactions submitted by applications.

Chaincode usually handles the business logic after Channel member permission, so it is similar to a “smart contract”. Chaincode can be called within a transaction to update or query the ledger. Given the appropriate permissions, one chaincode can call another chaincode in the same channel or a different channel to access its state. Note that only read queries are allowed if the called chaincode is on a different channel than the calling chaincode. In other words, the called chain code on different channels is just a queryer and does not participate in the status verification check in the subsequent submission phase.

2. Chain code writing

Next, this article will introduce a chaincode sample walkthrough asset-transfer-basic, in the fabric-sample package.

In Smart Contract Writing (1), we have briefly introduced the relevant structure of the contractapi package of Fabric2.0 chain code and some common methods provided.

2.1 Contract Definition

First we define our contract SmartContract, define the Asset structure, as the asset use case on the ledger, JSON annotation, which will be used to convert the asset into JSON format and store it on the ledger. If you want to keep the JSON results consistent, you can create the asset object structure in alphabetical order.

// SmartContract provides functions for managing an Asset
typeSmartContract struct {
contractapi.Contract
}
type Asset struct {
AppraisedValue int `json:"AppraisedValue"`
Color string `json:"Color"`
ID string `json:"ID"`
Owner string `json:"Owner"`
Size int `json:"Size"`
}
2.2 Contract initialization

We define the InitLedger function to populate the ledger with some initial data. The InitLedger function is executed only once each time the chain code is deployed.

// InitLedger adds a base set of assets to the ledger
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []Asset{
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}

for _, asset := range assets {
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}

err = ctx.GetStub().PutState(asset.ID, assetJSON)
if err != nil {
return fmt.Errorf("failed to put to world state. %v", err)
}
}

return nil
}
2.3 Asset Creation

We define the CreateAsset function to create an asset on the ledger. Check if the asset exists before creating it.

// CreateAsset issues a new asset to the world state with given details.
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if exists {
return fmt.Errorf("the asset %s already exists", id)
}

asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}

return ctx.GetStub().PutState(id, assetJSON)
}
2.4 Asset deletion

We define the DeleteAsset function to delete an asset on the ledger. Check if the asset exists before deleting it.

// DeleteAsset deletes an given asset from the world state.
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}

return ctx.GetStub().DelState(id)
}
2.5 Asset Update

We define the UpdateAsset function to update an asset on the ledger. Check if the asset exists before updating.

// UpdateAsset updates an existing asset in the world state with provided parameters.
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}

// overwriting original asset with new asset
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}

return ctx.GetStub().PutState(id, assetJSON)
}

2.6 Asset Query

We define the ReadAsset function to query an asset on the ledger.

// ReadAsset returns the asset stored in the world state with given id.
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("the asset %s does not exist", id)
}

var asset Asset
err = json.Unmarshal(assetJSON, & asset)
if err != nil {
return nil, err
}

return & asset, nil
}
2.7 Asset Transaction

We define the TransferAsset function to trade an asset on the ledger.

// TransferAsset updates the owner field of asset with given id in world state, and returns the old owner.
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return "", err
}

oldOwner := asset.Owner
asset.Owner = newOwner

assetJSON, err := json.Marshal(asset)
if err != nil {
return "", err
}

err = ctx.GetStub().PutState(id, assetJSON)
if err != nil {
return "", err
}

return oldOwner, nil
}
2.8 Query all assets

We define the GetAllAssets function to query all assets on the ledger.

// GetAllAssets returns all assets found in world state
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
// range query with empty string for startKey and endKey does an
// open-ended query of all assets in the chaincode namespace.
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, err
}
defer resultsIterator.Close()

var assets []*Asset
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}

var asset Asset
err = json.Unmarshal(queryResponse.Value, & asset)
if err != nil {
return nil, err
}
assets = append(assets, & asset)
}

return assets, nil
}