Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Go chaincode for the full-stack asset transfer guide

This folder contains a Go implementation of the `asset-transfer` smart contract used by the full-stack asset transfer guide.

The contract implements the following operations:

- `CreateAsset`
- `ReadAsset`
- `UpdateAsset`
- `DeleteAsset`
- `AssetExists`
- `TransferAsset`
- `GetAllAssets`

The implementation preserves the same asset data model and state-based endorsement behavior as the existing TypeScript contract implementation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#
# SPDX-License-Identifier: Apache-2.0
#
---
smart_contract_name: "asset-transfer"
smart_contract_version: "1.0.0"
smart_contract_sequence: 1
smart_contract_package: "asset-transfer.tgz"
# smart_contract_constructor: "initLedger"
smart_contract_endorsement_policy: ""
smart_contract_collections_file: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

type Asset struct {
ID string `json:"ID"`
Color string `json:"Color"`
Owner string `json:"Owner"`
AppraisedValue int `json:"AppraisedValue"`
Size int `json:"Size"`
}

type ownerIdentifier struct {
Org string `json:"org"`
User string `json:"user"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
package main

import (
"encoding/json"
"fmt"
"regexp"

"github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)

type SmartContract struct {
contractapi.Contract
}

func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, asset Asset) error {
if asset.ID == "" {
return fmt.Errorf("Missing ID")
}
if asset.Owner == "" {
return fmt.Errorf("Missing Owner")
}

exists, err := s.AssetExists(ctx, asset.ID)
if err != nil {
return err
}
if exists {
return fmt.Errorf("The asset %s already exists", asset.ID)
}

ownerID, err := clientIdentifier(ctx, asset.Owner)
if err != nil {
return err
}
asset.Owner, err = toJSON(ownerID)
if err != nil {
return err
}

assetBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal asset: %v", err)
}

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

mspID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSPID: %v", err)
}
if err := setEndorsingOrgs(ctx, asset.ID, mspID); err != nil {
return err
}

return ctx.GetStub().SetEvent("CreateAsset", assetBytes)
}

func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
asset, err := s.readAsset(ctx, id)
if err != nil {
return nil, err
}
return asset, nil
}

func (s *SmartContract) readAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetBytes, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read asset: %v", err)
}
if len(assetBytes) == 0 {
return nil, fmt.Errorf("Sorry, asset %s has not been created", id)
}

var asset Asset
if err := json.Unmarshal(assetBytes, &asset); err != nil {
return nil, fmt.Errorf("failed to unmarshal asset: %v", err)
}

return &asset, nil
}

func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, assetUpdate Asset) error {
if assetUpdate.ID == "" {
return fmt.Errorf("No asset ID specified")
}

existingAsset, err := s.readAsset(ctx, assetUpdate.ID)
if err != nil {
return err
}

permission, err := hasWritePermission(ctx, existingAsset)
if err != nil {
return err
}
if !permission {
return fmt.Errorf("Only owner can update assets")
}

updatedAsset := *existingAsset
if assetUpdate.Color != "" {
updatedAsset.Color = assetUpdate.Color
}
if assetUpdate.Size != 0 {
updatedAsset.Size = assetUpdate.Size
}
if assetUpdate.AppraisedValue != 0 {
updatedAsset.AppraisedValue = assetUpdate.AppraisedValue
}
updatedAsset.Owner = existingAsset.Owner

updatedBytes, err := json.Marshal(updatedAsset)
if err != nil {
return fmt.Errorf("failed to marshal updated asset: %v", err)
}

if err := ctx.GetStub().PutState(updatedAsset.ID, updatedBytes); err != nil {
return fmt.Errorf("failed to update asset state: %v", err)
}

mspID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSPID: %v", err)
}
if err := setEndorsingOrgs(ctx, updatedAsset.ID, mspID); err != nil {
return err
}

return ctx.GetStub().SetEvent("UpdateAsset", updatedBytes)
}

func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
asset, err := s.readAsset(ctx, id)
if err != nil {
return err
}

permission, err := hasWritePermission(ctx, asset)
if err != nil {
return err
}
if !permission {
return fmt.Errorf("Only owner can delete assets")
}

assetBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal deleted asset: %v", err)
}

if err := ctx.GetStub().DelState(id); err != nil {
return fmt.Errorf("failed to delete asset state: %v", err)
}

if err := ctx.GetStub().SetEvent("DeleteAsset", assetBytes); err != nil {
return fmt.Errorf("failed to set delete event: %v", err)
}

return nil
}

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 asset state: %v", err)
}

return len(assetJSON) > 0, nil
}

func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string, newOwnerOrg string) error {
asset, err := s.readAsset(ctx, id)
if err != nil {
return err
}

permission, err := hasWritePermission(ctx, asset)
if err != nil {
return err
}
if !permission {
return fmt.Errorf("Only owner can transfer assets")
}

ownerID := ownerIdentifier{Org: newOwnerOrg, User: newOwner}
asset.Owner, err = toJSON(ownerID)
if err != nil {
return err
}

assetBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal transferred asset: %v", err)
}

if err := ctx.GetStub().PutState(id, assetBytes); err != nil {
return fmt.Errorf("failed to update asset state: %v", err)
}

if err := setEndorsingOrgs(ctx, id, newOwnerOrg); err != nil {
return err
}

return ctx.GetStub().SetEvent("TransferAsset", assetBytes)
}

func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
iterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, fmt.Errorf("failed to get assets by range: %v", err)
}
defer iterator.Close()

var assets []*Asset
for iterator.HasNext() {
queryResponse, err := iterator.Next()
if err != nil {
return nil, fmt.Errorf("failed to iterate assets: %v", err)
}

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

return assets, nil
}

func hasWritePermission(ctx contractapi.TransactionContextInterface, asset *Asset) (bool, error) {
clientID, err := clientIdentifier(ctx, "")
if err != nil {
return false, err
}

var owner ownerIdentifier
if err := json.Unmarshal([]byte(asset.Owner), &owner); err != nil {
return false, fmt.Errorf("failed to unmarshal owner identifier: %v", err)
}

return clientID.Org == owner.Org, nil
}

func clientIdentifier(ctx contractapi.TransactionContextInterface, user string) (ownerIdentifier, error) {
mspID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return ownerIdentifier{}, fmt.Errorf("failed to get client MSPID: %v", err)
}

if user == "" {
commonName, err := clientCommonName(ctx)
if err != nil {
return ownerIdentifier{}, err
}
user = commonName
}

return ownerIdentifier{Org: mspID, User: user}, nil
}

func clientCommonName(ctx contractapi.TransactionContextInterface) (string, error) {
cert, err := ctx.GetClientIdentity().GetX509Certificate()
if err != nil {
return "", fmt.Errorf("failed to read client certificate: %v", err)
}

if cert.Subject.CommonName != "" {
return cert.Subject.CommonName, nil
}

matches := regexp.MustCompile(`^CN=(.*)$`).FindStringSubmatch(cert.Subject.String())
if len(matches) != 2 {
return "", fmt.Errorf("unable to identify client identity common name: %s", cert.Subject.String())
}

return matches[1], nil
}

func newOwnerIdentifier(user string, org string) ownerIdentifier {
return ownerIdentifier{Org: org, User: user}
}

func toJSON(o interface{}) (string, error) {
bytes, err := json.Marshal(o)
if err != nil {
return "", fmt.Errorf("failed to marshal owner identifier: %v", err)
}
return string(bytes), nil
}

func setEndorsingOrgs(ctx contractapi.TransactionContextInterface, ledgerKey string, orgs ...string) error {
policy, err := statebased.NewStateEP(nil)
if err != nil {
return fmt.Errorf("failed to create state endorsement policy: %v", err)
}

if err := policy.AddOrgs(statebased.RoleTypePeer, orgs...); err != nil {
return fmt.Errorf("failed to add orgs to endorsement policy: %v", err)
}

policyBytes, err := policy.Policy()
if err != nil {
return fmt.Errorf("failed to marshal endorsement policy: %v", err)
}

return ctx.GetStub().SetStateValidationParameter(ledgerKey, policyBytes)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module github.com/hyperledger/fabric-samples/full-stack-asset-transfer-guide/contracts/asset-transfer-go

go 1.23.0

require (
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0
)

require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading