mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
Merge c4c4a2589e into a2c40e6522
This commit is contained in:
commit
de51abe0a7
8 changed files with 579 additions and 1 deletions
|
|
@ -0,0 +1,28 @@
|
|||
module github.com/hyperledger/fabric-samples/full-stack-asset-transfer-guide/contracts/asset-transfer-go
|
||||
|
||||
go 1.23
|
||||
|
||||
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.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/grpc v1.67.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 h1:IhkHfrl5X/fVnmB6pWeCYCdIJRi9bxj+WTnVN8DtW3c=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0/go.mod h1:PHHaFffjw7p7n9bmCfcm7RqDqYdivNEsJdiNIKZo5Lk=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 h1:rmUoBmciB0GL/miqcbJmJbgp5QTWoJUrZo+CNxrNLF4=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0/go.mod h1:FeWeO/jwGjiME7ak3GufqKIcwkejtzrDG4QxbfKydWs=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
|
||||
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
asset "github.com/hyperledger/fabric-samples/full-stack-asset-transfer-guide/contracts/asset-transfer-go/smartcontract"
|
||||
)
|
||||
|
||||
func main() {
|
||||
assetChaincode, err := contractapi.NewChaincode(&asset.SmartContract{})
|
||||
if err != nil {
|
||||
log.Panicf("Error creating asset-transfer-go chaincode: %v", err)
|
||||
}
|
||||
|
||||
if err := assetChaincode.Start(); err != nil {
|
||||
log.Panicf("Error starting asset-transfer-go chaincode: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package smartcontract
|
||||
|
||||
// Asset represents a tradeable asset in the world state.
|
||||
type Asset struct {
|
||||
ID string `json:"ID"`
|
||||
Color string `json:"Color"`
|
||||
Owner string `json:"Owner"`
|
||||
AppraisedValue int `json:"AppraisedValue"`
|
||||
Size int `json:"Size"`
|
||||
}
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package smartcontract
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"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 == nil {
|
||||
return fmt.Errorf("asset cannot be nil")
|
||||
}
|
||||
|
||||
if asset.ID == "" {
|
||||
return fmt.Errorf("Missing ID")
|
||||
}
|
||||
|
||||
ownerIdentifier, err := clientIdentifierWithUser(ctx, asset.Owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
asset.Owner, err = marshalOwnerIdentifier(ownerIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exists, err := s.AssetExists(ctx, asset.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("The asset %s already exists", asset.ID)
|
||||
}
|
||||
|
||||
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 in world state: %v", err)
|
||||
}
|
||||
|
||||
clientMSP, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
if err := setStateBasedEndorsement(ctx, asset.ID, clientMSP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.GetStub().SetEvent("CreateAsset", assetBytes); err != nil {
|
||||
return fmt.Errorf("failed to set event CreateAsset: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
|
||||
assetBytes, err := s.readAsset(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 == nil {
|
||||
return fmt.Errorf("asset cannot be nil")
|
||||
}
|
||||
if assetUpdate.ID == "" {
|
||||
return fmt.Errorf("No asset ID specified")
|
||||
}
|
||||
|
||||
existingAsset, err := s.ReadAsset(ctx, assetUpdate.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allowed, err := hasWritePermission(ctx, existingAsset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !allowed {
|
||||
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
|
||||
}
|
||||
|
||||
assetBytes, err := json.Marshal(&updatedAsset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal updated asset: %v", err)
|
||||
}
|
||||
|
||||
if err := ctx.GetStub().PutState(updatedAsset.ID, assetBytes); err != nil {
|
||||
return fmt.Errorf("failed to put asset in world state: %v", err)
|
||||
}
|
||||
|
||||
clientMSP, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
if err := setStateBasedEndorsement(ctx, updatedAsset.ID, clientMSP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.GetStub().SetEvent("UpdateAsset", assetBytes); err != nil {
|
||||
return fmt.Errorf("failed to set event UpdateAsset: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
|
||||
asset, err := s.ReadAsset(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allowed, err := hasWritePermission(ctx, asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !allowed {
|
||||
return fmt.Errorf("Only owner can delete assets")
|
||||
}
|
||||
|
||||
if err := ctx.GetStub().DelState(id); err != nil {
|
||||
return fmt.Errorf("failed to delete asset from world state: %v", err)
|
||||
}
|
||||
|
||||
assetBytes, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal asset event payload: %v", err)
|
||||
}
|
||||
|
||||
if err := ctx.GetStub().SetEvent("DeleteAsset", assetBytes); err != nil {
|
||||
return fmt.Errorf("failed to set event DeleteAsset: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
|
||||
assetBytes, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
|
||||
return len(assetBytes) > 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
|
||||
}
|
||||
|
||||
allowed, err := hasWritePermission(ctx, asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !allowed {
|
||||
return fmt.Errorf("Only owner can transfer assets")
|
||||
}
|
||||
|
||||
asset.Owner, err = marshalOwnerIdentifier(ownerIdentifier(newOwner, newOwnerOrg))
|
||||
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 put asset in world state: %v", err)
|
||||
}
|
||||
|
||||
if err := setStateBasedEndorsement(ctx, id, newOwnerOrg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.GetStub().SetEvent("TransferAsset", assetBytes); err != nil {
|
||||
return fmt.Errorf("failed to set event TransferAsset: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
|
||||
iterator, err := ctx.GetStub().GetStateByRange("", "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve all assets: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = iterator.Close()
|
||||
}()
|
||||
|
||||
assets := make([]*Asset, 0)
|
||||
for iterator.HasNext() {
|
||||
queryResponse, err := iterator.Next()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed retrieving next item: %v", err)
|
||||
}
|
||||
|
||||
var asset Asset
|
||||
if err := json.Unmarshal(queryResponse.Value, &asset); err != nil {
|
||||
log.Printf("failed to unmarshal asset from world state: %v", err)
|
||||
continue
|
||||
}
|
||||
assets = append(assets, &asset)
|
||||
}
|
||||
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
func (s *SmartContract) readAsset(ctx contractapi.TransactionContextInterface, id string) ([]byte, error) {
|
||||
assetBytes, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
|
||||
if len(assetBytes) == 0 {
|
||||
return nil, fmt.Errorf("Sorry, asset %s has not been created", id)
|
||||
}
|
||||
|
||||
return assetBytes, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAssetJSONRoundTrip(t *testing.T) {
|
||||
asset := Asset{
|
||||
ID: "asset1",
|
||||
Color: "blue",
|
||||
Owner: `{"org":"Org1MSP","user":"User1"}`,
|
||||
AppraisedValue: 100,
|
||||
Size: 10,
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal asset: %v", err)
|
||||
}
|
||||
|
||||
var decoded Asset
|
||||
if err := json.Unmarshal(bytes, &decoded); err != nil {
|
||||
t.Fatalf("failed to unmarshal asset: %v", err)
|
||||
}
|
||||
|
||||
if decoded.ID != asset.ID || decoded.Color != asset.Color || decoded.Owner != asset.Owner || decoded.AppraisedValue != asset.AppraisedValue || decoded.Size != asset.Size {
|
||||
t.Fatalf("decoded asset does not match original asset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwnerIdentifierSerialization(t *testing.T) {
|
||||
owner := ownerIdentifier("User1", "Org1MSP")
|
||||
serialized, err := marshalOwnerIdentifier(owner)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal owner identifier: %v", err)
|
||||
}
|
||||
|
||||
decoded, err := ownerIdentifierFromString(serialized)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal owner identifier: %v", err)
|
||||
}
|
||||
|
||||
if decoded.Org != owner.Org || decoded.User != owner.User {
|
||||
t.Fatalf("decoded owner identifier does not match original")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package smartcontract
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
)
|
||||
|
||||
type OwnerIdentifier struct {
|
||||
Org string `json:"org"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
func hasWritePermission(ctx contractapi.TransactionContextInterface, asset *Asset) (bool, error) {
|
||||
clientID, err := clientIdentifier(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
ownerID, err := ownerIdentifierFromString(asset.Owner)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid asset owner field: %v", err)
|
||||
}
|
||||
|
||||
return clientID.Org == ownerID.Org, nil
|
||||
}
|
||||
|
||||
func clientIdentifier(ctx contractapi.TransactionContextInterface) (*OwnerIdentifier, error) {
|
||||
org, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
user, err := clientCommonName(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &OwnerIdentifier{Org: org, User: user}, nil
|
||||
}
|
||||
|
||||
func clientIdentifierWithUser(ctx contractapi.TransactionContextInterface, user string) (*OwnerIdentifier, error) {
|
||||
org, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
if user == "" {
|
||||
user, err = clientCommonName(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &OwnerIdentifier{Org: org, User: user}, nil
|
||||
}
|
||||
|
||||
func clientCommonName(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
encodedID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read client identity: %v", err)
|
||||
}
|
||||
|
||||
idBytes, err := base64.StdEncoding.DecodeString(encodedID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode client identity: %v", err)
|
||||
}
|
||||
|
||||
certBlock, _ := pem.Decode(idBytes)
|
||||
if certBlock == nil {
|
||||
certBlock = &pem.Block{Bytes: idBytes}
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to parse client identity certificate: %v", err)
|
||||
}
|
||||
|
||||
if cert.Subject.CommonName == "" {
|
||||
return "", fmt.Errorf("unable to identify client identity common name: %v", cert.Subject)
|
||||
}
|
||||
|
||||
return cert.Subject.CommonName, nil
|
||||
}
|
||||
|
||||
func ownerIdentifier(user string, org string) *OwnerIdentifier {
|
||||
return &OwnerIdentifier{Org: org, User: user}
|
||||
}
|
||||
|
||||
func ownerIdentifierFromString(owner string) (*OwnerIdentifier, error) {
|
||||
var identifier OwnerIdentifier
|
||||
if err := json.Unmarshal([]byte(owner), &identifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if identifier.Org == "" || identifier.User == "" {
|
||||
return nil, fmt.Errorf("owner must include org and user")
|
||||
}
|
||||
|
||||
return &identifier, nil
|
||||
}
|
||||
|
||||
func marshalOwnerIdentifier(identifier *OwnerIdentifier) (string, error) {
|
||||
serialized, err := json.Marshal(identifier)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal owner identifier: %v", err)
|
||||
}
|
||||
|
||||
return string(serialized), nil
|
||||
}
|
||||
|
||||
func setStateBasedEndorsement(ctx contractapi.TransactionContextInterface, ledgerKey string, org 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, org); err != nil {
|
||||
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
|
||||
}
|
||||
|
||||
policyBytes, err := policy.Policy()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal endorsement policy: %v", err)
|
||||
}
|
||||
|
||||
if err := ctx.GetStub().SetStateValidationParameter(ledgerKey, policyBytes); err != nil {
|
||||
return fmt.Errorf("failed to set state validation parameter: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"ignoreDeprecations": "6.0",
|
||||
"lib": ["ES2023"],
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue