Module 3: Exercises and Assessment
Practical Exercises
Exercise 1: Creating Your First Chaincode
Objective: Develop, package, and test a simple chaincode that manages basic assets.
Tasks:
1. Create a new directory for your chaincode project:
bash
mkdir -p ~/first-chaincode/
cd ~/first-chaincode/
- Create the chaincode files: ```bash # Create go.mod file cat > go.mod << EOF module first-chaincode
go 1.16
require ( github.com/hyperledger/fabric-chaincode-go v0.0.0-20210718160520-38d29fabecb9 github.com/hyperledger/fabric-contract-api-go v1.1.1 ) EOF
# Create main.go file cat > main.go << EOF package main
import ( "encoding/json" "fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// SimpleAsset implements a simple chaincode to manage an asset type SimpleAsset struct { contractapi.Contract }
// Asset describes basic details of an asset type Asset struct { ID string `json:"id"` Value string `json:"value"` Owner string `json:"owner"` }
// InitLedger adds a base set of assets to the ledger func (s *SimpleAsset) InitLedger(ctx contractapi.TransactionContextInterface) error { assets := []Asset{ {ID: "asset1", Value: "blue", Owner: "Tom"}, {ID: "asset2", Value: "red", Owner: "Jane"}, {ID: "asset3", Value: "green", Owner: "Alex"}, }
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 issues a new asset to the world state with given details func (s *SimpleAsset) CreateAsset(ctx contractapi.TransactionContextInterface, id string, value string, owner string) 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,
Value: value,
Owner: owner,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// ReadAsset returns the asset stored in the world state with given id func (s SimpleAsset) 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 updates an existing asset in the world state with provided parameters func (s *SimpleAsset) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, value string, owner 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) }
asset := Asset{
ID: id,
Value: value,
Owner: owner,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// DeleteAsset deletes an asset from the world state func (s *SimpleAsset) 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 returns true when asset with given ID exists in world state func (s *SimpleAsset) 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
}
// GetAllAssets returns all assets found in world state func (s SimpleAsset) 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
}
func main() { assetChaincode, err := contractapi.NewChaincode(&SimpleAsset{}) if err != nil { fmt.Printf("Error creating asset-transfer chaincode: %v\n", err) return }
if err := assetChaincode.Start(); err != nil {
fmt.Printf("Error starting asset-transfer chaincode: %v\\n", err)
}
} EOF ```
- Test your chaincode using the Fabric test network: ```bash cd ~/fabric-samples/test-network
# Start the test network if not already running ./network.sh up createChannel -c mychannel
# Package and deploy the chaincode ./network.sh deployCC -ccn simple-asset -ccp ~/first-chaincode -ccl go ```
- Interact with your chaincode: ```bash # Set environment variables export PATH=${PWD}/../bin:$PATH export FABRIC_CFG_PATH=$PWD/../config/ export CORE_PEER_TLS_ENABLED=true export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051
# Initialize the ledger peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n simple-asset --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"InitLedger","Args":[]}'
# Create a new asset peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n simple-asset --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAsset","Args":["asset4", "yellow", "Sam"]}'
# Query an asset peer chaincode query -C mychannel -n simple-asset -c '{"function":"ReadAsset","Args":["asset4"]}'
# Get all assets peer chaincode query -C mychannel -n simple-asset -c '{"function":"GetAllAssets","Args":[]}' ```
Deliverable: A working chaincode implementation with documentation of your testing process and results.
Exercise 2: Implementing Private Data Collections
Objective: Extend your chaincode to use private data collections for sensitive information.
Tasks: 1. Create a private data collection configuration file: ```bash cd ~/first-chaincode/
# Create collections_config.json cat > collections_config.json << EOF [ { "name": "assetPrivateDetails", "policy": "OR('Org1MSP.member', 'Org2MSP.member')", "requiredPeerCount": 0, "maxPeerCount": 3, "blockToLive": 100000, "memberOnlyRead": true, "memberOnlyWrite": true } ] EOF ```
- Modify your chaincode to handle private data: ```bash # Create an updated version of the chaincode cat > main.go << EOF package main
import ( "encoding/json" "fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// SimpleAsset implements a simple chaincode to manage an asset type SimpleAsset struct { contractapi.Contract }
// Asset describes basic details of an asset type Asset struct { ID string `json:"id"` Value string `json:"value"` Owner string `json:"owner"` }
// AssetPrivateDetails contains private details about an asset type AssetPrivateDetails struct { ID string `json:"id"` Price int `json:"price"` Note string `json:"note"` }
// InitLedger adds a base set of assets to the ledger func (s *SimpleAsset) InitLedger(ctx contractapi.TransactionContextInterface) error { assets := []Asset{ {ID: "asset1", Value: "blue", Owner: "Tom"}, {ID: "asset2", Value: "red", Owner: "Jane"}, {ID: "asset3", Value: "green", Owner: "Alex"}, }
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 issues a new asset to the world state with given details func (s *SimpleAsset) CreateAsset(ctx contractapi.TransactionContextInterface, id string, value string, owner string) 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,
Value: value,
Owner: owner,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// CreateAssetWithPrivateData issues a new asset to the world state with given details and private data func (s *SimpleAsset) CreateAssetWithPrivateData(ctx contractapi.TransactionContextInterface, id string, value string, owner string) error { // Create the public asset err := s.CreateAsset(ctx, id, value, owner) if err != nil { return err }
// Get the private data from the transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
// Private data must be passed in the transient field
privateDataJSON, ok := transientMap["asset_private_details"]
if !ok {
return fmt.Errorf("asset_private_details key not found in the transient map")
}
var privateData AssetPrivateDetails
err = json.Unmarshal(privateDataJSON, &privateData)
if err != nil {
return fmt.Errorf("failed to unmarshal private details: %v", err)
}
// Verify that the private data ID matches the public data ID
if privateData.ID != id {
return fmt.Errorf("private data ID %s does not match public data ID %s", privateData.ID, id)
}
// Put private data in the private data collection
return ctx.GetStub().PutPrivateData("assetPrivateDetails", id, privateDataJSON)
}
// ReadAsset returns the asset stored in the world state with given id func (s SimpleAsset) 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
}
// ReadAssetPrivateDetails returns the private details of an asset func (s SimpleAsset) ReadAssetPrivateDetails(ctx contractapi.TransactionContextInterface, id string) (AssetPrivateDetails, error) { privateDataJSON, err := ctx.GetStub().GetPrivateData("assetPrivateDetails", id) if err != nil { return nil, fmt.Errorf("failed to read private data: %v", err) } if privateDataJSON == nil { return nil, fmt.Errorf("the private details for %s do not exist", id) }
var privateData AssetPrivateDetails
err = json.Unmarshal(privateDataJSON, &privateData)
if err != nil {
return nil, err
}
return &privateData, nil
}
// UpdateAsset updates an existing asset in the world state with provided parameters func (s *SimpleAsset) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, value string, owner 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) }
asset := Asset{
ID: id,
Value: value,
Owner: owner,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// DeleteAsset deletes an asset from the world state func (s *SimpleAsset) 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) }
// Delete private data if it exists
err = ctx.GetStub().DelPrivateData("assetPrivateDetails", id)
if err != nil {
return fmt.Errorf("failed to delete private data: %v", err)
}
// Delete public data
return ctx.GetStub().DelState(id)
}
// AssetExists returns true when asset with given ID exists in world state func (s *SimpleAsset) 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
}
// GetAllAssets returns all assets found in world state func (s SimpleAsset) 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
}
func main() { assetChaincode, err := contractapi.NewChaincode(&SimpleAsset{}) if err != nil { fmt.Printf("Error creating asset-transfer chaincode: %v\n", err) return }
if err := assetChaincode.Start(); err != nil {
fmt.Printf("Error starting asset-transfer chaincode: %v\\n", err)
}
} EOF ```
- Deploy the updated chaincode with private data collection: ```bash cd ~/fabric-samples/test-network
# Package and deploy the updated chaincode ./network.sh deployCC -ccn simple-asset-private -ccp ~/first-chaincode -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ~/first-chaincode/collections_config.json ```
- Test the private data functionality: ```bash # Create an asset with private data export ASSET_PRIVATE_DETAILS=$(echo -n '{"id":"asset5","price":1000,"note":"Confidential information"}' | base64 | tr -d \n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n simple-asset-private --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAssetWithPrivateData","Args":["asset5", "purple", "Maria"]}' --transient "{\"asset_private_details\":\"$ASSET_PRIVATE_DETAILS\"}"
# Query the public data peer chaincode query -C mychannel -n simple-asset-private -c '{"function":"ReadAsset","Args":["asset5"]}'
# Query the private data peer chaincode query -C mychannel -n simple-asset-private -c '{"function":"ReadAssetPrivateDetails","Args":["asset5"]}' ```
Deliverable: An enhanced chaincode implementation that uses private data collections, with documentation of your testing process and results.
Exercise 3: Implementing Access Control
Objective: Implement role-based access control in your chaincode using client identity attributes.
Tasks: 1. Modify your chaincode to include access control: ```bash cd ~/first-chaincode/
# Create an updated version with access control cat > main.go << EOF package main
import ( "encoding/json" "fmt" "strings"
"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// SimpleAsset implements a simple chaincode to manage an asset type SimpleAsset struct { contractapi.Contract }
// Asset describes basic details of an asset type Asset struct { ID string `json:"id"` Value string `json:"value"` Owner string `json:"owner"` }
// InitLedger adds a base set of assets to the ledger func (s *SimpleAsset) InitLedger(ctx contractapi.TransactionContextInterface) error { // Check if the caller has admin role err := s.checkRole(ctx, "admin") if err != nil { return err }
assets := []Asset{
{ID: "asset1", Value: "blue", Owner: "Tom"},
{ID: "asset2", Value: "red", Owner: "Jane"},
{ID: "asset3", Value: "green", Owner: "Alex"},
}
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 issues a new asset to the world state with given details func (s *SimpleAsset) CreateAsset(ctx contractapi.TransactionContextInterface, id string, value string, owner string) error { // Check if the caller has admin or creator role err := s.checkRole(ctx, "admin", "creator") if err != nil { return err }
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,
Value: value,
Owner: owner,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// ReadAsset returns the asset stored in the world state with given id func (s SimpleAsset) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (Asset, error) { // Anyone can read assets 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 updates an existing asset in the world state with provided parameters func (s *SimpleAsset) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, value string, owner string) error { // Check if the caller has admin role or is the owner asset, err := s.ReadAsset(ctx, id) if err != nil { return err }
// Get the client ID
clientID, err := s.getClientID(ctx)
if err != nil {
return err
}
// Check if caller is admin or the owner
isAdmin, err := s.hasRole(ctx, "admin")
if err != nil {
return err
}
if !isAdmin && clientID != asset.Owner {
return fmt.Errorf("only admins or the owner can update this asset")
}
// Update the asset
asset.Value = value
asset.Owner = owner
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// DeleteAsset deletes an asset from the world state func (s *SimpleAsset) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error { // Only admins can delete assets err := s.checkRole(ctx, "admin") if err != nil { return err }
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 returns true when asset with given ID exists in world state func (s *SimpleAsset) 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
}
// GetAllAssets returns all assets found in world state func (s SimpleAsset) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]Asset, error) { // Anyone can get all assets 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
}
// Helper function to check if the caller has one of the specified roles func (s *SimpleAsset) checkRole(ctx contractapi.TransactionContextInterface, roles ...string) error { for _, role := range roles { hasRole, err := s.hasRole(ctx, role) if err != nil { return err } if hasRole { return nil } }
return fmt.Errorf("client does not have the required roles: %s", strings.Join(roles, ", "))
}
// Helper function to check if the caller has a specific role func (s *SimpleAsset) hasRole(ctx contractapi.TransactionContextInterface, role string) (bool, error) { val, ok, err := ctx.GetClientIdentity().GetAttributeValue("role") if err != nil { return false, fmt.Errorf("error getting role attribute: %v", err) } if !ok { return false, nil }
return val == role, nil
}
// Helper function to get the client's ID func (s *SimpleAsset) getClientID(ctx contractapi.TransactionContextInterface) (string, error) { id, err := ctx.GetClientIdentity().GetID() if err != nil { return "", fmt.Errorf("failed to get client identity: %v", err) }
return id, nil
}
func main() { assetChaincode, err := contractapi.NewChaincode(&SimpleAsset{}) if err != nil { fmt.Printf("Error creating asset-transfer chaincode: %v\n", err) return }
if err := assetChaincode.Start(); err != nil {
fmt.Printf("Error starting asset-transfer chaincode: %v\\n", err)
}
} EOF ```
- Test the access control functionality: ```bash cd ~/fabric-samples/test-network
# Deploy the updated chaincode ./network.sh deployCC -ccn simple-asset-rbac -ccp ~/first-chaincode -ccl go
# Test with different user roles (in a real scenario, you would use different identities with different attributes) # For this exercise, we'll simulate by checking the error messages
# Try to create an asset (should fail without proper role) peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n simple-asset-rbac --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAsset","Args":["asset6", "orange", "Carlos"]}'
# Query assets (should work for any user) peer chaincode query -C mychannel -n simple-asset-rbac -c '{"function":"GetAllAssets","Args":[]}' ```
Deliverable: A chaincode implementation with role-based access control, with documentation explaining how the access control works and how it could be implemented in a production environment with proper identity attributes.
Assessment
Multiple Choice Questions
-
Which of the following is NOT a supported programming language for Hyperledger Fabric chaincode? a) Go b) Node.js c) Java d) Python
-
In Hyperledger Fabric 2.x, which of the following is NOT part of the chaincode lifecycle? a) Package b) Install c) Approve d) Validate
-
When using private data collections in Hyperledger Fabric, where is the private data stored? a) In a separate blockchain b) In the same blocks as public data c) In private state databases on authorized peers d) In an external database
-
Which parameter in a private data collection configuration specifies how long the private data should be kept before being purged? a) timeToLive b) blockToLive c) expirationTime d) purgeAfter
-
Which of the following is the correct way to access the client identity in chaincode? a) ctx.GetClientIdentity() b) ctx.GetStub().GetClientIdentity() c) ctx.GetIdentity() d) ctx.GetStub().GetCreator()
-
In Hyperledger Fabric chaincode, what is the purpose of the Init function? a) It is called every time a transaction is submitted b) It is called when the chaincode is instantiated or upgraded c) It is used to initialize the peer d) It is used to create the genesis block
-
Which of the following is the correct way to invoke another chaincode from within chaincode? a) ctx.InvokeChaincode() b) ctx.GetStub().InvokeChaincode() c) ctx.CallChaincode() d) ctx.GetStub().CallChaincode()
-
What is the purpose of the GetHistoryForKey function in chaincode? a) To retrieve all previous versions of a key b) To get the transaction history of the chaincode c) To retrieve the history of the channel d) To get the history of the peer
-
Which of the following is NOT a valid operation on private data in chaincode? a) PutPrivateData b) GetPrivateData c) DelPrivateData d) QueryPrivateData
-
What is the main advantage of using the contractapi package in Go chaincode development? a) It provides better performance b) It allows for more complex queries c) It simplifies chaincode development with a more intuitive programming model d) It enables cross-channel transactions
Short Answer Questions
-
Explain the difference between the old Init/Invoke model and the new contract interface model in Hyperledger Fabric chaincode development.
-
Describe how private data collections work in Hyperledger Fabric and when you would use them instead of channels for data privacy.
-
Explain how to implement access control in chaincode using client identity attributes.
-
Discuss the best practices for error handling in chaincode and why proper error handling is important.
-
Describe the process of upgrading chaincode in Hyperledger Fabric 2.x and how it differs from the process in Fabric 1.x.
Answers to Multiple Choice Questions
- d) Python
- d) Validate
- c) In private state databases on authorized peers
- b) blockToLive
- a) ctx.GetClientIdentity()
- b) It is called when the chaincode is instantiated or upgraded
- b) ctx.GetStub().InvokeChaincode()
- a) To retrieve all previous versions of a key
- d) QueryPrivateData
- c) It simplifies chaincode development with a more intuitive programming model