Skip to content

Commit

Permalink
Add private data go application
Browse files Browse the repository at this point in the history
Created project directory, app.go and connect.go files. Reused the logic for
connect.go from the events application and added second organization setup.

Implemented private data transaction example in go as described in the main
documentation in "Tutorials/Using Private Data in Fabric".

Updated README.md with the command to run the go application and the script
which runs the application in the Github Actions workflow.

Fixed typos and punctuation in the private data typescript application.

Signed-off-by: Stanislav Jakuschevskij <stas@two-giants.com>
  • Loading branch information
twoGiants committed Oct 29, 2024
1 parent f16e9e6 commit b5c18e2
Show file tree
Hide file tree
Showing 7 changed files with 549 additions and 16 deletions.
10 changes: 7 additions & 3 deletions asset-transfer-private-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ Like other samples, the Fabric test network is used to deploy and run this sampl
# To deploy the go chaincode implementation
./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-go -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg '../asset-transfer-private-data/chaincode-go/collections_config.json' -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
# To deploy the typescript chaincode implementation
./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-typescript/ -ccl typescript -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-typescript/collections_config.json
./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-typescript/ -ccl typescript -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-typescript/collections_config.json
```

3. Run the application (from the `asset-transfer-private-data` folder).
Expand All @@ -64,6 +64,10 @@ Like other samples, the Fabric test network is used to deploy and run this sampl
cd application-gateway-typescript
npm install
npm start
# To run the Go sample application
cd application-gateway-go
go run .
```

## Clean up
Expand All @@ -72,4 +76,4 @@ When you are finished, you can bring down the test network (from the `test-netwo

```
./network.sh down
```
```
358 changes: 358 additions & 0 deletions asset-transfer-private-data/application-gateway-go/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"time"

"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/hash"
)

const (
channelName = "mychannel"
chaincodeName = "private"
mspIDOrg1 = "Org1MSP"
mspIDOrg2 = "Org2MSP"

// Collection names.
org1PrivateCollectionName = "Org1MSPPrivateCollection"
org2PrivateCollectionName = "Org2MSPPrivateCollection"

Red = "\033[31m"
Reset = "\033[0m"
)

// Use a unique key so that we can run multiple times.
var now = time.Now()
var assetID1 = fmt.Sprintf("asset%d", now.Unix())
var assetID2 = fmt.Sprintf("asset%d", now.Unix()+1)

