From b5c18e2c93037736bf5030ec831aa719031f04f4 Mon Sep 17 00:00:00 2001 From: Stanislav Jakuschevskij Date: Fri, 18 Oct 2024 16:10:20 +0200 Subject: [PATCH] Add private data go application 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 --- asset-transfer-private-data/README.md | 10 +- .../application-gateway-go/app.go | 358 ++++++++++++++++++ .../application-gateway-go/connect.go | 111 ++++++ .../application-gateway-go/go.mod | 19 + .../application-gateway-go/go.sum | 32 ++ .../application-gateway-typescript/src/app.ts | 26 +- ci/scripts/run-test-network-private.sh | 9 + 7 files changed, 549 insertions(+), 16 deletions(-) create mode 100644 asset-transfer-private-data/application-gateway-go/app.go create mode 100644 asset-transfer-private-data/application-gateway-go/connect.go create mode 100644 asset-transfer-private-data/application-gateway-go/go.mod create mode 100644 asset-transfer-private-data/application-gateway-go/go.sum diff --git a/asset-transfer-private-data/README.md b/asset-transfer-private-data/README.md index 8ee1528946..0df860a825 100644 --- a/asset-transfer-private-data/README.md +++ b/asset-transfer-private-data/README.md @@ -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). @@ -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 @@ -72,4 +76,4 @@ When you are finished, you can bring down the test network (from the `test-netwo ``` ./network.sh down -``` \ No newline at end of file +``` diff --git a/asset-transfer-private-data/application-gateway-go/app.go b/asset-transfer-private-data/application-gateway-go/app.go new file mode 100644 index 0000000000..17acf87d31 --- /dev/null +++ b/asset-transfer-private-data/application-gateway-go/app.go @@ -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") +} diff --git a/asset-transfer-private-data/application-gateway-go/connect.go b/asset-transfer-private-data/application-gateway-go/connect.go new file mode 100644 index 0000000000..3bd768634c --- /dev/null +++ b/asset-transfer-private-data/application-gateway-go/connect.go @@ -0,0 +1,111 @@ +/* +Copyright 2022 IBM All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "crypto/x509" + "fmt" + "os" + "path" + + "github.com/hyperledger/fabric-gateway/pkg/identity" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +const ( + cryptoPathOrg1 = "../../test-network/organizations/peerOrganizations/org1.example.com" + keyDirectoryPathOrg1 = cryptoPathOrg1 + "/users/User1@org1.example.com/msp/keystore" + certDirectoryPathOrg1 = cryptoPathOrg1 + "/users/User1@org1.example.com/msp/signcerts" + tlsCertPathOrg1 = cryptoPathOrg1 + "/peers/peer0.org1.example.com/tls/ca.crt" + peerEndpointOrg1 = "dns:///localhost:7051" + peerNameOrg1 = "peer0.org1.example.com" + cryptoPathOrg2 = "../../test-network/organizations/peerOrganizations/org2.example.com" + keyDirectoryPathOrg2 = cryptoPathOrg2 + "/users/User1@org2.example.com/msp/keystore" + certDirectoryPathOrg2 = cryptoPathOrg2 + "/users/User1@org2.example.com/msp/signcerts" + tlsCertPathOrg2 = cryptoPathOrg2 + "/peers/peer0.org2.example.com/tls/ca.crt" + peerEndpointOrg2 = "dns:///localhost:9051" + peerNameOrg2 = "peer0.org2.example.com" +) + +// newGrpcConnection creates a gRPC connection to the Gateway server. +func newGrpcConnection(tlsCertPath, peerEndpoint, peerName string) *grpc.ClientConn { + certificatePEM, err := os.ReadFile(tlsCertPath) + if err != nil { + panic(fmt.Errorf("failed to read TLS certificate file: %w", err)) + } + + certificate, err := identity.CertificateFromPEM(certificatePEM) + if err != nil { + panic(err) + } + + certPool := x509.NewCertPool() + certPool.AddCert(certificate) + transportCredentials := credentials.NewClientTLSFromCert(certPool, peerName) + + connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials)) + if err != nil { + panic(fmt.Errorf("failed to create gRPC connection: %w", err)) + } + + return connection +} + +// newIdentity creates a client identity for this Gateway connection using an X.509 certificate. +func newIdentity(certDirectoryPath, mspId string) *identity.X509Identity { + certificatePEM, err := readFirstFile(certDirectoryPath) + if err != nil { + panic(fmt.Errorf("failed to read certificate file: %w", err)) + } + + certificate, err := identity.CertificateFromPEM(certificatePEM) + if err != nil { + panic(err) + } + + id, err := identity.NewX509Identity(mspId, certificate) + if err != nil { + panic(err) + } + + return id +} + +// newSign creates a function that generates a digital signature from a message digest using a private key. +func newSign(keyDirectoryPash string) identity.Sign { + privateKeyPEM, err := readFirstFile(keyDirectoryPash) + if err != nil { + panic(fmt.Errorf("failed to read private key file: %w", err)) + } + + privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM) + if err != nil { + panic(err) + } + + sign, err := identity.NewPrivateKeySign(privateKey) + if err != nil { + panic(err) + } + + return sign +} + +func readFirstFile(dirPath string) ([]byte, error) { + dir, err := os.Open(dirPath) + if err != nil { + return nil, err + } + + fileNames, err := dir.Readdirnames(1) + if err != nil { + return nil, err + } + + return os.ReadFile(path.Join(dirPath, fileNames[0])) +} diff --git a/asset-transfer-private-data/application-gateway-go/go.mod b/asset-transfer-private-data/application-gateway-go/go.mod new file mode 100644 index 0000000000..6ea60de380 --- /dev/null +++ b/asset-transfer-private-data/application-gateway-go/go.mod @@ -0,0 +1,19 @@ +module assetTransfer + +go 1.22.0 + +require ( + github.com/hyperledger/fabric-gateway v1.6.0 + google.golang.org/grpc v1.67.0 +) + +require ( + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/asset-transfer-private-data/application-gateway-go/go.sum b/asset-transfer-private-data/application-gateway-go/go.sum new file mode 100644 index 0000000000..fe0c28be6f --- /dev/null +++ b/asset-transfer-private-data/application-gateway-go/go.sum @@ -0,0 +1,32 @@ +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/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-gateway v1.6.0 h1:mPdXFSHdEjT0cmhsqKBfFMTVyBvfJXlO3Neicp/c27E= +github.com/hyperledger/fabric-gateway v1.6.0/go.mod h1:qHdJcgC6UrTxfYH+YIyAhPUkeNri0gPpyP/6xmiYrZo= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/asset-transfer-private-data/application-gateway-typescript/src/app.ts b/asset-transfer-private-data/application-gateway-typescript/src/app.ts index ffa5423905..1e190227f1 100644 --- a/asset-transfer-private-data/application-gateway-typescript/src/app.ts +++ b/asset-transfer-private-data/application-gateway-typescript/src/app.ts @@ -18,14 +18,14 @@ const mspIdOrg2 = 'Org2MSP'; const utf8Decoder = new TextDecoder(); -// Collection Names +// Collection names. const org1PrivateCollectionName = 'Org1MSPPrivateCollection'; const org2PrivateCollectionName = 'Org2MSPPrivateCollection'; const RED = '\x1b[31m\n'; const RESET = '\x1b[0m'; -// Use a unique key so that we can run multiple times +// Use a unique key so that we can run multiple times. const now = Date.now(); const assetID1 = `asset${String(now)}`; const assetID2 = `asset${String(now + 1)}`; @@ -74,10 +74,10 @@ async function main(): Promise { await createAssets(contractOrg1); // Read asset from the Org1's private data collection with ID in the given range. - await getAssetsByRange(contractOrg1); + await getAssetByRange(contractOrg1); try { - // Attempt to transfer asset without prior aprroval from Org2, transaction expected to fail. + // Attempt to transfer asset without prior approval from Org2, transaction expected to fail. console.log('\nAttempt TransferAsset without prior AgreeToTransfer'); await transferAsset(contractOrg1, assetID1); doFail('TransferAsset transaction succeeded when it was expected to fail'); @@ -101,7 +101,7 @@ async function main(): Promise { // Transfer asset to Org2. await transferAsset(contractOrg1, assetID1); - // Again ReadAsset : results will show that the buyer identity now owns the asset. + // Again ReadAsset: results will show that the buyer identity now owns the asset. await readAssetByID(contractOrg1, assetID1); // Confirm that transfer removed the private details from the Org1 collection. @@ -112,7 +112,7 @@ async function main(): Promise { console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); - // Org2 can read asset private details: Org2 is owner, and private details exist in new owner's Collection + // Org2 can read asset private details: Org2 is owner, and private details exist in new owner's collection. const org2ReadSuccess = await readAssetPrivateDetails(contractOrg2, assetID1, org2PrivateCollectionName); if (!org2ReadSuccess) { doFail(`Asset private data not found in ${org2PrivateCollectionName}`); @@ -131,8 +131,8 @@ async function main(): Promise { // Delete AssetID2 as Org1. await deleteAsset(contractOrg1, assetID2); - // Trigger a purge of the private data for the asset - // The previous delete is optinal if purge is used + // Trigger a purge of the private data for the asset. + // The previous delete is optional if purge is used. await purgeAsset(contractOrg1, assetID2); } finally { gatewayOrg1.close(); @@ -186,9 +186,9 @@ async function createAssets(contract: Contract): Promise { console.log('*** Transaction committed successfully'); } -async function getAssetsByRange(contract: Contract): Promise { +async function getAssetByRange(contract: Contract): Promise { // GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive). - console.log(`\n--> Evaluate Transaction: ReadAssetPrivateDetails from ${org1PrivateCollectionName}`); + console.log(`\n--> Evaluate Transaction: GetAssetByRange from ${org1PrivateCollectionName}`); const resultBytes = await contract.evaluateTransaction( 'GetAssetByRange', @@ -198,7 +198,7 @@ async function getAssetsByRange(contract: Contract): Promise { const resultString = utf8Decoder.decode(resultBytes); if (!resultString) { - doFail('Received empty query list for readAssetPrivateDetailsOrg1'); + doFail('Received empty query list for GetAssetByRange'); } const result: unknown = JSON.parse(resultString); console.log('*** Result:', result); @@ -217,8 +217,8 @@ async function readAssetByID(contract: Contract, assetID: string): Promise } async function agreeToTransfer(contract: Contract, assetID: string): Promise { - // 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 + // 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. const dataForAgreement = { assetID, appraisedValue: 100 }; console.log('\n--> Submit Transaction: AgreeToTransfer, payload:', dataForAgreement); diff --git a/ci/scripts/run-test-network-private.sh b/ci/scripts/run-test-network-private.sh index c973409b82..914c94ba4b 100755 --- a/ci/scripts/run-test-network-private.sh +++ b/ci/scripts/run-test-network-private.sh @@ -34,3 +34,12 @@ print "Start application" npm start popd stopNetwork + +# Run Go gateway application +createNetwork +print "Initializing Go gateway application" +pushd ../asset-transfer-private-data/application-gateway-go +print "Executing application" +go run . +popd +stopNetwork