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