func main() {
clientOrg1 := newGrpcConnection(
tlsCertPathOrg1,
peerEndpointOrg1,
peerNameOrg1,
)
defer clientOrg1.Close()

gatewayOrg1, err := client.Connect(
newIdentity(certDirectoryPathOrg1, mspIDOrg1),
client.WithSign(newSign(keyDirectoryPathOrg1)),
client.WithClientConnection(clientOrg1),
client.WithHash(hash.SHA256),
)
if err != nil {
panic(err)
}
defer gatewayOrg1.Close()

clientOrg2 := newGrpcConnection(
tlsCertPathOrg2,
peerEndpointOrg2,
peerNameOrg2,
)
defer clientOrg2.Close()

gatewayOrg2, err := client.Connect(
newIdentity(certDirectoryPathOrg2, mspIDOrg2),
client.WithSign(newSign(keyDirectoryPathOrg2)),
client.WithClientConnection(clientOrg2),
client.WithHash(hash.SHA256),
)
if err != nil {
panic(err)
}
defer gatewayOrg2.Close()

// Get the smart contract as an Org1 client.
contractOrg1 := gatewayOrg1.GetNetwork(channelName).GetContract(chaincodeName)

// Get the smart contract as an Org1 client.
contractOrg2 := gatewayOrg2.GetNetwork(channelName).GetContract(chaincodeName)

fmt.Println("~~~~~~~~~~~~~~~~ As Org1 Client ~~~~~~~~~~~~~~~~")

// Create new assets on the ledger.
createAssets(*contractOrg1)

// Read asset from the Org1's private data collection with ID in the given range.
getAssetByRange(*contractOrg1)

// Attempt to transfer asset without prior approval from Org2, transaction expected to fail.
fmt.Println("\nAttempt TransferAsset without prior AgreeToTransfer")
err = transferAsset(*contractOrg1, assetID1)
if err == nil {
doFail("TransferAsset transaction succeeded when it was expected to fail")
}
fmt.Printf("*** Received expected error: %+v\n", err)

fmt.Println("\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~")

// Read the asset by ID.
readAssetByID(*contractOrg2, assetID1)

// Make agreement to transfer the asset from Org1 to Org2.
agreeToTransfer(*contractOrg2, assetID1)

fmt.Println("\n~~~~~~~~~~~~~~~~ As Org1 Client ~~~~~~~~~~~~~~~~")

// Read transfer agreement.
readTransferAgreement(*contractOrg1, assetID1)

// Transfer asset to Org2.
transferAsset(*contractOrg1, assetID1)

// Again ReadAsset: results will show that the buyer identity now owns the asset.
readAssetByID(*contractOrg1, assetID1)

// Confirm that transfer removed the private details from the Org1 collection.
if org1ReadSuccess := readAssetPrivateDetails(*contractOrg1, assetID1, org1PrivateCollectionName); !org1ReadSuccess {
doFail(fmt.Sprintf("Asset private data still exists in %s", org1PrivateCollectionName))
}

fmt.Println("\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~")

// Org2 can read asset private details: Org2 is owner, and private details exist in new owner's Collection.
if org2ReadSuccess := readAssetPrivateDetails(*contractOrg2, assetID1, org2PrivateCollectionName); !org2ReadSuccess {
doFail(fmt.Sprintf("Asset private data not found in %s", org2PrivateCollectionName))
}

fmt.Println("\nAttempt DeleteAsset using non-owner organization")
err = deleteAsset(*contractOrg2, assetID2)
if err == nil {
doFail("DeleteAsset transaction succeeded when it was expected to fail")
}
fmt.Printf("*** Received expected error: %s\n", err.Error())

fmt.Println("\n~~~~~~~~~~~~~~~~ As Org1 Client ~~~~~~~~~~~~~~~~")

// Delete AssetID2 as Org1.
deleteAsset(*contractOrg1, assetID2)

// Trigger a purge of the private data for the asset.
// The previous delete is optional if purge is used.
purgeAsset(*contractOrg1, assetID2)
}

func createAssets(contract client.Contract) {
assetType := "ValuableAsset"

fmt.Printf("\n--> Submit Transaction: CreateAsset, ID: %s\n", assetID1)

type assetTransientInput struct {
ObjectType string
AssetID string
Color string
Size uint8
AppraisedValue uint16
}

asset1Data := assetTransientInput{
ObjectType: assetType,
AssetID: assetID1,
Color: "green",
Size: 20,
AppraisedValue: 100,
}

if _, err := contract.Submit(
"CreateAsset",
client.WithTransient(transientData("asset_properties", asset1Data)),
); err != nil {
panic(err)
}

logTxCommitSuccess()
fmt.Printf("\n--> Submit Transaction: CreateAsset, ID: %s\n", assetID2)

asset2Data := assetTransientInput{
ObjectType: assetType,
AssetID: assetID2,
Color: "blue",
Size: 35,
AppraisedValue: 727,
}

if _, err := contract.Submit(
"CreateAsset",
client.WithTransient(transientData("asset_properties", asset2Data)),
); err != nil {
panic(err)
}

logTxCommitSuccess()
}

func getAssetByRange(contract client.Contract) {
// GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive).
fmt.Printf("\n--> Evaluate Transaction: GetAssetByRange from %s\n", org1PrivateCollectionName)

resultBytes, err := contract.EvaluateTransaction("GetAssetByRange", assetID1, fmt.Sprintf("asset%d", now.Unix()+2))
if err != nil {
panic(err)
}

result := formatJSON(resultBytes)
if result == "" {
doFail("Received empty query list for GetAssetByRange")
}
fmt.Printf("*** Result: %s\n", result)
}

