From 4d42997eb863cb7afbe460025268a5df4320ec73 Mon Sep 17 00:00:00 2001 From: Fernando Garzon Date: Mon, 30 Jan 2023 09:16:16 -0800 Subject: [PATCH] Still developing the PDC function. Update JAN 30 - 2023 --- .../chaincode-go/chaincode/smartcontract.go | 137 +++++++++++++++++- test-network/snippet.go | 118 +++++++++++++++ 2 files changed, 254 insertions(+), 1 deletion(-) diff --git a/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go b/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go index 43d7d94b..9d326b0c 100644 --- a/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go +++ b/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go @@ -1,12 +1,14 @@ package chaincode import ( + "encoding/base64" "encoding/json" "fmt" //"github.com/gofiber/fiber/v2/uuid" "github.com/google/uuid" + "github.com/hyperledger/fabric-chaincode-go/shim" "github.com/hyperledger/fabric-contract-api-go/contractapi" "github.com/xeipuuv/gojsonschema" @@ -29,6 +31,9 @@ type SmartContract struct { var lastSchemaHash string var APIUserIds []string +const PDC1 = "PDC1" +const PDC2 = "PDC2" + type Data struct { Contributor string `json:"Contributor"` ContributorId string `json:"ContributorId"` @@ -38,7 +43,7 @@ type Data struct { JsonFileContent map[string]interface{} } -type PrivateDataContent struc { +type PrivateDataContent struct { Id string `json:"Id"` AnonymousFunder string `json:"AnonymousFunder"` AssetValue string `json:"AssetValue"` @@ -411,6 +416,107 @@ func (s *SmartContract) CreateDataSample(ctx contractapi.TransactionContextInter } +func (s *SmartContract) WriteToPDC(ctx contractapi.TransactionContextInterface) error { + // Get new asset from transient map + transientMap, err := ctx.GetStub().GetTransient() + if err != nil { + return fmt.Errorf("error getting transient: %v", err) + } + + // Asset properties are private, therefore they get passed in transient field, instead of func args + transientAssetJSON, ok := transientMap["asset_properties"] + if !ok { + //log error to stdout + return fmt.Errorf("asset not found in the transient map input") + } + type assetTransientInput struct { + Contributor string `json:"Contributor"` + ContributorId string `json:"ContributorId"` + ContentHash string `json:"ContentHash"` + Id string `json:"Id"` + Owner string `json:"Owners"` + JsonFileContent map[string]interface{} + } + + var assetInput assetTransientInput + err = json.Unmarshal(transientAssetJSON, &assetInput) + if err != nil { + return fmt.Errorf("failed to unmarshal JSON: %v", err) + } + + // Check if asset already exists + assetAsBytes, err := ctx.GetStub().GetPrivateData(PDC1, assetInput.Id) + if err != nil { + return fmt.Errorf("failed to get asset: %v", err) + } else if assetAsBytes != nil { + fmt.Println("Asset already exists: " + assetInput.Id) + return fmt.Errorf("this asset already exists: " + assetInput.Id) + } + + // Get ID of submitting client identity + clientID, err := submittingClientIdentity(ctx) + if err != nil { + return err + } + + // Verify that the client is submitting request to peer in their organization + // This is to ensure that a client from another org doesn't attempt to read or + // write private data from this peer. + err = verifyClientOrgMatchesPeerOrg(ctx) + if err != nil { + return fmt.Errorf("CreateAsset cannot be performed: Error %v", err) + } + + // Make submitting client the owner + asset := Data{ + Contributor: assetInput.Contributor, + ContributorId: assetInput.ContributorId, + ContentHash: assetInput.ContentHash, + Id: assetInput.Id, + Owner: clientID, + JsonFileContent: assetInput.JsonFileContent, + } + assetJSONasBytes, err := json.Marshal(asset) + if err != nil { + return fmt.Errorf("failed to marshal asset into JSON: %v", err) + } + + // Save asset to private data collection + // Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode + // Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz + log.Printf("CreateAsset Put: collection %v, ID %v, owner %v", assetCollection, assetInput.ID, clientID) + + err = ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes) + if err != nil { + return fmt.Errorf("failed to put asset into private data collection: %v", err) + } + + // Save asset details to collection visible to owning organization + assetPrivateDetails := AssetPrivateDetails{ + ID: assetInput.ID, + AppraisedValue: assetInput.AppraisedValue, + } + + assetPrivateDetailsAsBytes, err := json.Marshal(assetPrivateDetails) // marshal asset details to JSON + if err != nil { + return fmt.Errorf("failed to marshal into JSON: %v", err) + } + + // Get collection name for this organization. + orgCollection, err := getCollectionName(ctx) + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } + + // Put asset appraised value into owners org specific private data collection + log.Printf("Put: collection %v, ID %v", orgCollection, assetInput.ID) + err = ctx.GetStub().PutPrivateData(orgCollection, assetInput.ID, assetPrivateDetailsAsBytes) + if err != nil { + return fmt.Errorf("failed to put asset private details: %v", err) + } + return nil +} + // UpdateAsset updates an existing asset in the world state with provided parameters. /*func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, Contributor string, ContributorId string, Id string, Owner string) error { @@ -798,6 +904,35 @@ func (s *SmartContract) GetAPIUserByUUID(ctx contractapi.TransactionContextInter } +func submittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) { + b64ID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return "", fmt.Errorf("Failed to read clientID: %v", err) + } + decodeID, err := base64.StdEncoding.DecodeString(b64ID) + if err != nil { + return "", fmt.Errorf("failed to base64 decode clientID: %v", err) + } + return string(decodeID), nil +} + +func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error { + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return fmt.Errorf("failed getting the client's MSPID: %v", err) + } + peerMSPID, err := shim.GetMSPID() + if err != nil { + return fmt.Errorf("failed getting the peer's MSPID: %v", err) + } + + if clientMSPID != peerMSPID { + return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID) + } + + return nil +} + /* // ReadAsset returns the asset stored in the world state with given id. func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) { diff --git a/test-network/snippet.go b/test-network/snippet.go index e69de29b..24d92820 100644 --- a/test-network/snippet.go +++ b/test-network/snippet.go @@ -0,0 +1,118 @@ +// CreateAsset creates a new asset by placing the main asset details in the assetCollection +// that can be read by both organizations. The appraisal value is stored in the owners org specific collection. +func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) error { + + // Get new asset from transient map + transientMap, err := ctx.GetStub().GetTransient() + if err != nil { + return fmt.Errorf("error getting transient: %v", err) + } + + // Asset properties are private, therefore they get passed in transient field, instead of func args + transientAssetJSON, ok := transientMap["asset_properties"] + if !ok { + //log error to stdout + return fmt.Errorf("asset not found in the transient map input") + } + + type assetTransientInput struct { + Type string `json:"objectType"` //Type is used to distinguish the various types of objects in state database + ID string `json:"assetID"` + Color string `json:"color"` + Size int `json:"size"` + AppraisedValue int `json:"appraisedValue"` + } + + var assetInput assetTransientInput + err = json.Unmarshal(transientAssetJSON, &assetInput) + if err != nil { + return fmt.Errorf("failed to unmarshal JSON: %v", err) + } + + if len(assetInput.Type) == 0 { + return fmt.Errorf("objectType field must be a non-empty string") + } + if len(assetInput.ID) == 0 { + return fmt.Errorf("assetID field must be a non-empty string") + } + if len(assetInput.Color) == 0 { + return fmt.Errorf("color field must be a non-empty string") + } + if assetInput.Size <= 0 { + return fmt.Errorf("size field must be a positive integer") + } + if assetInput.AppraisedValue <= 0 { + return fmt.Errorf("appraisedValue field must be a positive integer") + } + + // Check if asset already exists + assetAsBytes, err := ctx.GetStub().GetPrivateData(assetCollection, assetInput.ID) + if err != nil { + return fmt.Errorf("failed to get asset: %v", err) + } else if assetAsBytes != nil { + fmt.Println("Asset already exists: " + assetInput.ID) + return fmt.Errorf("this asset already exists: " + assetInput.ID) + } + + // Get ID of submitting client identity + clientID, err := submittingClientIdentity(ctx) + if err != nil { + return err + } + + // Verify that the client is submitting request to peer in their organization + // This is to ensure that a client from another org doesn't attempt to read or + // write private data from this peer. + err = verifyClientOrgMatchesPeerOrg(ctx) + if err != nil { + return fmt.Errorf("CreateAsset cannot be performed: Error %v", err) + } + + // Make submitting client the owner + asset := Asset{ + Type: assetInput.Type, + ID: assetInput.ID, + Color: assetInput.Color, + Size: assetInput.Size, + Owner: clientID, + } + assetJSONasBytes, err := json.Marshal(asset) + if err != nil { + return fmt.Errorf("failed to marshal asset into JSON: %v", err) + } + + // Save asset to private data collection + // Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode + // Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz + log.Printf("CreateAsset Put: collection %v, ID %v, owner %v", assetCollection, assetInput.ID, clientID) + + err = ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes) + if err != nil { + return fmt.Errorf("failed to put asset into private data collection: %v", err) + } + + // Save asset details to collection visible to owning organization + assetPrivateDetails := AssetPrivateDetails{ + ID: assetInput.ID, + AppraisedValue: assetInput.AppraisedValue, + } + + assetPrivateDetailsAsBytes, err := json.Marshal(assetPrivateDetails) // marshal asset details to JSON + if err != nil { + return fmt.Errorf("failed to marshal into JSON: %v", err) + } + + // Get collection name for this organization. + orgCollection, err := getCollectionName(ctx) + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } + + // Put asset appraised value into owners org specific private data collection + log.Printf("Put: collection %v, ID %v", orgCollection, assetInput.ID) + err = ctx.GetStub().PutPrivateData(orgCollection, assetInput.ID, assetPrivateDetailsAsBytes) + if err != nil { + return fmt.Errorf("failed to put asset private details: %v", err) + } + return nil +} \ No newline at end of file