[Blockchain] Blueprint for Smart Contract: smartcontract.go
포스트 난이도: HOO_Senior
# fabric-samples v2.5
If you're venturing into the world of Hyperledger Fabric, understanding the core components of a smart contract is essential. In this post, we’ll break down the key functions within the smartcontract.go file, which will help you not only build your own smart contract but also grasp the basics of chaincode development in Go.
https://github.com/hyperledger/fabric
Chaincode, commonly referred to as smart contracts in the Hyperledger Fabric ecosystem, is a critical piece that encapsulates business logic agreed upon by members of the blockchain network. The smartcontract.go file typically serves as the blueprint for these contracts, providing the necessary functions to interact with the blockchain.
Let’s walk through some of the primary functions you might encounter in smartcontract.go:
- InitLedger
- CreateAsset
- ReadAsset
- UpdateAsset
- DeleteAsset
- TransferAsset
# Imports
The package is named chaincode and imports the necessary libraries. The "encoding/json" package is used for converting Go objects to JSON and vice versa, which is essential for Go-based chaincode. The "fmt" package is used for formatting text, such as creating error messages. The contractapi package provides the interfaces and classes needed to define a chaincode smart contract for Hyperledger Fabric.
package chaincode
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
# SmartContract struct
The SmartContract struct embeds contractapi.Contract. This struct serves as the main entry point for your chaincode, and all your functions will be associated with it.
type SmartContract struct {
contractapi.Contract
}
# Asset struct
The Asset data model represents the structure that will be stored in the ledger. Each Asset has several fields, such as ID, Color, Size, Owner, and AppraisedValue. If you want to create your own smart contract, you might define a new struct here that represents the data model for your specific use case. For example, you could define a struct like Henry with fields such as Is and Handsome.
type Asset struct {
AppraisedValue int `json:"AppraisedValue"`
Color string `json:"Color"`
ID string `json:"ID"`
Owner string `json:"Owner"`
Size int `json:"Size"`
}
# InitLedger Function
This function initializes the ledger with a set of default asset entries. It is called when the chaincode is instantiated on the blockchain network. You can customize the assets array with your own data entries to populate the ledger at the start. Make sure to change the fields according to your new data model.
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []Asset{
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
// other assets...
}
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
}
# CreateAsset Function
This function allows a new Asset to be created and stored in the ledger. It first checks if an asset with the given ID already exists, and if not, it creates a new one. To customize this function, modify the parameters and asset creation logic according to your custom data model. Ensure that you adjust the parameters in the function signature and the asset creation process accordingly.
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)
}
# ReadAsset Function
This function retrieves an Asset from the ledger using its id. To customize this function, modify the return type and unmarshaling logic according to your custom data model.
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
}
# UpdateAsset Function
This function updates an existing Asset in the ledger with new details. It checks if the asset exists before performing the update. To custermize this function, adjust the parameters and the Asset creation logic according to your custom data model.
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)
}
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)
}
# DeleteAsset Function
This function deletes an Asset from the ledger using its id. The logic for deletion is straightforward, but if your smart contract needs specific deletion logic (e.g., transferring data before deletion), you may need to modify it accordingly.
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)
}
# AssetExists Function
This helper function checks whether an Asset exists in the ledger using its id. You might not need to change this unless your existence check requires more complex logic or additional criteria.
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read from world state: %v", err)
}
return assetJSON != nil, nil
}
# TransferAsset Function
This function transfers ownership of an Asset to a new owner. It updates the Owner field in the asset and returns the previous owner's name. Adjust the logic if your smart contract requires more complex transfer processes (e.g., multiple ownerships, conditional transfers).
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
}
# GetAllAssets Function
This function retrieves all Assets stored in the ledger. To modify this, it needs to return a different data structure or add filters or conditions to the data retrieval process.
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
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
}