func readAssetByID(contract client.Contract, assetID string) {
fmt.Printf("\n--> Evaluate Transaction: ReadAsset, ID: %s\n", assetID)

resultBytes, err := contract.EvaluateTransaction("ReadAsset", assetID)
if err != nil {
panic(err)
}

result := formatJSON(resultBytes)
if result == "" {
doFail("Received empty result for ReadAsset")
}
fmt.Printf("*** Result: %s\n", result)
}

func agreeToTransfer(contract client.Contract, assetID string) {
// Buyer from Org2 agrees to buy the asset.
// To purchase the asset, the buyer needs to agree to the same value as the asset owner.
dataForAgreement := struct {
AssetID string
AppraisedValue int
}{assetID, 100}
fmt.Printf("\n--> Submit Transaction: AgreeToTransfer, payload: %+v\n", dataForAgreement)

if _, err := contract.Submit(
"AgreeToTransfer",
client.WithTransient(transientData("asset_value", dataForAgreement)),
); err != nil {
panic(err)
}

logTxCommitSuccess()
}

func readTransferAgreement(contract client.Contract, assetID string) {
fmt.Printf("\n--> Evaluate Transaction: ReadTransferAgreement, ID: %s\n", assetID)

resultBytes, err := contract.EvaluateTransaction("ReadTransferAgreement", assetID)
if err != nil {
panic(err)
}

result := formatJSON(resultBytes)
if result == "" {
doFail("Received empty result for ReadTransferAgreement")
}
fmt.Printf("*** Result: %s\n", result)
}

func transferAsset(contract client.Contract, assetID string) (err error) {
fmt.Printf("\n--> Submit Transaction: TransferAsset, ID: %s\n", assetID)

buyerDetails := struct {
AssetID string
BuyerMSP string
}{assetID, mspIDOrg2}

if _, err = contract.Submit(
"TransferAsset",
client.WithTransient(
transientData("asset_owner", buyerDetails),
),
); err != nil {
return
}

logTxCommitSuccess()
return
}

func deleteAsset(contract client.Contract, assetID string) (err error) {
fmt.Printf("\n--> Submit Transaction: DeleteAsset, ID: %s\n", assetID)

dataForDelete := struct{ AssetID string }{assetID}

if _, err = contract.Submit(
"DeleteAsset",
client.WithTransient(
transientData("asset_delete", dataForDelete),
),
); err != nil {
return
}

logTxCommitSuccess()
return
}

func purgeAsset(contract client.Contract, assetID string) (err error) {
fmt.Printf("\n--> Submit Transaction: PurgeAsset, ID: %s\n", assetID)

dataForPurge := struct{ AssetID string }{assetID}

if _, err = contract.Submit(
"PurgeAsset",
client.WithTransient(
transientData("asset_purge", dataForPurge),
),
); err != nil {
return
}

logTxCommitSuccess()
return
}

func readAssetPrivateDetails(contract client.Contract, assetID, collectionName string) bool {
fmt.Printf("\n--> Evaluate Transaction: ReadAssetPrivateDetails from %s, ID: %s\n", collectionName, assetID)

resultBytes, err := contract.EvaluateTransaction("ReadAssetPrivateDetails", collectionName, assetID)
if err != nil {
panic(err)
}

result := formatJSON(resultBytes)
if result == "" {
fmt.Println("*** No result")
return false
}
fmt.Printf("*** Result: %s\n", result)
return true
}

func transientData(key string, value any) map[string][]byte {
valueAsBytes, err := json.Marshal(&value)
if err != nil {
panic(err)
}

return map[string][]byte{key: valueAsBytes}
}

func formatJSON(data []byte) string {
var result bytes.Buffer
if err := json.Indent(&result, data, "", " "); err != nil {
panic(fmt.Errorf("failed to parse JSON: %w", err))
}
return result.String()
}

func doFail(msg string) {
fmt.Println(Red + msg + Reset)
panic(errors.New(msg))
}

func logTxCommitSuccess() {
fmt.Println("*** Transaction committed successfully")
}
Loading

0 comments on commit b5c18e2

Please sign in to comment.