-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
7 changed files
with
549 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
358 changes: 358 additions & 0 deletions
358
asset-transfer-private-data/application-gateway-go/app.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
Oops, something went wrong.