From 1114bf8896e872f949f50f4b2d1517f3c3f90f7b Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Fri, 7 Jun 2024 17:52:09 +0100 Subject: [PATCH] Remove legacy sample applications The removed samples make use of deprecated legacy client SDKs. They all have equivalent samples implemented using the currently supported Fabric Gateway client API, and are therefore redundant. Signed-off-by: Mark S. Lewis --- .github/workflows/test-network-gateway.yaml | 39 -- .github/workflows/test-network-hsm.yaml | 7 +- .../application-go/.gitignore | 4 - .../application-go/assetTransfer.go | 169 ------ asset-transfer-basic/application-go/go.mod | 49 -- asset-transfer-basic/application-go/go.sum | 234 -------- .../application-java/.gitattributes | 6 - .../application-java/build.gradle | 42 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - asset-transfer-basic/application-java/gradlew | 249 -------- .../application-java/gradlew.bat | 92 --- .../application-java/settings.gradle | 10 - .../src/main/java/application/java/App.java | 120 ---- .../java/application/java/EnrollAdmin.java | 59 -- .../java/application/java/RegisterUser.java | 107 ---- .../src/main/resources/log4j.properties | 19 - .../application-javascript/.eslintignore | 5 - .../application-javascript/.eslintrc.js | 36 -- .../application-javascript/.gitignore | 14 - .../application-javascript/app.js | 183 ------ .../application-javascript/package.json | 23 - .../application-typescript-hsm/.gitignore | 15 - .../application-typescript-hsm/README.md | 263 --------- .../application-typescript-hsm/package.json | 51 -- .../application-typescript-hsm/softhsm2.conf | 5 - .../application-typescript-hsm/src/app.ts | 286 --------- .../src/utils/AppUtil.ts | 56 -- .../src/utils/CAUtil.ts | 94 --- .../application-typescript-hsm/tsconfig.json | 18 - .../application-typescript-hsm/tslint.json | 24 - .../application-typescript/.gitignore | 15 - .../application-typescript/package.json | 51 -- .../application-typescript/src/app.ts | 172 ------ .../src/utils/AppUtil.ts | 76 --- .../src/utils/CAUtil.ts | 104 ---- .../application-typescript/tsconfig.json | 15 - .../application-typescript/tslint.json | 23 - .../application-javascript/.eslintignore | 5 - .../application-javascript/.eslintrc.js | 37 -- .../application-javascript/.gitignore | 14 - .../application-javascript/app.js | 545 ------------------ .../application-javascript/package.json | 22 - asset-transfer-private-data/README.md | 6 - .../application-javascript/.eslintignore | 5 - .../application-javascript/.eslintrc.js | 37 -- .../application-javascript/.gitignore | 16 - .../application-javascript/app.js | 358 ------------ .../application-javascript/package.json | 45 -- asset-transfer-secured-agreement/README.md | 4 - .../application-javascript/.eslintignore | 5 - .../application-javascript/.eslintrc.js | 37 -- .../application-javascript/.gitignore | 14 - .../application-javascript/app.js | 544 ----------------- .../application-javascript/package.json | 22 - ci/scripts/run-test-network-basic.sh | 44 +- ci/scripts/run-test-network-events.sh | 19 +- ci/scripts/run-test-network-gateway.sh | 76 --- ci/scripts/run-test-network-hsm.sh | 23 +- ci/scripts/run-test-network-private.sh | 17 +- ci/scripts/run-test-network-secured.sh | 18 +- .../application-typescript/package.json | 2 +- .../scripts/generate-hsm-user.sh | 68 ++- .../.eslintrc.yaml | 10 - .../legacy-application-javascript/.gitignore | 5 - .../legacy-application-javascript/README.md | 328 ----------- .../addAssets.js | 118 ---- .../blockEventListener.js | 179 ------ .../blockProcessing.js | 217 ------- .../legacy-application-javascript/config.json | 7 - .../couchdbutil.js | 111 ---- .../deleteAsset.js | 69 --- .../enrollAdmin.js | 55 -- .../network-clean.sh | 20 - .../package.json | 45 -- .../registerUser.js | 75 --- .../startFabric.sh | 36 -- .../transferAsset.js | 69 --- 78 files changed, 78 insertions(+), 5991 deletions(-) delete mode 100644 .github/workflows/test-network-gateway.yaml delete mode 100755 asset-transfer-basic/application-go/.gitignore delete mode 100644 asset-transfer-basic/application-go/assetTransfer.go delete mode 100644 asset-transfer-basic/application-go/go.mod delete mode 100644 asset-transfer-basic/application-go/go.sum delete mode 100644 asset-transfer-basic/application-java/.gitattributes delete mode 100644 asset-transfer-basic/application-java/build.gradle delete mode 100644 asset-transfer-basic/application-java/gradle/wrapper/gradle-wrapper.jar delete mode 100644 asset-transfer-basic/application-java/gradle/wrapper/gradle-wrapper.properties delete mode 100755 asset-transfer-basic/application-java/gradlew delete mode 100644 asset-transfer-basic/application-java/gradlew.bat delete mode 100644 asset-transfer-basic/application-java/settings.gradle delete mode 100644 asset-transfer-basic/application-java/src/main/java/application/java/App.java delete mode 100644 asset-transfer-basic/application-java/src/main/java/application/java/EnrollAdmin.java delete mode 100644 asset-transfer-basic/application-java/src/main/java/application/java/RegisterUser.java delete mode 100644 asset-transfer-basic/application-java/src/main/resources/log4j.properties delete mode 100644 asset-transfer-basic/application-javascript/.eslintignore delete mode 100644 asset-transfer-basic/application-javascript/.eslintrc.js delete mode 100644 asset-transfer-basic/application-javascript/.gitignore delete mode 100644 asset-transfer-basic/application-javascript/app.js delete mode 100644 asset-transfer-basic/application-javascript/package.json delete mode 100644 asset-transfer-basic/application-typescript-hsm/.gitignore delete mode 100644 asset-transfer-basic/application-typescript-hsm/README.md delete mode 100644 asset-transfer-basic/application-typescript-hsm/package.json delete mode 100644 asset-transfer-basic/application-typescript-hsm/softhsm2.conf delete mode 100644 asset-transfer-basic/application-typescript-hsm/src/app.ts delete mode 100644 asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts delete mode 100644 asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts delete mode 100644 asset-transfer-basic/application-typescript-hsm/tsconfig.json delete mode 100644 asset-transfer-basic/application-typescript-hsm/tslint.json delete mode 100644 asset-transfer-basic/application-typescript/.gitignore delete mode 100644 asset-transfer-basic/application-typescript/package.json delete mode 100644 asset-transfer-basic/application-typescript/src/app.ts delete mode 100644 asset-transfer-basic/application-typescript/src/utils/AppUtil.ts delete mode 100644 asset-transfer-basic/application-typescript/src/utils/CAUtil.ts delete mode 100644 asset-transfer-basic/application-typescript/tsconfig.json delete mode 100644 asset-transfer-basic/application-typescript/tslint.json delete mode 100644 asset-transfer-events/application-javascript/.eslintignore delete mode 100644 asset-transfer-events/application-javascript/.eslintrc.js delete mode 100644 asset-transfer-events/application-javascript/.gitignore delete mode 100644 asset-transfer-events/application-javascript/app.js delete mode 100644 asset-transfer-events/application-javascript/package.json delete mode 100644 asset-transfer-private-data/application-javascript/.eslintignore delete mode 100644 asset-transfer-private-data/application-javascript/.eslintrc.js delete mode 100644 asset-transfer-private-data/application-javascript/.gitignore delete mode 100644 asset-transfer-private-data/application-javascript/app.js delete mode 100644 asset-transfer-private-data/application-javascript/package.json delete mode 100644 asset-transfer-secured-agreement/application-javascript/.eslintignore delete mode 100644 asset-transfer-secured-agreement/application-javascript/.eslintrc.js delete mode 100644 asset-transfer-secured-agreement/application-javascript/.gitignore delete mode 100644 asset-transfer-secured-agreement/application-javascript/app.js delete mode 100644 asset-transfer-secured-agreement/application-javascript/package.json delete mode 100755 ci/scripts/run-test-network-gateway.sh delete mode 100644 off_chain_data/legacy-application-javascript/.eslintrc.yaml delete mode 100644 off_chain_data/legacy-application-javascript/.gitignore delete mode 100644 off_chain_data/legacy-application-javascript/README.md delete mode 100644 off_chain_data/legacy-application-javascript/addAssets.js delete mode 100644 off_chain_data/legacy-application-javascript/blockEventListener.js delete mode 100644 off_chain_data/legacy-application-javascript/blockProcessing.js delete mode 100644 off_chain_data/legacy-application-javascript/config.json delete mode 100644 off_chain_data/legacy-application-javascript/couchdbutil.js delete mode 100644 off_chain_data/legacy-application-javascript/deleteAsset.js delete mode 100644 off_chain_data/legacy-application-javascript/enrollAdmin.js delete mode 100755 off_chain_data/legacy-application-javascript/network-clean.sh delete mode 100644 off_chain_data/legacy-application-javascript/package.json delete mode 100644 off_chain_data/legacy-application-javascript/registerUser.js delete mode 100755 off_chain_data/legacy-application-javascript/startFabric.sh delete mode 100644 off_chain_data/legacy-application-javascript/transferAsset.js diff --git a/.github/workflows/test-network-gateway.yaml b/.github/workflows/test-network-gateway.yaml deleted file mode 100644 index 47ec8a0a15..0000000000 --- a/.github/workflows/test-network-gateway.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# -name: Test Network Gateway 🖥️ -run-name: ${{ github.actor }} is running the Test Network Gateway tests 🖥️ - -on: - workflow_dispatch: - push: - branches: [ "main", "release-2.5" ] - pull_request: - branches: [ "main", "release-2.5" ] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - gateway: - runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-20.04' || 'ubuntu-20.04' }} - strategy: - matrix: - chaincode-language: - - go - - javascript - - typescript - - java - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up the test network runtime - uses: ./.github/actions/test-network-setup - - - name: Run Test - working-directory: test-network - env: - CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }} - run: ../ci/scripts/run-test-network-gateway.sh diff --git a/.github/workflows/test-network-hsm.yaml b/.github/workflows/test-network-hsm.yaml index 0128368d0a..d904b82493 100644 --- a/.github/workflows/test-network-hsm.yaml +++ b/.github/workflows/test-network-hsm.yaml @@ -36,13 +36,16 @@ jobs: run: sudo apt install -y softhsm2 - name: Set up SoftHSM + env: + TMPDIR: ${{ runner.temp }} + SOFTHSM2_CONF: ${{ github.workspace }}/softhsm2.conf run: | - echo directories.tokendir = /tmp > $HOME/softhsm2.conf - export SOFTHSM2_CONF=$HOME/softhsm2.conf + echo "directories.tokendir = ${TMPDIR}" > "${SOFTHSM2_CONF}" softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 - name: Run Test Network HSM working-directory: test-network env: CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }} + SOFTHSM2_CONF: ${{ github.workspace }}/softhsm2.conf run: ../ci/scripts/run-test-network-hsm.sh diff --git a/asset-transfer-basic/application-go/.gitignore b/asset-transfer-basic/application-go/.gitignore deleted file mode 100755 index e9fec12f6e..0000000000 --- a/asset-transfer-basic/application-go/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -wallet -!wallet/.gitkeep - -keystore diff --git a/asset-transfer-basic/application-go/assetTransfer.go b/asset-transfer-basic/application-go/assetTransfer.go deleted file mode 100644 index 2b0b98c749..0000000000 --- a/asset-transfer-basic/application-go/assetTransfer.go +++ /dev/null @@ -1,169 +0,0 @@ -/* -Copyright 2020 IBM All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package main - -import ( - "fmt" - "log" - "os" - "path/filepath" - - "github.com/hyperledger/fabric-sdk-go/pkg/core/config" - "github.com/hyperledger/fabric-sdk-go/pkg/gateway" -) - -func main() { - log.Println("============ application-golang starts ============") - - err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true") - if err != nil { - log.Fatalf("Error setting DISCOVERY_AS_LOCALHOST environment variable: %v", err) - } - - walletPath := "wallet" - // remove any existing wallet from prior runs - os.RemoveAll(walletPath) - wallet, err := gateway.NewFileSystemWallet(walletPath) - if err != nil { - log.Fatalf("Failed to create wallet: %v", err) - } - - if !wallet.Exists("appUser") { - err = populateWallet(wallet) - if err != nil { - log.Fatalf("Failed to populate wallet contents: %v", err) - } - } - - ccpPath := filepath.Join( - "..", - "..", - "test-network", - "organizations", - "peerOrganizations", - "org1.example.com", - "connection-org1.yaml", - ) - - gw, err := gateway.Connect( - gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))), - gateway.WithIdentity(wallet, "appUser"), - ) - if err != nil { - log.Fatalf("Failed to connect to gateway: %v", err) - } - defer gw.Close() - - channelName := "mychannel" - if cname := os.Getenv("CHANNEL_NAME"); cname != "" { - channelName = cname - } - - log.Println("--> Connecting to channel", channelName) - network, err := gw.GetNetwork(channelName) - if err != nil { - log.Fatalf("Failed to get network: %v", err) - } - - chaincodeName := "basic" - if ccname := os.Getenv("CHAINCODE_NAME"); ccname != "" { - chaincodeName = ccname - } - - log.Println("--> Using chaincode", chaincodeName) - contract := network.GetContract(chaincodeName) - - log.Println("--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger") - result, err := contract.SubmitTransaction("InitLedger") - if err != nil { - log.Fatalf("Failed to Submit transaction: %v", err) - } - log.Println(string(result)) - - log.Println("--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger") - result, err = contract.EvaluateTransaction("GetAllAssets") - if err != nil { - log.Fatalf("Failed to evaluate transaction: %v", err) - } - log.Println(string(result)) - - log.Println("--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments") - result, err = contract.SubmitTransaction("CreateAsset", "asset113", "yellow", "5", "Tom", "1300") - if err != nil { - log.Fatalf("Failed to Submit transaction: %v", err) - } - log.Println(string(result)) - - log.Println("--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID") - result, err = contract.EvaluateTransaction("ReadAsset", "asset113") - if err != nil { - log.Fatalf("Failed to evaluate transaction: %v\n", err) - } - log.Println(string(result)) - - log.Println("--> Evaluate Transaction: AssetExists, function returns 'true' if an asset with given assetID exist") - result, err = contract.EvaluateTransaction("AssetExists", "asset1") - if err != nil { - log.Fatalf("Failed to evaluate transaction: %v\n", err) - } - log.Println(string(result)) - - log.Println("--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom") - _, err = contract.SubmitTransaction("TransferAsset", "asset1", "Tom") - if err != nil { - log.Fatalf("Failed to Submit transaction: %v", err) - } - - log.Println("--> Evaluate Transaction: ReadAsset, function returns 'asset1' attributes") - result, err = contract.EvaluateTransaction("ReadAsset", "asset1") - if err != nil { - log.Fatalf("Failed to evaluate transaction: %v", err) - } - log.Println(string(result)) - log.Println("============ application-golang ends ============") -} - -func populateWallet(wallet *gateway.Wallet) error { - log.Println("============ Populating wallet ============") - credPath := filepath.Join( - "..", - "..", - "test-network", - "organizations", - "peerOrganizations", - "org1.example.com", - "users", - "User1@org1.example.com", - "msp", - ) - - certPath := filepath.Join(credPath, "signcerts", "cert.pem") - // read the certificate pem - cert, err := os.ReadFile(filepath.Clean(certPath)) - if err != nil { - return err - } - - keyDir := filepath.Join(credPath, "keystore") - // there's a single file in this dir containing the private key - files, err := os.ReadDir(keyDir) - if err != nil { - return err - } - if len(files) != 1 { - return fmt.Errorf("keystore folder should have contain one file") - } - keyPath := filepath.Join(keyDir, files[0].Name()) - key, err := os.ReadFile(filepath.Clean(keyPath)) - if err != nil { - return err - } - - identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key)) - - return wallet.Put("appUser", identity) -} diff --git a/asset-transfer-basic/application-go/go.mod b/asset-transfer-basic/application-go/go.mod deleted file mode 100644 index c859f52288..0000000000 --- a/asset-transfer-basic/application-go/go.mod +++ /dev/null @@ -1,49 +0,0 @@ -module asset-transfer-basic - -go 1.18 - -require github.com/hyperledger/fabric-sdk-go v1.0.0 - -require ( - github.com/Knetic/govaluate v3.0.0+incompatible // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cloudflare/cfssl v1.4.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.4.7 // indirect - github.com/go-kit/kit v0.8.0 // indirect - github.com/go-logfmt/logfmt v0.4.0 // indirect - github.com/golang/mock v1.4.3 // indirect - github.com/golang/protobuf v1.3.3 // indirect - github.com/google/certificate-transparency-go v1.0.21 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hyperledger/fabric-config v0.0.5 // indirect - github.com/hyperledger/fabric-lib-go v1.0.0 // indirect - github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23 // indirect - github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect - github.com/magiconair/properties v1.8.1 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.3.2 // indirect - github.com/pelletier/go-toml v1.8.0 // indirect - github.com/pkg/errors v0.8.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.1.0 // indirect - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect - github.com/prometheus/common v0.6.0 // indirect - github.com/prometheus/procfs v0.0.3 // indirect - github.com/spf13/afero v1.3.1 // indirect - github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.1.1 // indirect - github.com/stretchr/testify v1.5.1 // indirect - github.com/weppos/publicsuffix-go v0.5.0 // indirect - github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e // indirect - github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb // indirect - golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect - golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect - golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect - golang.org/x/text v0.3.2 // indirect - google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect - google.golang.org/grpc v1.29.1 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect -) diff --git a/asset-transfer-basic/application-go/go.sum b/asset-transfer-basic/application-go/go.sum deleted file mode 100644 index 26bb2eb66f..0000000000 --- a/asset-transfer-basic/application-go/go.sum +++ /dev/null @@ -1,234 +0,0 @@ -bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= -github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= -github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= -github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= -github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw= -github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo= -github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4= -github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hyperledger/fabric-config v0.0.5 h1:khRkm8U9Ghdg8VmZfptgzCFlCzrka8bPfUkM+/j6Zlg= -github.com/hyperledger/fabric-config v0.0.5/go.mod h1:YpITBI/+ZayA3XWY5lF302K7PAsFYjEEPM/zr3hegA8= -github.com/hyperledger/fabric-lib-go v1.0.0 h1:UL1w7c9LvHZUSkIvHTDGklxFv2kTeva1QI2emOVc324= -github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc= -github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= -github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23 h1:SEbB3yH4ISTGRifDamYXAst36gO2kM855ndMJlsv+pc= -github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= -github.com/hyperledger/fabric-sdk-go v1.0.0 h1:NRu0iNbHV6u4nd9jgYghAdA1Ll4g0Sri4hwMEGiTbyg= -github.com/hyperledger/fabric-sdk-go v1.0.0/go.mod h1:qWE9Syfg1KbwNjtILk70bJLilnmCvllIYFCSY/pa1RU= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= -github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c= -github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/spf13/afero v1.3.1 h1:GPTpEAuNr98px18yNQ66JllNil98wfRZ/5Ukny8FeQA= -github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.1.1 h1:/8JBRFO4eoHu1TmpsLgNBq1CQgRUg4GolYlEFieqJgo= -github.com/spf13/viper v1.1.1/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU= -github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= -github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= -github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw= -github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8= -github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb h1:vxqkjztXSaPVDc8FQCdHTaejm2x747f6yPbnu1h2xkg= -github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/asset-transfer-basic/application-java/.gitattributes b/asset-transfer-basic/application-java/.gitattributes deleted file mode 100644 index 00a51aff5e..0000000000 --- a/asset-transfer-basic/application-java/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -# -# https://help.github.com/articles/dealing-with-line-endings/ -# -# These are explicitly windows files and should use crlf -*.bat text eol=crlf - diff --git a/asset-transfer-basic/application-java/build.gradle b/asset-transfer-basic/application-java/build.gradle deleted file mode 100644 index a204249d8b..0000000000 --- a/asset-transfer-basic/application-java/build.gradle +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Java project to get you started. - * For more details take a look at the Java Quickstart chapter in the Gradle - * User Manual available at https://docs.gradle.org/6.5/userguide/tutorial_java_projects.html - */ - -plugins { - // Apply the java plugin to add support for Java - id 'java' - - // Apply the application plugin to add support for building a CLI application. - id 'application' -} -ext { - javaMainClass = "application.java.App" -} - -repositories { - // You can declare any Maven/Ivy/file repository here. - mavenCentral() -} - -dependencies { - // This dependency is used by the application. - implementation 'com.google.guava:guava:29.0-jre' - implementation 'org.hyperledger.fabric:fabric-gateway-java:2.1.1' -} - -application { - // Define the main class for the application. - mainClassName = 'application.java.App' -} - -// task for running the app after building dependencies -task runApp(type: Exec) { - dependsOn build - group = "Execution" - description = "Run the main class with ExecTask" - commandLine "java", "-classpath", sourceSets.main.runtimeClasspath.getAsPath(), javaMainClass -} \ No newline at end of file diff --git a/asset-transfer-basic/application-java/gradle/wrapper/gradle-wrapper.jar b/asset-transfer-basic/application-java/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/asset-transfer-basic/application-java/gradle/wrapper/gradle-wrapper.properties b/asset-transfer-basic/application-java/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index a80b22ce5c..0000000000 --- a/asset-transfer-basic/application-java/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/asset-transfer-basic/application-java/gradlew b/asset-transfer-basic/application-java/gradlew deleted file mode 100755 index 0adc8e1a53..0000000000 --- a/asset-transfer-basic/application-java/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/asset-transfer-basic/application-java/gradlew.bat b/asset-transfer-basic/application-java/gradlew.bat deleted file mode 100644 index 93e3f59f13..0000000000 --- a/asset-transfer-basic/application-java/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/asset-transfer-basic/application-java/settings.gradle b/asset-transfer-basic/application-java/settings.gradle deleted file mode 100644 index 5423bc7de3..0000000000 --- a/asset-transfer-basic/application-java/settings.gradle +++ /dev/null @@ -1,10 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html - */ - -rootProject.name = 'application-java' diff --git a/asset-transfer-basic/application-java/src/main/java/application/java/App.java b/asset-transfer-basic/application-java/src/main/java/application/java/App.java deleted file mode 100644 index 7278b25f83..0000000000 --- a/asset-transfer-basic/application-java/src/main/java/application/java/App.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -// Running TestApp: -// gradle runApp - -package application.java; - -import java.nio.file.Path; -import java.nio.file.Paths; -import org.hyperledger.fabric.gateway.Contract; -import org.hyperledger.fabric.gateway.Gateway; -import org.hyperledger.fabric.gateway.Network; -import org.hyperledger.fabric.gateway.Wallet; -import org.hyperledger.fabric.gateway.Wallets; - - -public class App { - - private static final String CHANNEL_NAME = System.getenv().getOrDefault("CHANNEL_NAME", "mychannel"); - private static final String CHAINCODE_NAME = System.getenv().getOrDefault("CHAINCODE_NAME", "basic"); - - static { - System.setProperty("org.hyperledger.fabric.sdk.service_discovery.as_localhost", "true"); - } - - // helper function for getting connected to the gateway - public static Gateway connect() throws Exception{ - // Load a file system based wallet for managing identities. - Path walletPath = Paths.get("wallet"); - Wallet wallet = Wallets.newFileSystemWallet(walletPath); - // load a CCP - Path networkConfigPath = Paths.get("..", "..", "test-network", "organizations", "peerOrganizations", "org1.example.com", "connection-org1.yaml"); - - Gateway.Builder builder = Gateway.createBuilder(); - builder.identity(wallet, "javaAppUser").networkConfig(networkConfigPath).discovery(true); - return builder.connect(); - } - - public static void main(String[] args) throws Exception { - // enrolls the admin and registers the user - try { - EnrollAdmin.main(null); - RegisterUser.main(null); - } catch (Exception e) { - System.err.println(e); - } - - // connect to the network and invoke the smart contract - try (Gateway gateway = connect()) { - - // get the network and contract - Network network = gateway.getNetwork(CHANNEL_NAME); - Contract contract = network.getContract(CHAINCODE_NAME); - - byte[] result; - - System.out.println("Submit Transaction: InitLedger creates the initial set of assets on the ledger."); - contract.submitTransaction("InitLedger"); - - System.out.println("\n"); - result = contract.evaluateTransaction("GetAllAssets"); - System.out.println("Evaluate Transaction: GetAllAssets, result: " + new String(result)); - - System.out.println("\n"); - System.out.println("Submit Transaction: CreateAsset asset213"); - // CreateAsset creates an asset with ID asset213, color yellow, owner Tom, size 5 and appraisedValue of 1300 - contract.submitTransaction("CreateAsset", "asset213", "yellow", "5", "Tom", "1300"); - - System.out.println("\n"); - System.out.println("Evaluate Transaction: ReadAsset asset213"); - // ReadAsset returns an asset with given assetID - result = contract.evaluateTransaction("ReadAsset", "asset213"); - System.out.println("result: " + new String(result)); - - System.out.println("\n"); - System.out.println("Evaluate Transaction: AssetExists asset1"); - // AssetExists returns "true" if an asset with given assetID exist - result = contract.evaluateTransaction("AssetExists", "asset1"); - System.out.println("result: " + new String(result)); - - System.out.println("\n"); - System.out.println("Submit Transaction: UpdateAsset asset1, new AppraisedValue : 350"); - // UpdateAsset updates an existing asset with new properties. Same args as CreateAsset - contract.submitTransaction("UpdateAsset", "asset1", "blue", "5", "Tomoko", "350"); - - System.out.println("\n"); - System.out.println("Evaluate Transaction: ReadAsset asset1"); - result = contract.evaluateTransaction("ReadAsset", "asset1"); - System.out.println("result: " + new String(result)); - - try { - System.out.println("\n"); - System.out.println("Submit Transaction: UpdateAsset asset70"); - // Non existing asset asset70 should throw Error - contract.submitTransaction("UpdateAsset", "asset70", "blue", "5", "Tomoko", "300"); - } catch (Exception e) { - System.err.println("Expected an error on UpdateAsset of non-existing Asset: " + e); - } - - System.out.println("\n"); - System.out.println("Submit Transaction: TransferAsset asset1 from owner Tomoko > owner Tom"); - // TransferAsset transfers an asset with given ID to new owner Tom - contract.submitTransaction("TransferAsset", "asset1", "Tom"); - - System.out.println("\n"); - System.out.println("Evaluate Transaction: ReadAsset asset1"); - result = contract.evaluateTransaction("ReadAsset", "asset1"); - System.out.println("result: " + new String(result)); - } - catch(Exception e){ - System.err.println(e); - System.exit(1); - } - - } -} diff --git a/asset-transfer-basic/application-java/src/main/java/application/java/EnrollAdmin.java b/asset-transfer-basic/application-java/src/main/java/application/java/EnrollAdmin.java deleted file mode 100644 index 98f65e9017..0000000000 --- a/asset-transfer-basic/application-java/src/main/java/application/java/EnrollAdmin.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package application.java; - -import java.nio.file.Paths; -import java.util.Properties; - -import java.io.File; -import org.apache.commons.io.FileUtils; - -import org.hyperledger.fabric.gateway.Wallet; -import org.hyperledger.fabric.gateway.Wallets; -import org.hyperledger.fabric.gateway.Identities; -import org.hyperledger.fabric.gateway.Identity; -import org.hyperledger.fabric.sdk.Enrollment; -import org.hyperledger.fabric.sdk.security.CryptoSuite; -import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory; -import org.hyperledger.fabric_ca.sdk.EnrollmentRequest; -import org.hyperledger.fabric_ca.sdk.HFCAClient; - -public class EnrollAdmin { - - public static void main(String[] args) throws Exception { - - // Create a CA client for interacting with the CA. - Properties props = new Properties(); - props.put("pemFile", - "../../test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem"); - props.put("allowAllHostNames", "true"); - HFCAClient caClient = HFCAClient.createNewInstance("https://localhost:7054", props); - CryptoSuite cryptoSuite = CryptoSuiteFactory.getDefault().getCryptoSuite(); - caClient.setCryptoSuite(cryptoSuite); - - // Delete wallet if it exists from prior runs - FileUtils.deleteDirectory(new File("wallet")); - - // Create a wallet for managing identities - Wallet wallet = Wallets.newFileSystemWallet(Paths.get("wallet")); - - // Check to see if we've already enrolled the admin user. - if (wallet.get("admin") != null) { - System.out.println("An identity for the admin user \"admin\" already exists in the wallet"); - return; - } - - // Enroll the admin user, and import the new identity into the wallet. - final EnrollmentRequest enrollmentRequestTLS = new EnrollmentRequest(); - enrollmentRequestTLS.addHost("localhost"); - enrollmentRequestTLS.setProfile("tls"); - Enrollment enrollment = caClient.enroll("admin", "adminpw", enrollmentRequestTLS); - Identity user = Identities.newX509Identity("Org1MSP", enrollment); - wallet.put("admin", user); - System.out.println("Successfully enrolled user \"admin\" and imported it into the wallet"); - } -} diff --git a/asset-transfer-basic/application-java/src/main/java/application/java/RegisterUser.java b/asset-transfer-basic/application-java/src/main/java/application/java/RegisterUser.java deleted file mode 100644 index cf3a411a91..0000000000 --- a/asset-transfer-basic/application-java/src/main/java/application/java/RegisterUser.java +++ /dev/null @@ -1,107 +0,0 @@ -/* -SPDX-License-Identifier: Apache-2.0 -*/ - -package application.java; - -import java.nio.file.Paths; -import java.security.PrivateKey; -import java.util.Properties; -import java.util.Set; - -import org.hyperledger.fabric.gateway.Wallet; -import org.hyperledger.fabric.gateway.Wallets; -import org.hyperledger.fabric.gateway.Identities; -import org.hyperledger.fabric.gateway.Identity; -import org.hyperledger.fabric.gateway.X509Identity; -import org.hyperledger.fabric.sdk.Enrollment; -import org.hyperledger.fabric.sdk.User; -import org.hyperledger.fabric.sdk.security.CryptoSuite; -import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory; -import org.hyperledger.fabric_ca.sdk.HFCAClient; -import org.hyperledger.fabric_ca.sdk.RegistrationRequest; - -public class RegisterUser { - - public static void main(String[] args) throws Exception { - - // Create a CA client for interacting with the CA. - Properties props = new Properties(); - props.put("pemFile", - "../../test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem"); - props.put("allowAllHostNames", "true"); - HFCAClient caClient = HFCAClient.createNewInstance("https://localhost:7054", props); - CryptoSuite cryptoSuite = CryptoSuiteFactory.getDefault().getCryptoSuite(); - caClient.setCryptoSuite(cryptoSuite); - - // Create a wallet for managing identities - Wallet wallet = Wallets.newFileSystemWallet(Paths.get("wallet")); - - // Check to see if we've already enrolled the user. - if (wallet.get("javaAppUser") != null) { - System.out.println("An identity for the user \"javaAppUser\" already exists in the wallet"); - return; - } - - X509Identity adminIdentity = (X509Identity)wallet.get("admin"); - if (adminIdentity == null) { - System.out.println("\"admin\" needs to be enrolled and added to the wallet first"); - return; - } - User admin = new User() { - - @Override - public String getName() { - return "admin"; - } - - @Override - public Set getRoles() { - return null; - } - - @Override - public String getAccount() { - return null; - } - - @Override - public String getAffiliation() { - return "org1.department1"; - } - - @Override - public Enrollment getEnrollment() { - return new Enrollment() { - - @Override - public PrivateKey getKey() { - return adminIdentity.getPrivateKey(); - } - - @Override - public String getCert() { - return Identities.toPemString(adminIdentity.getCertificate()); - } - }; - } - - @Override - public String getMspId() { - return "Org1MSP"; - } - - }; - - // Register the user, enroll the user, and import the new identity into the wallet. - RegistrationRequest registrationRequest = new RegistrationRequest("javaAppUser"); - registrationRequest.setAffiliation("org1.department1"); - registrationRequest.setEnrollmentID("javaAppUser"); - String enrollmentSecret = caClient.register(registrationRequest, admin); - Enrollment enrollment = caClient.enroll("javaAppUser", enrollmentSecret); - Identity user = Identities.newX509Identity("Org1MSP", enrollment); - wallet.put("javaAppUser", user); - System.out.println("Successfully enrolled user \"javaAppUser\" and imported it into the wallet"); - } - -} diff --git a/asset-transfer-basic/application-java/src/main/resources/log4j.properties b/asset-transfer-basic/application-java/src/main/resources/log4j.properties deleted file mode 100644 index f1f841feb4..0000000000 --- a/asset-transfer-basic/application-java/src/main/resources/log4j.properties +++ /dev/null @@ -1,19 +0,0 @@ -# initialize root logger with level ERROR for stdout and fout -log4j.rootLogger=ERROR,stdout,fout -# set the log level for these components -log4j.logger.com.endeca=INFO -log4j.logger.com.endeca.itl.web.metrics=INFO - -# add a ConsoleAppender to the logger stdout to write to the console -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -# use a simple message format -log4j.appender.stdout.layout.ConversionPattern=%m%n - -# add a FileAppender to the logger fout -log4j.appender.fout=org.apache.log4j.FileAppender -# create a log file -log4j.appender.fout.File=crawl.log -log4j.appender.fout.layout=org.apache.log4j.PatternLayout -# use a more detailed message pattern -log4j.appender.fout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n diff --git a/asset-transfer-basic/application-javascript/.eslintignore b/asset-transfer-basic/application-javascript/.eslintignore deleted file mode 100644 index 1595847010..0000000000 --- a/asset-transfer-basic/application-javascript/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -coverage diff --git a/asset-transfer-basic/application-javascript/.eslintrc.js b/asset-transfer-basic/application-javascript/.eslintrc.js deleted file mode 100644 index 6fa636ba89..0000000000 --- a/asset-transfer-basic/application-javascript/.eslintrc.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - */ - -module.exports = { - env: { - node: true, - mocha: true - }, - parserOptions: { - ecmaVersion: 8, - sourceType: 'script' - }, - extends: "eslint:recommended", - rules: { - indent: ['error', 'tab'], - 'linebreak-style': ['error', 'unix'], - quotes: ['error', 'single'], - semi: ['error', 'always'], - 'no-unused-vars': ['error', { args: 'none' }], - 'no-console': 'off', - curly: 'error', - eqeqeq: 'error', - 'no-throw-literal': 'error', - strict: 'error', - 'no-var': 'error', - 'dot-notation': 'error', - 'no-trailing-spaces': 'error', - 'no-use-before-define': 'error', - 'no-useless-call': 'error', - 'no-with': 'error', - 'operator-linebreak': 'error', - yoda: 'error', - 'quote-props': ['error', 'as-needed'] - } -}; diff --git a/asset-transfer-basic/application-javascript/.gitignore b/asset-transfer-basic/application-javascript/.gitignore deleted file mode 100644 index 21b287f72d..0000000000 --- a/asset-transfer-basic/application-javascript/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -# Coverage directory used by tools like istanbul -coverage - -# Dependency directories -node_modules/ -jspm_packages/ -package-lock.json - -wallet -!wallet/.gitkeep diff --git a/asset-transfer-basic/application-javascript/app.js b/asset-transfer-basic/application-javascript/app.js deleted file mode 100644 index b27a5c2c19..0000000000 --- a/asset-transfer-basic/application-javascript/app.js +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; - -const { Gateway, Wallets } = require('fabric-network'); -const FabricCAServices = require('fabric-ca-client'); -const path = require('path'); -const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js'); -const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js'); - -const channelName = process.env.CHANNEL_NAME || 'mychannel'; -const chaincodeName = process.env.CHAINCODE_NAME || 'basic'; - -const mspOrg1 = 'Org1MSP'; -const walletPath = path.join(__dirname, 'wallet'); -const org1UserId = 'javascriptAppUser'; - -function prettyJSONString(inputString) { - return JSON.stringify(JSON.parse(inputString), null, 2); -} - -// pre-requisites: -// - fabric-sample two organization test-network setup with two peers, ordering service, -// and 2 certificate authorities -// ===> from directory /fabric-samples/test-network -// ./network.sh up createChannel -ca -// - Use any of the asset-transfer-basic chaincodes deployed on the channel "mychannel" -// with the chaincode name of "basic". The following deploy command will package, -// install, approve, and commit the javascript chaincode, all the actions it takes -// to deploy a chaincode to a channel. -// ===> from directory /fabric-samples/test-network -// ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript -// - Be sure that node.js is installed -// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript -// node -v -// - npm installed code dependencies -// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript -// npm install -// - to run this test application -// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript -// node app.js - -// NOTE: If you see kind an error like these: -/* - 2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied - ******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied - - OR - - Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] - ******** FAILED to run the application: Error: Identity not found in wallet: appUser -*/ -// Delete the /fabric-samples/asset-transfer-basic/application-javascript/wallet directory -// and retry this application. -// -// The certificate authority must have been restarted and the saved certificates for the -// admin and application user are not valid. Deleting the wallet store will force these to be reset -// with the new certificate authority. -// - -/** - * A test application to show basic queries operations with any of the asset-transfer-basic chaincodes - * -- How to submit a transaction - * -- How to query and check the results - * - * To see the SDK workings, try setting the logging to show on the console before running - * export HFC_LOGGING='{"debug":"console"}' - */ -async function main() { - try { - // build an in memory object with the network configuration (also known as a connection profile) - const ccp = buildCCPOrg1(); - - // build an instance of the fabric ca services client based on - // the information in the network configuration - const caClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com'); - - // setup the wallet to hold the credentials of the application user - const wallet = await buildWallet(Wallets, walletPath); - - // in a real application this would be done on an administrative flow, and only once - await enrollAdmin(caClient, wallet, mspOrg1); - - // in a real application this would be done only when a new user was required to be added - // and would be part of an administrative flow - await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1'); - - // Create a new gateway instance for interacting with the fabric network. - // In a real application this would be done as the backend server session is setup for - // a user that has been verified. - const gateway = new Gateway(); - - try { - // setup the gateway instance - // The user will now be able to create connections to the fabric network and be able to - // submit transactions and query. All transactions submitted by this gateway will be - // signed by this user using the credentials stored in the wallet. - await gateway.connect(ccp, { - wallet, - identity: org1UserId, - discovery: { enabled: true, asLocalhost: true } // using asLocalhost as this gateway is using a fabric network deployed locally - }); - - // Build a network instance based on the channel where the smart contract is deployed - const network = await gateway.getNetwork(channelName); - - // Get the contract from the network. - const contract = network.getContract(chaincodeName); - - // Initialize a set of asset data on the channel using the chaincode 'InitLedger' function. - // This type of transaction would only be run once by an application the first time it was started after it - // deployed the first time. Any updates to the chaincode deployed later would likely not need to run - // an "init" type function. - console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger'); - await contract.submitTransaction('InitLedger'); - console.log('*** Result: committed'); - - // Let's try a query type operation (function). - // This will be sent to just one peer and the results will be shown. - console.log('\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger'); - let result = await contract.evaluateTransaction('GetAllAssets'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - // Now let's try to submit a transaction. - // This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent - // to the orderer to be committed by each of the peer's to the channel ledger. - console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments'); - result = await contract.submitTransaction('CreateAsset', 'asset313', 'yellow', '5', 'Tom', '1300'); - console.log('*** Result: committed'); - if (`${result}` !== '') { - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - } - - console.log('\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID'); - result = await contract.evaluateTransaction('ReadAsset', 'asset313'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - console.log('\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with given assetID exist'); - result = await contract.evaluateTransaction('AssetExists', 'asset1'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - console.log('\n--> Submit Transaction: UpdateAsset asset1, change the appraisedValue to 350'); - await contract.submitTransaction('UpdateAsset', 'asset1', 'blue', '5', 'Tomoko', '350'); - console.log('*** Result: committed'); - - console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes'); - result = await contract.evaluateTransaction('ReadAsset', 'asset1'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - try { - // How about we try a transactions where the executing chaincode throws an error - // Notice how the submitTransaction will throw an error containing the error thrown by the chaincode - console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error'); - await contract.submitTransaction('UpdateAsset', 'asset70', 'blue', '5', 'Tomoko', '300'); - console.log('******** FAILED to return an error'); - } catch (error) { - console.log(`*** Successfully caught the error: \n ${error}`); - } - - console.log('\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom'); - await contract.submitTransaction('TransferAsset', 'asset1', 'Tom'); - console.log('*** Result: committed'); - - console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes'); - result = await contract.evaluateTransaction('ReadAsset', 'asset1'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - } finally { - // Disconnect from the gateway when the application is closing - // This will close all connections to the network - gateway.disconnect(); - } - } catch (error) { - console.error(`******** FAILED to run the application: ${error}`); - process.exit(1); - } -} - - -main(); diff --git a/asset-transfer-basic/application-javascript/package.json b/asset-transfer-basic/application-javascript/package.json deleted file mode 100644 index a5f5f35a19..0000000000 --- a/asset-transfer-basic/application-javascript/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "asset-transfer-basic", - "version": "1.0.0", - "description": "Asset-transfer-basic application implemented in JavaScript", - "engines": { - "node": ">=14.14", - "npm": ">=6" - }, - "scripts": { - "lint": "eslint *.js", - "pretest": "npm run lint" - }, - "engineStrict": true, - "author": "Hyperledger", - "license": "Apache-2.0", - "dependencies": { - "fabric-ca-client": "^2.2.19", - "fabric-network": "^2.2.19" - }, - "devDependencies": { - "eslint": "^7.32.0" - } -} diff --git a/asset-transfer-basic/application-typescript-hsm/.gitignore b/asset-transfer-basic/application-typescript-hsm/.gitignore deleted file mode 100644 index 48285d12a9..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - - -# Coverage directory used by tools like istanbul -coverage - -# Dependency directories -node_modules/ -jspm_packages/ - -# Compiled TypeScript files -dist - diff --git a/asset-transfer-basic/application-typescript-hsm/README.md b/asset-transfer-basic/application-typescript-hsm/README.md deleted file mode 100644 index 8b87a82c29..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/README.md +++ /dev/null @@ -1,263 +0,0 @@ -# Asset Transfer Basic typescript HSM sample - -This sample takes the Asset Transfer basic example and re-works it to focus on how -you would use a Hardware Security Modules within your node client application. - -## About the Sample - -This typescript sample application is able to use any of the asset transfer basic -chaincode samples. It will show how to register users with a Fabric CA and enroll users which will store keys in an HSM (In this case the sample uses SoftHSM which is an HSM implementation that should be used for development and testing purposes only). It also demonstrates setting up a wallet that will store identities that can then be used to transact on the fabric network which are HSM managed. - -## C Compilers - -In order for the client application to run successfully you must ensure you have C compilers and Python 3 (Note that Python 2 may still work however Python 2 is out of support and could stop working in the future) installed otherwise the node dependency `pkcs11js` will not be built and the application will fail. The failure will look like - -``` -Loaded the network configuration located at /home/dave/temp/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection-org1.json -******** FAILED to run the application: Error: Cannot find module 'pkcs11js' -Require stack: -- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/lib/impl/bccsp_pkcs11.js -- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/lib/Utils.js -- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/index.js -- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-ca-client/lib/FabricCAServices.js -- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-ca-client/index.js -- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/dist/app.js -``` - -how to install the required C Compilers and Python will depend on your operating system and version. - -## Configuring and running a Hardware Security Module - -This sample sets the hsmOptions for the wallet as follows - -```javascript -const softHSMOptions: HsmOptions = { - lib: await findSoftHSMPKCS11Lib(), - pin: process.env.PKCS11_PIN || '98765432', - label: process.env.PKCS11_LABEL || 'ForFabric', -}; -``` - -which is specific to using SoftHSM which has been initialised with a token labelled `ForFabric` -and a user pin of `98765432`, however you can override these values to use your own HSM by either -editting the application or use these environment variables to pass in the values: - -* PKCS11_LIB - path to the your specific HSM Library -* PKCS11_PIN - your HSM pin -* PKCS11_LABEL - your HSM label - -Alternatively you could install SoftHSM to try out the application as described in the next sections - -### Install SoftHSM - -In order to run the application in the absence of a real HSM, a software -emulator of the PKCS#11 interface is required. -For more information please refer to [SoftHSM](https://www.opendnssec.org/softhsm/). - -SoftHSM can either be installed using the package manager for your host system: - -* Ubuntu: `sudo apt install softhsm2` -* macOS: `brew install softhsm` -* Windows: **unsupported** - -Or compiled and installed from source: - -1. install openssl 1.0.0+ or botan 1.10.0+ -2. download the source code from -3. `tar -xvf softhsm-2.5.0.tar.gz` -4. `cd softhsm-2.5.0` -5. `./configure --disable-gost` (would require additional libraries, turn it off unless you need 'gost' algorithm support for the Russian market) -6. `make` -7. `sudo make install` - -### Specify the SoftHSM configuration file - -A configuration file for SoftHSM is provided in this sample directory. This file -uses /tmp as the location for SoftHSM to store it's data which means (depending on -your operating system configuration) the data could be deleted at some point, for example -when you reboot your system. If this data is lost then you will have to delete the wallet -created. An alternative is to change this file to store SoftHSM data in a permanent location -on your file system. -To use this configuration file you need to export an environment variable to point to it -for example, if you are in the application directory then you can use the following command - -```bash -export SOFTHSM2_CONF=$PWD/softhsm2.conf -``` - -Ensure you have this set when initializing the token as well as running the application - -### Initialize a token to store keys in SoftHSM - -If you have not initialized a token previously (or it has been deleted) then you will need to perform this one time operation - -```bash -softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 -``` - -The Security Officer PIN, specified with the `--so-pin` flag, can be used to re-initialize the token, -and the user PIN (see below), specified with the `--pin` flag, is used by applications to access the token for -generating and retrieving keys. - -## Running the sample - -Use the Fabric test network utility (`network.sh`) to start a Fabric network and deploy one -one of the sample chaincodes. - -Follow these step in order. -- Start the test network with a Certificate Authority and create a channel, so assuming you are in the ensuring you are in the `application-typescript-hsm` directory - -``` -cd ../../test-network -./network.sh down -./network.sh up createChannel -c mychannel -ca -``` - -- Deploy the chaincode (smart contract) - -``` -# to deploy the javascript version -./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl javascript -ccp ./../asset-transfer-basic/chaincode-javascript/ -ccn basic -``` - -- Run the application - -``` -cd ../asset-transfer-basic/application-typescript-hsm -npm install -npm run build -node dist/app.js -``` - -### Expected successful execution - -If the sample runs successfully then it will output several messages showing the various actions and finally display the message - -``` -*** The sample ran successfully *** -``` - -One the first run of the sample, a CA admin id from Org1 will be enrolled from the CA. The certificate for this admin will be stored in the application wallet but the private key will have been stored in the HSM, it will not be in the application wallet. A User in Org1 will be registered in the the Org1 CA and then enrolled. Again only it's certificate will be stored in the application wallet. -All signing of transactions done by the application (driven by the node sdk) will actually be done by the HSM rather than within the node sdk as the private key will never be directly available outside of the HSM. - -This sample can be run multiple times even while the same network remains up. As the certificates are already in the application wallet it will not have to enroll a CA admin or register and enroll a user. - -### Possible issues you could encounter running the sample - -If you see this error: - -``` -******** FAILED to run the application: Pkcs11Error: CKR_GENERAL_ERROR -``` -Make sure you have exported SOFTHSM2_CONF correctly and it points to a valid SoftHSM configuration file that references -a directory containing a initialised SoftHSM token - - -If you see this error: - -``` -2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied -******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied -``` - -Then the current certificates in your wallet are not valid for the network you are trying to interact with. This would happen if you had -shutdown the fabric network using `network.sh down` and created a new network because this causes all the root certificates to be recreated -To address this problem, you can delete the `wallet` directory in the `dist` folder (`fabric-samples/asset-transfer-basic/application-typescript-hsm/dist/wallet`) of this sample to create new certificates. Because new keypairs are generated these will be stored in SoftHSM and the existing old ones will not be referenced - -If you see this error - -``` -Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] -******** FAILED to run the application: Error: Identity not found in wallet: appUser -``` - -Then the most likely cause is you have deleted your wallet. You would need to either -- stop and create a new fabric network using the `network.sh down` and then following the above instructions to start a new fabric network (but you should not re-initialize the softhsm token) -- or change the application such that it registers a new user instead of `appUser`. This is because be default a registered user can only be enrolled once (using the userid and it's secret) - -If you see this error - -``` -******** FAILED to run the application: Error: _pkcs11OpenSession[336]: PKCS11 label ForFabric cannot be found in the slot list -``` - -Then the SoftHSM token directory has been deleted (could be due to the /tmp file being cleaned up if you use the sample softhsm2.conf file provided). -You will either need to -- delete your existing wallet, bring down the network as described in `Clean up` section and recreate the network including re-initializing the softhsm token -- or you could just re-initialise the softhsm token but you will need to change the application so that it registers a new user instead of `appUser` - -If you see this error (note the number following `SKI` will not be the same) - -``` -******** FAILED to run the application: Error: _pkcs11SkiToHandle[572]: no key with SKI 27f3557183cd5f26384ab69968ba74944c94c0e24f681c4fadd6502886891da0 found -``` - -Then the certificates in your wallet do not have corresponding keys in SoftHSM. You can should bring down the network and delete your current wallet (as described in `Clean up` section) and create the network again - - -If you see either of these errors when the application ends - -``` -free(): double free detected in tcache 2 -Aborted -``` - -or - -``` -node[61480]: ../src/node_http2.cc:521:void node::http2::Http2Session::CheckAllocatedSize(size_t) const: Assertion `(current_nghttp2_memory_) >= (previous_size)' failed. - 1: 0xa1a640 node::Abort() [node] - 2: 0xa1a6be [node] - 3: 0xa55e2a node::mem::NgLibMemoryManager::ReallocImpl(void*, unsigned long, void*) [node] - 4: 0xa55ed3 node::mem::NgLibMemoryManager::FreeImpl(void*, void*) [node] - 5: 0x18b0388 nghttp2_session_close_stream [node] - 6: 0x18b76ea nghttp2_session_mem_recv [node] - 7: 0xa54937 node::http2::Http2Session::ConsumeHTTP2Data() [node] - 8: 0xa54d5e node::http2::Http2Session::OnStreamRead(long, uv_buf_t const&) [node] - 9: 0xb6a651 node::TLSWrap::ClearOut() [node] -10: 0xb6bcdb node::TLSWrap::OnStreamRead(long, uv_buf_t const&) [node] -11: 0xaf54b1 [node] -12: 0x137fed9 [node] -13: 0x1380500 [node] -14: 0x1386de5 [node] -15: 0x137458f uv_run [node] -16: 0xa5d7a6 node::NodeMainInstance::Run() [node] -17: 0x9eab6c node::Start(int, char**) [node] -18: 0x7fd7612180b3 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6] -19: 0x981fe5 [node] -Aborted (core dumped) -``` - -then this is due to a bug in `node`. May sure you are using the latest supported version of node, however at the time of writing (node 14.17.1 & node 12.22.1) a fix had not been released by node.js - -### Enabling Node SDK logging - -To see the SDK workings, try setting the logging to show on the console before running -``` -export HFC_LOGGING='{"debug":"console"}' -``` -or log to a file -``` -export HFC_LOGGING='{"debug":"./debug.log"}' -``` - -## Clean up - -When you are finished, you can bring down the test network. -The command will remove all the nodes of the test network, and delete any -ledger data that you created: - -```bash -cd ../../test-network -./network.sh down -``` -Be sure to delete the wallet directory create by the application. -The stored identities will no longer be valid when restarting the network. -For example if you are in the application-typescript-hsm directory - -```bash -rm -fr dist/wallet -``` - -## Suggestions -When typescript node applications log problems or terminate with a stack trace you normally would have to look at the compiled .js code to find the code that was executed in the stack trace. It would be easier if you could find the corresponding lines in the typescript source file. One solution to this problem can be found here https://github.com/evanw/node-source-map-support which will convert stack trace output to corresponding typescript file lines using the generated source maps. \ No newline at end of file diff --git a/asset-transfer-basic/application-typescript-hsm/package.json b/asset-transfer-basic/application-typescript-hsm/package.json deleted file mode 100644 index cd7606489c..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "asset-transfer-basic", - "version": "1.0.0", - "description": "Asset Transfer Basic contract implemented in TypeScript", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "engines": { - "node": ">=12", - "npm": ">=5" - }, - "scripts": { - "lint": "tslint -c tslint.json 'src/**/*.ts'", - "pretest": "npm run lint", - "start": "npm run build && node dist/app.js", - "build": "tsc", - "build:watch": "tsc -w", - "prepublishOnly": "npm run build" - }, - "engineStrict": true, - "author": "Hyperledger", - "license": "Apache-2.0", - "dependencies": { - "fabric-ca-client": "^2.2.19", - "fabric-network": "^2.2.19" - }, - "devDependencies": { - "@types/node": "^12.20.55", - "tslint": "^5.11.0", - "typescript": "^3.1.6" - }, - "nyc": { - "extension": [ - ".ts", - ".tsx" - ], - "exclude": [ - "coverage/**", - "dist/**" - ], - "reporter": [ - "text-summary", - "html" - ], - "all": true, - "check-coverage": true, - "statements": 100, - "branches": 100, - "functions": 100, - "lines": 100 - } -} diff --git a/asset-transfer-basic/application-typescript-hsm/softhsm2.conf b/asset-transfer-basic/application-typescript-hsm/softhsm2.conf deleted file mode 100644 index 86687cf7d8..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/softhsm2.conf +++ /dev/null @@ -1,5 +0,0 @@ -directories.tokendir = /tmp/ -objectstore.backend = file - -# ERROR, WARNING, INFO, DEBUG -log.level = INFO \ No newline at end of file diff --git a/asset-transfer-basic/application-typescript-hsm/src/app.ts b/asset-transfer-basic/application-typescript-hsm/src/app.ts deleted file mode 100644 index 0f3baa3ab8..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/src/app.ts +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ -import * as FabricCAServices from 'fabric-ca-client'; -import { Contract, Gateway, GatewayOptions, HsmOptions, HsmX509Provider, Wallets } from 'fabric-network'; -import * as fs from 'fs'; -import * as path from 'path'; -import { buildCCPOrg1, prettyJSONString } from './utils//AppUtil'; -import { enrollUserToWallet, registerUser, UserToEnroll, UserToRegister } from './utils/CAUtil'; - -const walletPath = path.join(__dirname, 'wallet'); - -// define information about the channel and chaincode that will be driven by this application -const channelName = envOrDefault('CHANNEL_NAME', 'mychannel'); -const chaincodeName = envOrDefault('CHAINCODE_NAME', 'default-basic'); - - -// define the CA Registrar -const mspOrg1 = 'Org1MSP'; -const org1CAAdminUserId = 'admin'; -const org1CAAdminSecret = 'adminpw'; - -// define the user in org1 to use -const org1UserId = 'appUser'; -const org1Secret = 'appUserSecret'; -const org1Affiliation = 'org1.department1'; - -type Request = 'submit' | 'evaluate'; - -interface TransactionToSendFormat { - request: Request; - txName: string; - txArgs: string[]; - description?: string; -} - -// The set of transactions to be invoked -const transactionsToSend: TransactionToSendFormat[] = [ - { - request: 'submit', - txName: 'InitLedger', - txArgs: [], - description: '\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger', - }, - { - request: 'evaluate', - txName: 'GetAllAssets', - txArgs: [], - description: '\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger', - }, - { - request: 'submit', - txName: 'CreateAsset', - txArgs: ['asset513', 'yellow', '5', 'Tom', '1300'], - description: '\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments', - }, - { - request: 'evaluate', - txName: 'ReadAsset', - txArgs: ['asset513'], - description: '\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID', - }, - { - request: 'evaluate', - txName: 'AssetExists', - txArgs: ['asset1'], - description: '\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with given assetID exist', - }, - { - request: 'submit', - txName: 'UpdateAsset', - txArgs: ['asset1', 'blue', '5', 'Tomoko', '350'], - description: '\n--> Submit Transaction: UpdateAsset asset1, change the appraisedValue to 350', - }, - { - request: 'evaluate', - txName: 'ReadAsset', - txArgs: ['asset1'], - description: '\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes', - }, - { - request: 'submit', - txName: 'UpdateAsset', - txArgs: ['asset70', 'blue', '5', 'Tomoko', '300'], - description: '\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error', - }, - { - request: 'submit', - txName: 'TransferAsset', - txArgs: ['asset1', 'Tom'], - description: '\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom', - }, - { - request: 'evaluate', - txName: 'ReadAsset', - txArgs: ['asset1'], - description: '\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes', - }, -]; - -/** - * A test application to show basic queries operations with any of the asset-transfer-basic chaincodes - * -- How to submit a transaction - * -- How to query and check the results - * - * To see the SDK workings, try setting the logging to show on the console before running - * export HFC_LOGGING='{"debug":"console"}' - */ -async function main() { - try { - // build an in memory object with the network configuration (also known as a connection profile) - const ccp = buildCCPOrg1(); - - // softhsm pkcs11 options to use - const softHSMOptions: HsmOptions = { - lib: await findSoftHSMPKCS11Lib(), - pin: process.env.PKCS11_PIN || '98765432', - label: process.env.PKCS11_LABEL || 'ForFabric', - }; - - // setup the wallet to hold the credentials used by this application - // Here we add the HSM provider to the provider registry of the wallet - const wallet = await Wallets.newFileSystemWallet(walletPath); - const hsmProvider = new HsmX509Provider(softHSMOptions); - wallet.getProviderRegistry().addProvider(hsmProvider); - - // build an instance of the fabric ca services client based on - // the information in the network configuration - const caInfo = ccp.certificateAuthorities['ca.org1.example.com']; // lookup CA details from config - const caTLSCACerts = caInfo.tlsCACerts.pem; - - // Here we make sure we pass in an HSM enabled cryptosuite to this CA instance - const hsmCAClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName, hsmProvider.getCryptoSuite()); - console.log(`Built a CA Client named ${caInfo.caName}`); - - // enroll the CA admin into the wallet if it doesn't already exist in the wallet - if (!await wallet.get(org1CAAdminUserId)) { - await enrollUserToWallet({ - caClient: hsmCAClient, - wallet, - orgMspId: mspOrg1, - userId: org1CAAdminUserId, - userIdSecret: org1CAAdminSecret, - } as UserToEnroll); - } - - // if we don't have the required user in the wallet then - // register this user to the CA (if it's not already registered) - // then enroll that user into the wallet - if (!await wallet.get(org1UserId)) { - await registerUser({ - caClient: hsmCAClient, - adminId: org1CAAdminUserId, - wallet, - orgMspId: mspOrg1, - userId: org1UserId, - userIdSecret: org1Secret, - affiliation: org1Affiliation, - } as UserToRegister); - - // By default you can only enroll a user once, after that you would have to re-enroll using the current - // certificate rather than using the secret. - await enrollUserToWallet({ - caClient: hsmCAClient, - wallet, - orgMspId: mspOrg1, - userId: org1UserId, - userIdSecret: org1Secret, - } as UserToEnroll); - } - - // Create a new gateway instance for interacting with the fabric network bound to our user - // This sample expects a locally deployed fabric network (ie running on the same machine in docker containers) - // therefore we set asLocalhost to true - const gateway = new Gateway(); - - const gatewayOpts: GatewayOptions = { - wallet, - identity: org1UserId, - discovery: { enabled: true, asLocalhost: true }, // using asLocalhost as this gateway is using a fabric network deployed locally - }; - - try { - // setup the gateway instance - // The user will now be able to create connections to the fabric network and be able to - // submit transactions and query. All transactions submitted by this gateway will be - // signed by this user using the credentials stored in the wallet. - await gateway.connect(ccp, gatewayOpts); - - // Build a network instance based on the channel where the smart contract is deployed - const network = await gateway.getNetwork(channelName); - - // Get the contract from the network. - const contract = network.getContract(chaincodeName); - - // loop around all transactions to send, each one will be sent sequentially - // through the same gateway/network/contract as subsequent transations expect the - // previous submits to have been committed - // Note however that a gateway/network/contract can support concurrent submitted - // transactions. - for (const transactionToSend of transactionsToSend) { - await interactWithFabric(contract, transactionToSend); - } - - console.log('*** The sample ran successfully ***'); - } finally { - - // Disconnect from the gateway when the application is closing - // This will close all connections to the network - gateway.disconnect(); - } - } catch (error) { - console.error(`******** FAILED to run the application: ${error}`); - } -} - -/** - * Determine the location of the SoftHSM PKCS11 Library - * @returns the location of the SoftHSM PKCS11 Library or 'NOT FOUND' if not found - */ -async function findSoftHSMPKCS11Lib() { - const commonSoftHSMPathNames = [ - '/usr/lib/softhsm/libsofthsm2.so', - '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so', - '/usr/local/lib/softhsm/libsofthsm2.so', - '/usr/lib/libacsp-pkcs11.so', - ]; - - let pkcsLibPath = 'NOT FOUND'; - if (typeof process.env.PKCS11_LIB === 'string' && process.env.PKCS11_LIB !== '') { - pkcsLibPath = process.env.PKCS11_LIB; - } else { - - // Check common locations for PKCS library - - for (const pathnameToTry of commonSoftHSMPathNames) { - if (fs.existsSync(pathnameToTry)) { - pkcsLibPath = pathnameToTry; - break; - } - } - } - - return pkcsLibPath; -} - -/** - * Interact with the Fabric Network via the Contact with the required transaction to perform. - * @param contract The contract to send the transaction to - * @param transactionToPerform the transaction to perform - */ -async function interactWithFabric(contract: Contract, transactionToPerform: TransactionToSendFormat): Promise { - console.log(transactionToPerform.description); - try { - switch (transactionToPerform.request) { - case 'submit': { - await contract.submitTransaction(transactionToPerform.txName, ...transactionToPerform.txArgs); - console.log('*** Result: committed'); - break; - } - - case 'evaluate': { - const result = await contract.evaluateTransaction(transactionToPerform.txName, ...transactionToPerform.txArgs); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - } - } - } catch (error) { - console.log(`*** Successfully caught the error: \n ${error}`); - // In reality applications should check the returned error to decide whether the transaction needs to be retried (ie go through - // endorsement again) for example an MVCC_READ_CONFLICT error, resubmitted to the orderer for example a timeout because it was - // never committed (for example due to networking issues the transaction never gets included in a block), or whether it should - // be reported back, for example they tried to perform an invalid application action. - } -} - -/** - * envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined. - */ -function envOrDefault(key: string, defaultValue: string): string { - return process.env[key] || defaultValue; -} - -// execute the main function -main(); diff --git a/asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts b/asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts deleted file mode 100644 index 685acce56d..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as fs from 'fs'; -import * as path from 'path'; - -const buildCCPOrg1 = (): Record => { - // load the common connection configuration file - const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network', - 'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json'); - const fileExists = fs.existsSync(ccpPath); - if (!fileExists) { - throw new Error(`no such file or directory: ${ccpPath}`); - } - const contents = fs.readFileSync(ccpPath, 'utf8'); - - // build a JSON object from the file contents - const ccp = JSON.parse(contents); - - console.log(`Loaded the network configuration located at ${ccpPath}`); - return ccp; -}; - -const buildCCPOrg2 = (): Record => { - // load the common connection configuration file - const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network', - 'organizations', 'peerOrganizations', 'org2.example.com', 'connection-org2.json'); - const fileExists = fs.existsSync(ccpPath); - if (!fileExists) { - throw new Error(`no such file or directory: ${ccpPath}`); - } - const contents = fs.readFileSync(ccpPath, 'utf8'); - - // build a JSON object from the file contents - const ccp = JSON.parse(contents); - - console.log(`Loaded the network configuration located at ${ccpPath}`); - return ccp; -}; - -const prettyJSONString = (inputString: string): string => { - if (inputString) { - return JSON.stringify(JSON.parse(inputString), null, 2); - } else { - return inputString; - } -}; - -export { - buildCCPOrg1, - buildCCPOrg2, - prettyJSONString, -}; diff --git a/asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts b/asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts deleted file mode 100644 index 00944d2260..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as FabricCAServices from 'fabric-ca-client'; -import { Wallet } from 'fabric-network'; - -export interface UserToEnroll { - caClient: FabricCAServices; - wallet: Wallet; - orgMspId: string; - userId: string; - userIdSecret: string; -} - -/** - * enroll a registered CA user and store the credentials in the wallet - * @param userToEnroll details about the user and the wallet to use - */ -export const enrollUserToWallet = async (userToEnroll: UserToEnroll): Promise => { - try { - - // check that the identity isn't already in the wallet - const identity = await userToEnroll.wallet.get(userToEnroll.userId); - if (identity) { - console.log(`Identity ${userToEnroll.userId} already exists in the wallet`); - return; - } - - // Enroll the user - const enrollment = await userToEnroll.caClient.enroll({ enrollmentID: userToEnroll.userId, enrollmentSecret: userToEnroll.userIdSecret }); - - // store the user - const hsmIdentity = { - credentials: { - certificate: enrollment.certificate, - }, - mspId: userToEnroll.orgMspId, - type: 'HSM-X.509', - }; - await userToEnroll.wallet.put(userToEnroll.userId, hsmIdentity); - console.log(`Successfully enrolled user ${userToEnroll.userId} and imported it into the wallet`); - } catch (error) { - console.error(`Failed to enroll user ${userToEnroll.userId}: ${error}`); - } -}; - -export interface UserToRegister { - caClient: FabricCAServices; - wallet: Wallet; - orgMspId: string; - adminId: string; - userId: string; - userIdSecret: string; - affiliation: string; -} - -export const registerUser = async (userToRegister: UserToRegister): Promise => { - try { - - // Must use a CA admin (registrar) to register a new user - const adminIdentity = await userToRegister.wallet.get(userToRegister.adminId); - if (!adminIdentity) { - console.log('An identity for the admin user does not exist in the wallet'); - console.log('Enroll the admin user before retrying'); - return; - } - - // build a user object for authenticating with the CA - const provider = userToRegister.wallet.getProviderRegistry().getProvider(adminIdentity.type); - const adminUser = await provider.getUserContext(adminIdentity, userToRegister.adminId); - - // Register the user - // if affiliation is specified by client, the affiliation value must be configured in CA - await userToRegister.caClient.register({ - affiliation: userToRegister.affiliation, - enrollmentID: userToRegister.userId, - enrollmentSecret: userToRegister.userIdSecret, - role: 'client', - }, adminUser); - console.log(`Successfully registered ${userToRegister.userId}.`); - return; - - } catch (error) { - // check to see if it's an already registered error, if it is, then we can ignore it - // otherwise we rethrow the error - if (error.errors[0].code !== 74) { - console.error(`Failed to register user : ${error}`); - throw error; - } - } -}; diff --git a/asset-transfer-basic/application-typescript-hsm/tsconfig.json b/asset-transfer-basic/application-typescript-hsm/tsconfig.json deleted file mode 100644 index 590edcb3c7..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "target": "es2017", - "moduleResolution": "node", - "module": "commonjs", - "declaration": true, - "sourceMap": true, - "noImplicitAny": true, - "strict": true - }, - "include": [ - "./src/**/*" - ], - "exclude": [ - "./src/**/*.spec.ts" - ] -} diff --git a/asset-transfer-basic/application-typescript-hsm/tslint.json b/asset-transfer-basic/application-typescript-hsm/tslint.json deleted file mode 100644 index 3612c23a09..0000000000 --- a/asset-transfer-basic/application-typescript-hsm/tslint.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended" - ], - "jsRules": {}, - "rules": { - "indent": [true, "spaces", 4], - "linebreak-style": [true, "LF"], - "quotemark": [true, "single"], - "semicolon": [true, "always"], - "no-console": false, - "curly": true, - "triple-equals": true, - "no-string-throw": true, - "no-var-keyword": true, - "no-trailing-whitespace": true, - "object-literal-key-quotes": [true, "as-needed"], - "object-literal-sort-keys": false, - "max-line-length": false, - "interface-name":false - }, - "rulesDirectory": [] -} diff --git a/asset-transfer-basic/application-typescript/.gitignore b/asset-transfer-basic/application-typescript/.gitignore deleted file mode 100644 index 48285d12a9..0000000000 --- a/asset-transfer-basic/application-typescript/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - - -# Coverage directory used by tools like istanbul -coverage - -# Dependency directories -node_modules/ -jspm_packages/ - -# Compiled TypeScript files -dist - diff --git a/asset-transfer-basic/application-typescript/package.json b/asset-transfer-basic/application-typescript/package.json deleted file mode 100644 index ca8f7b38a3..0000000000 --- a/asset-transfer-basic/application-typescript/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "asset-transfer-basic", - "version": "1.0.0", - "description": "Asset Transfer Basic contract implemented in TypeScript", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "engines": { - "node": ">=14.14" - }, - "scripts": { - "lint": "tslint -c tslint.json 'src/**/*.ts'", - "pretest": "npm run lint", - "start": "npm run build && node dist/app.js", - "build": "tsc", - "build:watch": "tsc -w", - "prepublishOnly": "npm run build" - }, - "engineStrict": true, - "author": "Hyperledger", - "license": "Apache-2.0", - "dependencies": { - "fabric-ca-client": "^2.2.19", - "fabric-network": "^2.2.19" - }, - "devDependencies": { - "@tsconfig/node14": "^14.1.0", - "@types/node": "^14.17.32", - "tslint": "^5.11.0", - "typescript": "~4.9.4" - }, - "nyc": { - "extension": [ - ".ts", - ".tsx" - ], - "exclude": [ - "coverage/**", - "dist/**" - ], - "reporter": [ - "text-summary", - "html" - ], - "all": true, - "check-coverage": true, - "statements": 100, - "branches": 100, - "functions": 100, - "lines": 100 - } -} diff --git a/asset-transfer-basic/application-typescript/src/app.ts b/asset-transfer-basic/application-typescript/src/app.ts deleted file mode 100644 index 08758e06f3..0000000000 --- a/asset-transfer-basic/application-typescript/src/app.ts +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ -import { Gateway, GatewayOptions } from 'fabric-network'; -import * as path from 'path'; -import { buildCCPOrg1, buildWallet, prettyJSONString } from './utils//AppUtil'; -import { buildCAClient, enrollAdmin, registerAndEnrollUser } from './utils/CAUtil'; - -const channelName = process.env.CHANNEL_NAME || 'mychannel'; -const chaincodeName = process.env.CHAINCODE_NAME || 'basic'; - -const mspOrg1 = 'Org1MSP'; -const walletPath = path.join(__dirname, 'wallet'); -const org1UserId = 'typescriptAppUser'; - -// pre-requisites: -// - fabric-sample two organization test-network setup with two peers, ordering service, -// and 2 certificate authorities -// ===> from directory /fabric-samples/test-network -// ./network.sh up createChannel -ca -// - Use any of the asset-transfer-basic chaincodes deployed on the channel "mychannel" -// with the chaincode name of "basic". The following deploy command will package, -// install, approve, and commit the javascript chaincode, all the actions it takes -// to deploy a chaincode to a channel. -// ===> from directory /fabric-samples/test-network -// ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl javascript -// - Be sure that node.js is installed -// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript -// node -v -// - npm installed code dependencies -// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript -// npm install -// - to run this test application -// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript -// npm start - -// NOTE: If you see kind an error like these: -/* - 2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied - ******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied - - OR - - Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] - ******** FAILED to run the application: Error: Identity not found in wallet: appUser -*/ -// Delete the /fabric-samples/asset-transfer-basic/application-typescript/wallet directory -// and retry this application. - -// The certificate authority must have been restarted and the saved certificates for the -// admin and application user are not valid. Deleting the wallet store will force these to be reset -// with the new certificate authority. - -/** - * A test application to show basic queries operations with any of the asset-transfer-basic chaincodes - * -- How to submit a transaction - * -- How to query and check the results - * - * To see the SDK workings, try setting the logging to show on the console before running - * export HFC_LOGGING='{"debug":"console"}' - */ -async function main() { - try { - // build an in memory object with the network configuration (also known as a connection profile) - const ccp = buildCCPOrg1(); - - // build an instance of the fabric ca services client based on - // the information in the network configuration - const caClient = buildCAClient(ccp, 'ca.org1.example.com'); - - // setup the wallet to hold the credentials of the application user - const wallet = await buildWallet(walletPath); - - // in a real application this would be done on an administrative flow, and only once - await enrollAdmin(caClient, wallet, mspOrg1); - - // in a real application this would be done only when a new user was required to be added - // and would be part of an administrative flow - await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1'); - - // Create a new gateway instance for interacting with the fabric network. - // In a real application this would be done as the backend server session is setup for - // a user that has been verified. - const gateway = new Gateway(); - - const gatewayOpts: GatewayOptions = { - wallet, - identity: org1UserId, - discovery: { enabled: true, asLocalhost: true }, // using asLocalhost as this gateway is using a fabric network deployed locally - }; - - try { - // setup the gateway instance - // The user will now be able to create connections to the fabric network and be able to - // submit transactions and query. All transactions submitted by this gateway will be - // signed by this user using the credentials stored in the wallet. - await gateway.connect(ccp, gatewayOpts); - - // Build a network instance based on the channel where the smart contract is deployed - const network = await gateway.getNetwork(channelName); - - // Get the contract from the network. - const contract = network.getContract(chaincodeName); - - // Initialize a set of asset data on the channel using the chaincode 'InitLedger' function. - // This type of transaction would only be run once by an application the first time it was started after it - // deployed the first time. Any updates to the chaincode deployed later would likely not need to run - // an "init" type function. - console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger'); - await contract.submitTransaction('InitLedger'); - console.log('*** Result: committed'); - - // Let's try a query type operation (function). - // This will be sent to just one peer and the results will be shown. - console.log('\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger'); - let result = await contract.evaluateTransaction('GetAllAssets'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - // Now let's try to submit a transaction. - // This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent - // to the orderer to be committed by each of the peer's to the channel ledger. - console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments'); - await contract.submitTransaction('CreateAsset', 'asset413', 'yellow', '5', 'Tom', '1300'); - console.log('*** Result: committed'); - - console.log('\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID'); - result = await contract.evaluateTransaction('ReadAsset', 'asset413'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - console.log('\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with given assetID exist'); - result = await contract.evaluateTransaction('AssetExists', 'asset1'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - console.log('\n--> Submit Transaction: UpdateAsset asset1, change the appraisedValue to 350'); - await contract.submitTransaction('UpdateAsset', 'asset1', 'blue', '5', 'Tomoko', '350'); - console.log('*** Result: committed'); - - console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes'); - result = await contract.evaluateTransaction('ReadAsset', 'asset1'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - - try { - // How about we try a transactions where the executing chaincode throws an error - // Notice how the submitTransaction will throw an error containing the error thrown by the chaincode - console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error'); - await contract.submitTransaction('UpdateAsset', 'asset70', 'blue', '5', 'Tomoko', '300'); - console.log('******** FAILED to return an error'); - } catch (error) { - console.log(`*** Successfully caught the error: \n ${error}`); - } - - console.log('\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom'); - await contract.submitTransaction('TransferAsset', 'asset1', 'Tom'); - console.log('*** Result: committed'); - - console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes'); - result = await contract.evaluateTransaction('ReadAsset', 'asset1'); - console.log(`*** Result: ${prettyJSONString(result.toString())}`); - } finally { - // Disconnect from the gateway when the application is closing - // This will close all connections to the network - gateway.disconnect(); - } - } catch (error) { - console.error(`******** FAILED to run the application: ${error}`); - process.exit(1); - } -} - -main(); diff --git a/asset-transfer-basic/application-typescript/src/utils/AppUtil.ts b/asset-transfer-basic/application-typescript/src/utils/AppUtil.ts deleted file mode 100644 index d72ec0f9b5..0000000000 --- a/asset-transfer-basic/application-typescript/src/utils/AppUtil.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Wallet, Wallets } from 'fabric-network'; -import * as fs from 'fs'; -import * as path from 'path'; - -const buildCCPOrg1 = (): Record => { - // load the common connection configuration file - const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network', - 'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json'); - const fileExists = fs.existsSync(ccpPath); - if (!fileExists) { - throw new Error(`no such file or directory: ${ccpPath}`); - } - const contents = fs.readFileSync(ccpPath, 'utf8'); - - // build a JSON object from the file contents - const ccp = JSON.parse(contents); - - console.log(`Loaded the network configuration located at ${ccpPath}`); - return ccp; -}; - -const buildCCPOrg2 = (): Record => { - // load the common connection configuration file - const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network', - 'organizations', 'peerOrganizations', 'org2.example.com', 'connection-org2.json'); - const fileExists = fs.existsSync(ccpPath); - if (!fileExists) { - throw new Error(`no such file or directory: ${ccpPath}`); - } - const contents = fs.readFileSync(ccpPath, 'utf8'); - - // build a JSON object from the file contents - const ccp = JSON.parse(contents); - - console.log(`Loaded the network configuration located at ${ccpPath}`); - return ccp; -}; - -const buildWallet = async (walletPath: string): Promise => { - // Create a new wallet : Note that wallet is for managing identities. - let wallet: Wallet; - if (walletPath) { - - // remove any pre-existing wallet from prior runs - fs.rmSync(walletPath, { recursive: true, force: true }); - - wallet = await Wallets.newFileSystemWallet(walletPath); - console.log(`Built a file system wallet at ${walletPath}`); - } else { - wallet = await Wallets.newInMemoryWallet(); - console.log('Built an in memory wallet'); - } - - return wallet; -}; - -const prettyJSONString = (inputString: string): string => { - if (inputString) { - return JSON.stringify(JSON.parse(inputString), null, 2); - } else { - return inputString; - } -}; - -export { - buildCCPOrg1, - buildCCPOrg2, - buildWallet, - prettyJSONString, -}; diff --git a/asset-transfer-basic/application-typescript/src/utils/CAUtil.ts b/asset-transfer-basic/application-typescript/src/utils/CAUtil.ts deleted file mode 100644 index 7e792119eb..0000000000 --- a/asset-transfer-basic/application-typescript/src/utils/CAUtil.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import FabricCAServices from 'fabric-ca-client'; -import { Wallet } from 'fabric-network'; - -const adminUserId = 'admin'; -const adminUserPasswd = 'adminpw'; - -/** - * - * @param {*} ccp - */ -const buildCAClient = (ccp: Record, caHostName: string): FabricCAServices => { - // Create a new CA client for interacting with the CA. - const caInfo = ccp.certificateAuthorities[caHostName]; // lookup CA details from config - const caTLSCACerts = caInfo.tlsCACerts.pem; - const caClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName); - - console.log(`Built a CA Client named ${caInfo.caName}`); - return caClient; -}; - -const enrollAdmin = async (caClient: FabricCAServices, wallet: Wallet, orgMspId: string): Promise => { - try { - // Check to see if we've already enrolled the admin user. - const identity = await wallet.get(adminUserId); - if (identity) { - console.log('An identity for the admin user already exists in the wallet'); - return; - } - - // Enroll the admin user, and import the new identity into the wallet. - const enrollment = await caClient.enroll({ enrollmentID: adminUserId, enrollmentSecret: adminUserPasswd }); - const x509Identity = { - credentials: { - certificate: enrollment.certificate, - privateKey: enrollment.key.toBytes(), - }, - mspId: orgMspId, - type: 'X.509', - }; - await wallet.put(adminUserId, x509Identity); - console.log('Successfully enrolled admin user and imported it into the wallet'); - } catch (error) { - console.error(`Failed to enroll admin user : ${error}`); - } -}; - -const registerAndEnrollUser = async (caClient: FabricCAServices, wallet: Wallet, orgMspId: string, userId: string, affiliation: string): Promise => { - try { - // Check to see if we've already enrolled the user - const userIdentity = await wallet.get(userId); - if (userIdentity) { - console.log(`An identity for the user ${userId} already exists in the wallet`); - return; - } - - // Must use an admin to register a new user - const adminIdentity = await wallet.get(adminUserId); - if (!adminIdentity) { - console.log('An identity for the admin user does not exist in the wallet'); - console.log('Enroll the admin user before retrying'); - return; - } - - // build a user object for authenticating with the CA - const provider = wallet.getProviderRegistry().getProvider(adminIdentity.type); - const adminUser = await provider.getUserContext(adminIdentity, adminUserId); - - // Register the user, enroll the user, and import the new identity into the wallet. - // if affiliation is specified by client, the affiliation value must be configured in CA - const secret = await caClient.register({ - affiliation, - enrollmentID: userId, - role: 'client', - }, adminUser); - const enrollment = await caClient.enroll({ - enrollmentID: userId, - enrollmentSecret: secret, - }); - const x509Identity = { - credentials: { - certificate: enrollment.certificate, - privateKey: enrollment.key.toBytes(), - }, - mspId: orgMspId, - type: 'X.509', - }; - await wallet.put(userId, x509Identity); - console.log(`Successfully registered and enrolled user ${userId} and imported it into the wallet`); - } catch (error) { - console.error(`Failed to register user : ${error}`); - } -}; - -export { - buildCAClient, - enrollAdmin, - registerAndEnrollUser, -}; diff --git a/asset-transfer-basic/application-typescript/tsconfig.json b/asset-transfer-basic/application-typescript/tsconfig.json deleted file mode 100644 index ba53c81981..0000000000 --- a/asset-transfer-basic/application-typescript/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@tsconfig/node14/tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "declaration": true, - "sourceMap": true, - }, - "include": [ - "./src/**/*" - ], - "exclude": [ - "./src/**/*.spec.ts" - ] -} diff --git a/asset-transfer-basic/application-typescript/tslint.json b/asset-transfer-basic/application-typescript/tslint.json deleted file mode 100644 index a52c3ee2ed..0000000000 --- a/asset-transfer-basic/application-typescript/tslint.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended" - ], - "jsRules": {}, - "rules": { - "indent": [true, "spaces", 4], - "linebreak-style": [true, "LF"], - "quotemark": [true, "single"], - "semicolon": [true, "always"], - "no-console": false, - "curly": true, - "triple-equals": true, - "no-string-throw": true, - "no-var-keyword": true, - "no-trailing-whitespace": true, - "object-literal-key-quotes": [true, "as-needed"], - "object-literal-sort-keys": false, - "max-line-length": false - }, - "rulesDirectory": [] -} diff --git a/asset-transfer-events/application-javascript/.eslintignore b/asset-transfer-events/application-javascript/.eslintignore deleted file mode 100644 index 1595847010..0000000000 --- a/asset-transfer-events/application-javascript/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -coverage diff --git a/asset-transfer-events/application-javascript/.eslintrc.js b/asset-transfer-events/application-javascript/.eslintrc.js deleted file mode 100644 index ae0bf212d4..0000000000 --- a/asset-transfer-events/application-javascript/.eslintrc.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - */ -'use strict'; - -module.exports = { - env: { - node: true, - mocha: true, - es6: true - }, - parserOptions: { - ecmaVersion: 8, - sourceType: 'script' - }, - extends: 'eslint:recommended', - rules: { - indent: ['error', 'tab'], - 'linebreak-style': ['error', 'unix'], - quotes: ['error', 'single'], - semi: ['error', 'always'], - 'no-unused-vars': ['error', { args: 'none' }], - 'no-console': 'off', - curly: 'error', - eqeqeq: 'error', - 'no-throw-literal': 'error', - 'no-var': 'error', - 'dot-notation': 'error', - 'no-trailing-spaces': 'error', - 'no-use-before-define': 'error', - 'no-useless-call': 'error', - 'no-with': 'error', - 'operator-linebreak': 'error', - yoda: 'error', - 'quote-props': ['error', 'as-needed'] - } -}; diff --git a/asset-transfer-events/application-javascript/.gitignore b/asset-transfer-events/application-javascript/.gitignore deleted file mode 100644 index 21b287f72d..0000000000 --- a/asset-transfer-events/application-javascript/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -# Coverage directory used by tools like istanbul -coverage - -# Dependency directories -node_modules/ -jspm_packages/ -package-lock.json - -wallet -!wallet/.gitkeep diff --git a/asset-transfer-events/application-javascript/app.js b/asset-transfer-events/application-javascript/app.js deleted file mode 100644 index 10e4f36796..0000000000 --- a/asset-transfer-events/application-javascript/app.js +++ /dev/null @@ -1,545 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; - -/** - * Application that shows events when creating and updating an asset - * -- How to register a contract listener for chaincode events - * -- How to get the chaincode event name and value from the chaincode event - * -- How to retrieve the transaction and block information from the chaincode event - * -- How to register a block listener for full block events - * -- How to retrieve the transaction and block information from the full block event - * -- How to register to recieve private data associated with transactions when - * registering a block listener - * -- How to retreive the private data from the full block event - * -- The listener will be notified of an event at anytime. Notice that events will - * be posted by the listener after the application activity causing the ledger change - * and during other application activity unrelated to the event - * -- How to connect to a Gateway that will not use events when submitting transactions. - * This may be useful when the application does not want to wait for the peer to commit - * blocks and notify the application. - * - * To see the SDK workings, try setting the logging to be displayed on the console - * before executing this application. - * export HFC_LOGGING='{"debug":"console"}' - * See the following on how the SDK is working with the Peer's Event Services - * https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html - * - * See the following for more details on using the Node SDK - * https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.html - */ - -// pre-requisites: -// - fabric-sample two organization test-network setup with two peers, ordering service, -// and 2 certificate authorities -// ===> from directory test-network -// ./network.sh up createChannel -ca -// -// - Use the asset-transfer-events/chaincode-javascript chaincode deployed on -// the channel "mychannel". The following deploy command will package, install, -// approve, and commit the javascript chaincode, all the actions it takes -// to deploy a chaincode to a channel. -// ===> from directory test-network -// ./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-javascript/ -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -// -// - Be sure that node.js is installed -// ===> from directory asset-transfer-events/application-javascript -// node -v -// - npm installed code dependencies -// ===> from directory asset-transfer-events/application-javascript -// npm install -// - to run this test application -// ===> from directory asset-transfer-events/application-javascript -// node app.js - -// NOTE: If you see an error like these: -/* - - Error in setup: Error: DiscoveryService: mychannel error: access denied - - OR - - Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] - - */ -// Delete the /fabric-samples/asset-transfer-sbe/application-javascript/wallet directory -// and retry this application. -// -// The certificate authority must have been restarted and the saved certificates for the -// admin and application user are not valid. Deleting the wallet store will force these to be reset -// with the new certificate authority. -// - -// use this to set logging, must be set before the require('fabric-network'); -process.env.HFC_LOGGING = '{"debug": "./debug.log"}'; - -const { Gateway, Wallets } = require('fabric-network'); -const EventStrategies = require('fabric-network/lib/impl/event/defaulteventhandlerstrategies'); -const FabricCAServices = require('fabric-ca-client'); -const path = require('path'); -const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js'); -const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js'); - -const channelName = 'mychannel'; -const chaincodeName = 'events'; - -const org1 = 'Org1MSP'; -const Org1UserId = 'appUser1'; - -const RED = '\x1b[31m\n'; -const GREEN = '\x1b[32m\n'; -const BLUE = '\x1b[34m'; -const RESET = '\x1b[0m'; - -/** - * Perform a sleep -- asynchronous wait - * @param ms the time in milliseconds to sleep for - */ -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function initGatewayForOrg1(useCommitEvents) { - console.log(`${GREEN}--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer${RESET}`); - // build an in memory object with the network configuration (also known as a connection profile) - const ccpOrg1 = buildCCPOrg1(); - - // build an instance of the fabric ca services client based on - // the information in the network configuration - const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com'); - - // setup the wallet to cache the credentials of the application user, on the app server locally - const walletPathOrg1 = path.join(__dirname, 'wallet', 'org1'); - const walletOrg1 = await buildWallet(Wallets, walletPathOrg1); - - // in a real application this would be done on an administrative flow, and only once - // stores admin identity in local wallet, if needed - await enrollAdmin(caOrg1Client, walletOrg1, org1); - // register & enroll application user with CA, which is used as client identify to make chaincode calls - // and stores app user identity in local wallet - // In a real application this would be done only when a new user was required to be added - // and would be part of an administrative flow - await registerAndEnrollUser(caOrg1Client, walletOrg1, org1, Org1UserId, 'org1.department1'); - - try { - // Create a new gateway for connecting to Org's peer node. - const gatewayOrg1 = new Gateway(); - - if (useCommitEvents) { - await gatewayOrg1.connect(ccpOrg1, { - wallet: walletOrg1, - identity: Org1UserId, - discovery: { enabled: true, asLocalhost: true } - }); - } else { - await gatewayOrg1.connect(ccpOrg1, { - wallet: walletOrg1, - identity: Org1UserId, - discovery: { enabled: true, asLocalhost: true }, - eventHandlerOptions: EventStrategies.NONE - }); - } - - - return gatewayOrg1; - } catch (error) { - console.error(`Error in connecting to gateway for Org1: ${error}`); - process.exit(1); - } -} - -function checkAsset(org, resultBuffer, color, size, owner, appraisedValue, price) { - console.log(`${GREEN}<-- Query results from ${org}${RESET}`); - - let asset; - if (resultBuffer) { - asset = JSON.parse(resultBuffer.toString('utf8')); - } else { - console.log(`${RED}*** Failed to read asset${RESET}`); - } - console.log(`*** verify asset ${asset.ID}`); - - if (asset) { - if (asset.Color === color) { - console.log(`*** asset ${asset.ID} has color ${asset.Color}`); - } else { - console.log(`${RED}*** asset ${asset.ID} has color of ${asset.Color}${RESET}`); - } - if (asset.Size === size) { - console.log(`*** asset ${asset.ID} has size ${asset.Size}`); - } else { - console.log(`${RED}*** Failed size check from ${org} - asset ${asset.ID} has size of ${asset.Size}${RESET}`); - } - if (asset.Owner === owner) { - console.log(`*** asset ${asset.ID} owned by ${asset.Owner}`); - } else { - console.log(`${RED}*** Failed owner check from ${org} - asset ${asset.ID} owned by ${asset.Owner}${RESET}`); - } - if (asset.AppraisedValue === appraisedValue) { - console.log(`*** asset ${asset.ID} has appraised value ${asset.AppraisedValue}`); - } else { - console.log(`${RED}*** Failed appraised value check from ${org} - asset ${asset.ID} has appraised value of ${asset.AppraisedValue}${RESET}`); - } - if (price) { - if (asset.asset_properties && asset.asset_properties.Price === price) { - console.log(`*** asset ${asset.ID} has price ${asset.asset_properties.Price}`); - } else { - console.log(`${RED}*** Failed price check from ${org} - asset ${asset.ID} has price of ${asset.asset_properties.Price}${RESET}`); - } - } - } -} - -function showTransactionData(transactionData) { - const creator = transactionData.actions[0].header.creator; - console.log(` - submitted by: ${creator.mspid}-${creator.id_bytes.toString('hex')}`); - for (const endorsement of transactionData.actions[0].payload.action.endorsements) { - console.log(` - endorsed by: ${endorsement.endorser.mspid}-${endorsement.endorser.id_bytes.toString('hex')}`); - } - const chaincode = transactionData.actions[0].payload.chaincode_proposal_payload.input.chaincode_spec; - console.log(` - chaincode:${chaincode.chaincode_id.name}`); - console.log(` - function:${chaincode.input.args[0].toString()}`); - for (let x = 1; x < chaincode.input.args.length; x++) { - console.log(` - arg:${chaincode.input.args[x].toString()}`); - } -} - -async function main() { - console.log(`${BLUE} **** START ****${RESET}`); - try { - let randomNumber = Math.floor(Math.random() * 1000) + 1; - // use a random key so that we can run multiple times - let assetKey = `item-${randomNumber}`; - - /** ******* Fabric client init: Using Org1 identity to Org1 Peer ******* */ - const gateway1Org1 = await initGatewayForOrg1(true); // transaction handling uses commit events - const gateway2Org1 = await initGatewayForOrg1(); - - try { - // - // - - - - - - C H A I N C O D E E V E N T S - // - console.log(`${BLUE} **** CHAINCODE EVENTS ****${RESET}`); - let transaction; - let listener; - const network1Org1 = await gateway1Org1.getNetwork(channelName); - const contract1Org1 = network1Org1.getContract(chaincodeName); - - try { - // first create a listener to be notified of chaincode code events - // coming from the chaincode ID "events" - listener = async (event) => { - // The payload of the chaincode event is the value place there by the - // chaincode. Notice it is a byte data and the application will have - // to know how to deserialize. - // In this case we know that the chaincode will always place the asset - // being worked with as the payload for all events produced. - const asset = JSON.parse(event.payload.toString()); - console.log(`${GREEN}<-- Contract Event Received: ${event.eventName} - ${JSON.stringify(asset)}${RESET}`); - // show the information available with the event - console.log(`*** Event: ${event.eventName}:${asset.ID}`); - // notice how we have access to the transaction information that produced this chaincode event - const eventTransaction = event.getTransactionEvent(); - console.log(`*** transaction: ${eventTransaction.transactionId} status:${eventTransaction.status}`); - showTransactionData(eventTransaction.transactionData); - // notice how we have access to the full block that contains this transaction - const eventBlock = eventTransaction.getBlockEvent(); - console.log(`*** block: ${eventBlock.blockNumber.toString()}`); - }; - // now start the client side event service and register the listener - console.log(`${GREEN}--> Start contract event stream to peer in Org1${RESET}`); - await contract1Org1.addContractListener(listener); - } catch (eventError) { - console.log(`${RED}<-- Failed: Setup contract events - ${eventError}${RESET}`); - } - - try { - // C R E A T E - console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${assetKey} owned by Sam${RESET}`); - transaction = contract1Org1.createTransaction('CreateAsset'); - await transaction.submit(assetKey, 'blue', '10', 'Sam', '100'); - console.log(`${GREEN}<-- Submit CreateAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (createError) { - console.log(`${RED}<-- Submit Failed: CreateAsset - ${createError}${RESET}`); - } - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should be owned by Sam${RESET}`); - const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '100'); - } catch (readError) { - console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`); - } - - try { - // U P D A T E - console.log(`${GREEN}--> Submit Transaction: UpdateAsset ${assetKey} update appraised value to 200`); - transaction = contract1Org1.createTransaction('UpdateAsset'); - await transaction.submit(assetKey, 'blue', '10', 'Sam', '200'); - console.log(`${GREEN}<-- Submit UpdateAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (updateError) { - console.log(`${RED}<-- Failed: UpdateAsset - ${updateError}${RESET}`); - } - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now have appraised value of 200${RESET}`); - const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '200'); - } catch (readError) { - console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`); - } - - try { - // T R A N S F E R - console.log(`${GREEN}--> Submit Transaction: TransferAsset ${assetKey} to Mary`); - transaction = contract1Org1.createTransaction('TransferAsset'); - await transaction.submit(assetKey, 'Mary'); - console.log(`${GREEN}<-- Submit TransferAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (transferError) { - console.log(`${RED}<-- Failed: TransferAsset - ${transferError}${RESET}`); - } - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be owned by Mary${RESET}`); - const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200'); - } catch (readError) { - console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`); - } - - try { - // D E L E T E - console.log(`${GREEN}--> Submit Transaction: DeleteAsset ${assetKey}`); - transaction = contract1Org1.createTransaction('DeleteAsset'); - await transaction.submit(assetKey); - console.log(`${GREEN}<-- Submit DeleteAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (deleteError) { - console.log(`${RED}<-- Failed: DeleteAsset - ${deleteError}${RESET}`); - if (deleteError.toString().includes('ENDORSEMENT_POLICY_FAILURE')) { - console.log(`${RED}Be sure that chaincode was deployed with the endorsement policy "OR('Org1MSP.peer','Org2MSP.peer')"${RESET}`); - } - } - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be deleted${RESET}`); - const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200'); - console.log(`${RED}<-- Failed: ReadAsset - should not have read this asset${RESET}`); - } catch (readError) { - console.log(`${GREEN}<-- Success: ReadAsset - ${readError}${RESET}`); - } - - // all done with this listener - contract1Org1.removeContractListener(listener); - - // - // - - - - - - B L O C K E V E N T S with P R I V A T E D A T A - // - console.log(`${BLUE} **** BLOCK EVENTS with PRIVATE DATA ****${RESET}`); - const network2Org1 = await gateway2Org1.getNetwork(channelName); - const contract2Org1 = network2Org1.getContract(chaincodeName); - - randomNumber = Math.floor(Math.random() * 1000) + 1; - assetKey = `item-${randomNumber}`; - - let firstBlock = true; // simple indicator to track blocks - - try { - let listener; - - // create a block listener - listener = async (event) => { - if (firstBlock) { - console.log(`${GREEN}<-- Block Event Received - block number: ${event.blockNumber.toString()}` + - '\n### Note:' + - '\n This block event represents the current top block of the ledger.' + - `\n All block events after this one are events that represent new blocks added to the ledger${RESET}`); - firstBlock = false; - } else { - console.log(`${GREEN}<-- Block Event Received - block number: ${event.blockNumber.toString()}${RESET}`); - } - const transEvents = event.getTransactionEvents(); - for (const transEvent of transEvents) { - console.log(`*** transaction event: ${transEvent.transactionId}`); - if (transEvent.privateData) { - for (const namespace of transEvent.privateData.ns_pvt_rwset) { - console.log(` - private data: ${namespace.namespace}`); - for (const collection of namespace.collection_pvt_rwset) { - console.log(` - collection: ${collection.collection_name}`); - if (collection.rwset.reads) { - for (const read of collection.rwset.reads) { - console.log(` - read set - ${BLUE}key:${RESET} ${read.key} ${BLUE}value:${read.value.toString()}`); - } - } - if (collection.rwset.writes) { - for (const write of collection.rwset.writes) { - console.log(` - write set - ${BLUE}key:${RESET}${write.key} ${BLUE}is_delete:${RESET}${write.is_delete} ${BLUE}value:${RESET}${write.value.toString()}`); - } - } - } - } - } - if (transEvent.transactionData) { - showTransactionData(transEvent.transactionData); - } - } - }; - // now start the client side event service and register the listener - console.log(`${GREEN}--> Start private data block event stream to peer in Org1${RESET}`); - await network2Org1.addBlockListener(listener, {type: 'private'}); - } catch (eventError) { - console.log(`${RED}<-- Failed: Setup block events - ${eventError}${RESET}`); - } - - try { - // C R E A T E - console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${assetKey} owned by Sam${RESET}`); - transaction = contract2Org1.createTransaction('CreateAsset'); - - // create the private data with salt and assign to the transaction - const randomNumber = Math.floor(Math.random() * 100) + 1; - const asset_properties = { - object_type: 'asset_properties', - asset_id: assetKey, - Price: '90', - salt: Buffer.from(randomNumber.toString()).toString('hex') - }; - const asset_properties_string = JSON.stringify(asset_properties); - transaction.setTransient({ - asset_properties: Buffer.from(asset_properties_string) - }); - // With the addition of private data to the transaction - // We must only send this to the organization that will be - // saving the private data or we will get an endorsement policy failure - transaction.setEndorsingOrganizations(org1); - // endorse and commit - private data (transient data) will be - // saved to the implicit collection on the peer - await transaction.submit(assetKey, 'blue', '10', 'Sam', '100'); - console.log(`${GREEN}<-- Submit CreateAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (createError) { - console.log(`${RED}<-- Failed: CreateAsset - ${createError}${RESET}`); - } - await sleep(5000); // need to wait for event to be committed - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should be owned by Sam${RESET}`); - const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '100', '90'); - } catch (readError) { - console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`); - } - - try { - // U P D A T E - console.log(`${GREEN}--> Submit Transaction: UpdateAsset ${assetKey} update appraised value to 200`); - transaction = contract2Org1.createTransaction('UpdateAsset'); - - // update the private data with new salt and assign to the transaction - const randomNumber = Math.floor(Math.random() * 100) + 1; - const asset_properties = { - object_type: 'asset_properties', - asset_id: assetKey, - Price: '90', - salt: Buffer.from(randomNumber.toString()).toString('hex') - }; - const asset_properties_string = JSON.stringify(asset_properties); - transaction.setTransient({ - asset_properties: Buffer.from(asset_properties_string) - }); - transaction.setEndorsingOrganizations(org1); - - await transaction.submit(assetKey, 'blue', '10', 'Sam', '200'); - console.log(`${GREEN}<-- Submit UpdateAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (updateError) { - console.log(`${RED}<-- Failed: UpdateAsset - ${updateError}${RESET}`); - } - await sleep(5000); // need to wait for event to be committed - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now have appraised value of 200${RESET}`); - const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '200', '90'); - } catch (readError) { - console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`); - } - - try { - // T R A N S F E R - console.log(`${GREEN}--> Submit Transaction: TransferAsset ${assetKey} to Mary`); - transaction = contract2Org1.createTransaction('TransferAsset'); - - // update the private data with new salt and assign to the transaction - const randomNumber = Math.floor(Math.random() * 100) + 1; - const asset_properties = { - object_type: 'asset_properties', - asset_id: assetKey, - Price: '180', - salt: Buffer.from(randomNumber.toString()).toString('hex') - }; - const asset_properties_string = JSON.stringify(asset_properties); - transaction.setTransient({ - asset_properties: Buffer.from(asset_properties_string) - }); - transaction.setEndorsingOrganizations(org1); - - await transaction.submit(assetKey, 'Mary'); - console.log(`${GREEN}<-- Submit TransferAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (transferError) { - console.log(`${RED}<-- Failed: TransferAsset - ${transferError}${RESET}`); - } - await sleep(5000); // need to wait for event to be committed - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be owned by Mary${RESET}`); - const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200', '180'); - } catch (readError) { - console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`); - } - - try { - // D E L E T E - console.log(`${GREEN}--> Submit Transaction: DeleteAsset ${assetKey}`); - transaction = contract2Org1.createTransaction('DeleteAsset'); - await transaction.submit(assetKey); - console.log(`${GREEN}<-- Submit DeleteAsset Result: committed, asset ${assetKey}${RESET}`); - } catch (deleteError) { - console.log(`${RED}<-- Failed: DeleteAsset - ${deleteError}${RESET}`); - } - await sleep(5000); // need to wait for event to be committed - try { - // R E A D - console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be deleted${RESET}`); - const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey); - checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200'); - console.log(`${RED}<-- Failed: ReadAsset - should not have read this asset${RESET}`); - } catch (readError) { - console.log(`${GREEN}<-- Success: ReadAsset - ${readError}${RESET}`); - } - - // all done with this listener - network2Org1.removeBlockListener(listener); - - } catch (runError) { - console.error(`Error in transaction: ${runError}`); - if (runError.stack) { - console.error(runError.stack); - } - } - } catch (error) { - console.error(`Error in setup: ${error}`); - if (error.stack) { - console.error(error.stack); - } - process.exit(1); - } - - await sleep(5000); - console.log(`${BLUE} **** END ****${RESET}`); - process.exit(0); -} -main(); diff --git a/asset-transfer-events/application-javascript/package.json b/asset-transfer-events/application-javascript/package.json deleted file mode 100644 index 04d5fa49e9..0000000000 --- a/asset-transfer-events/application-javascript/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "asset-transfer-events", - "version": "1.0.0", - "description": "Javascript application that uses chaincode events and block events with private data", - "engines": { - "node": ">=12", - "npm": ">=5" - }, - "engineStrict": true, - "author": "Hyperledger", - "license": "Apache-2.0", - "scripts": { - "lint": "eslint *.js" - }, - "dependencies": { - "fabric-ca-client": "^2.2.19", - "fabric-network": "^2.2.19" - }, - "devDependencies": { - "eslint": "^7.32.0" - } -} diff --git a/asset-transfer-private-data/README.md b/asset-transfer-private-data/README.md index a9613d8c22..8ee1528946 100644 --- a/asset-transfer-private-data/README.md +++ b/asset-transfer-private-data/README.md @@ -60,16 +60,10 @@ Like other samples, the Fabric test network is used to deploy and run this sampl 3. Run the application (from the `asset-transfer-private-data` folder). ``` - # To run the Javascript sample application - cd application-javascript - npm install - node app.js - # To run the Typescript sample application cd application-gateway-typescript npm install npm start - ``` ## Clean up diff --git a/asset-transfer-private-data/application-javascript/.eslintignore b/asset-transfer-private-data/application-javascript/.eslintignore deleted file mode 100644 index 1595847010..0000000000 --- a/asset-transfer-private-data/application-javascript/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -coverage diff --git a/asset-transfer-private-data/application-javascript/.eslintrc.js b/asset-transfer-private-data/application-javascript/.eslintrc.js deleted file mode 100644 index 8b83df7355..0000000000 --- a/asset-transfer-private-data/application-javascript/.eslintrc.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - */ - -module.exports = { - env: { - node: true, - mocha: true - }, - parserOptions: { - ecmaVersion: 8, - sourceType: 'script' - }, - extends: "eslint:recommended", - rules: { - indent: ['error', 4], - 'linebreak-style': ['error', 'unix'], - quotes: ['error', 'single'], - semi: ['error', 'always'], - 'no-unused-vars': ['error', { args: 'none' }], - 'no-console': 'off', - curly: 'error', - eqeqeq: 'error', - 'no-throw-literal': 'error', - strict: 'error', - 'no-var': 'error', - 'dot-notation': 'error', - 'no-tabs': 'error', - 'no-trailing-spaces': 'error', - 'no-use-before-define': 'error', - 'no-useless-call': 'error', - 'no-with': 'error', - 'operator-linebreak': 'error', - yoda: 'error', - 'quote-props': ['error', 'as-needed'] - } -}; diff --git a/asset-transfer-private-data/application-javascript/.gitignore b/asset-transfer-private-data/application-javascript/.gitignore deleted file mode 100644 index b7db091395..0000000000 --- a/asset-transfer-private-data/application-javascript/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -# Coverage directory used by tools like istanbul -coverage - -# Dependency directories -node_modules/ -jspm_packages/ -package-lock.json - -wallet.org1 -wallet.org2 -!wallet.org1/.gitkeep -!wallet.org2/.gitkeep \ No newline at end of file diff --git a/asset-transfer-private-data/application-javascript/app.js b/asset-transfer-private-data/application-javascript/app.js deleted file mode 100644 index 36c03f5845..0000000000 --- a/asset-transfer-private-data/application-javascript/app.js +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; - -const { Gateway, Wallets } = require('fabric-network'); -const FabricCAServices = require('fabric-ca-client'); -const path = require('path'); -const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js'); -const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js'); - -const myChannel = 'mychannel'; -const myChaincodeName = 'private'; - -const memberAssetCollectionName = 'assetCollection'; -const org1PrivateCollectionName = 'Org1MSPPrivateCollection'; -const org2PrivateCollectionName = 'Org2MSPPrivateCollection'; -const mspOrg1 = 'Org1MSP'; -const mspOrg2 = 'Org2MSP'; -const Org1UserId = 'appUser1'; -const Org2UserId = 'appUser2'; - -const RED = '\x1b[31m\n'; -const RESET = '\x1b[0m'; - -function prettyJSONString(inputString) { - if (inputString) { - return JSON.stringify(JSON.parse(inputString), null, 2); - } - else { - return inputString; - } -} - -function doFail(msgString) { - console.error(`${RED}\t${msgString}${RESET}`); - process.exit(1); -} - -function verifyAssetData(org, resultBuffer, expectedId, color, size, ownerUserId, appraisedValue) { - - let asset; - if (resultBuffer) { - asset = JSON.parse(resultBuffer.toString('utf8')); - } else { - doFail('Failed to read asset'); - } - console.log(`*** verify asset data for: ${expectedId}`); - if (!asset) { - doFail('Received empty asset'); - } - if (expectedId !== asset.assetID) { - doFail(`recieved asset ${asset.assetID} , but expected ${expectedId}`); - } - if (asset.color !== color) { - doFail(`asset ${asset.assetID} has color of ${asset.color}, expected value ${color}`); - } - if (asset.size !== size) { - doFail(`Failed size check - asset ${asset.assetID} has size of ${asset.size}, expected value ${size}`); - } - - if (asset.owner.includes(ownerUserId)) { - console.log(`\tasset ${asset.assetID} owner: ${asset.owner}`); - } else { - doFail(`Failed owner check from ${org} - asset ${asset.assetID} owned by ${asset.owner}, expected userId ${ownerUserId}`); - } - if (appraisedValue) { - if (asset.appraisedValue !== appraisedValue) { - doFail(`Failed appraised value check from ${org} - asset ${asset.assetID} has appraised value of ${asset.appraisedValue}, expected value ${appraisedValue}`); - } - } -} - -function verifyAssetPrivateDetails(resultBuffer, expectedId, appraisedValue) { - let assetPD; - if (resultBuffer) { - assetPD = JSON.parse(resultBuffer.toString('utf8')); - } else { - doFail('Failed to read asset private details'); - } - console.log(`*** verify private details: ${expectedId}`); - if (!assetPD) { - doFail('Received empty data'); - } - if (expectedId !== assetPD.assetID) { - doFail(`recieved ${assetPD.assetID} , but expected ${expectedId}`); - } - - if (appraisedValue) { - if (assetPD.appraisedValue !== appraisedValue) { - doFail(`Failed appraised value check - asset ${assetPD.assetID} has appraised value of ${assetPD.appraisedValue}, expected value ${appraisedValue}`); - } - } -} - -async function initContractFromOrg1Identity() { - console.log('\n--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer'); - // build an in memory object with the network configuration (also known as a connection profile) - const ccpOrg1 = buildCCPOrg1(); - - // build an instance of the fabric ca services client based on - // the information in the network configuration - const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com'); - - // setup the wallet to cache the credentials of the application user, on the app server locally - const walletPathOrg1 = path.join(__dirname, 'wallet/org1'); - const walletOrg1 = await buildWallet(Wallets, walletPathOrg1); - - // in a real application this would be done on an administrative flow, and only once - // stores admin identity in local wallet, if needed - await enrollAdmin(caOrg1Client, walletOrg1, mspOrg1); - // register & enroll application user with CA, which is used as client identify to make chaincode calls - // and stores app user identity in local wallet - // In a real application this would be done only when a new user was required to be added - // and would be part of an administrative flow - await registerAndEnrollUser(caOrg1Client, walletOrg1, mspOrg1, Org1UserId, 'org1.department1'); - - try { - // Create a new gateway for connecting to Org's peer node. - const gatewayOrg1 = new Gateway(); - // Connect using Discovery enabled - await gatewayOrg1.connect(ccpOrg1, - { wallet: walletOrg1, identity: Org1UserId, discovery: { enabled: true, asLocalhost: true } }); - - return gatewayOrg1; - } catch (error) { - console.error(`Error in connecting to gateway: ${error}`); - process.exit(1); - } -} - -async function initContractFromOrg2Identity() { - console.log('\n--> Fabric client user & Gateway init: Using Org2 identity to Org2 Peer'); - const ccpOrg2 = buildCCPOrg2(); - const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com'); - - const walletPathOrg2 = path.join(__dirname, 'wallet/org2'); - const walletOrg2 = await buildWallet(Wallets, walletPathOrg2); - - await enrollAdmin(caOrg2Client, walletOrg2, mspOrg2); - await registerAndEnrollUser(caOrg2Client, walletOrg2, mspOrg2, Org2UserId, 'org2.department1'); - - try { - // Create a new gateway for connecting to Org's peer node. - const gatewayOrg2 = new Gateway(); - await gatewayOrg2.connect(ccpOrg2, - { wallet: walletOrg2, identity: Org2UserId, discovery: { enabled: true, asLocalhost: true } }); - - return gatewayOrg2; - } catch (error) { - console.error(`Error in connecting to gateway: ${error}`); - process.exit(1); - } -} - -// Main workflow : usecase details at asset-transfer-private-data/chaincode-go/README.md -// This app uses fabric-samples/test-network based setup and the companion chaincode -// For this usecase illustration, we will use both Org1 & Org2 client identity from this same app -// In real world the Org1 & Org2 identity will be used in different apps to achieve asset transfer. -async function main() { - try { - - /** ******* Fabric client init: Using Org1 identity to Org1 Peer ********** */ - const gatewayOrg1 = await initContractFromOrg1Identity(); - const networkOrg1 = await gatewayOrg1.getNetwork(myChannel); - const contractOrg1 = networkOrg1.getContract(myChaincodeName); - // Since this sample chaincode uses, Private Data Collection level endorsement policy, addDiscoveryInterest - // scopes the discovery service further to use the endorsement policies of collections, if any - contractOrg1.addDiscoveryInterest({ name: myChaincodeName, collectionNames: [memberAssetCollectionName, org1PrivateCollectionName] }); - - /** ~~~~~~~ Fabric client init: Using Org2 identity to Org2 Peer ~~~~~~~ */ - const gatewayOrg2 = await initContractFromOrg2Identity(); - const networkOrg2 = await gatewayOrg2.getNetwork(myChannel); - const contractOrg2 = networkOrg2.getContract(myChaincodeName); - contractOrg2.addDiscoveryInterest({ name: myChaincodeName, collectionNames: [memberAssetCollectionName, org2PrivateCollectionName] }); - try { - // Sample transactions are listed below - // Add few sample Assets & transfers one of the asset from Org1 to Org2 as the new owner - let randomNumber = Math.floor(Math.random() * 1000) + 1; - // use a random key so that we can run multiple times - let assetID1 = `asset${randomNumber}`; - let assetID2 = `asset${randomNumber + 1}`; - const assetType = 'ValuableAsset'; - let result; - let asset1Data = { objectType: assetType, assetID: assetID1, color: 'green', size: 20, appraisedValue: 100 }; - let asset2Data = { objectType: assetType, assetID: assetID2, color: 'blue', size: 35, appraisedValue: 727 }; - - console.log('\n**************** As Org1 Client ****************'); - console.log('Adding Assets to work with:\n--> Submit Transaction: CreateAsset ' + assetID1); - let statefulTxn = contractOrg1.createTransaction('CreateAsset'); - // if you need to customize endorsement to specific set of Orgs, use setEndorsingOrganizations - // statefulTxn.setEndorsingOrganizations(mspOrg1); - let tmapData = Buffer.from(JSON.stringify(asset1Data)); - statefulTxn.setTransient({ - asset_properties: tmapData - }); - result = await statefulTxn.submit(); - - // Add asset2 - console.log('\n--> Submit Transaction: CreateAsset ' + assetID2); - statefulTxn = contractOrg1.createTransaction('CreateAsset'); - tmapData = Buffer.from(JSON.stringify(asset2Data)); - statefulTxn.setTransient({ - asset_properties: tmapData - }); - result = await statefulTxn.submit(); - - - console.log('\n--> Evaluate Transaction: GetAssetByRange asset0-asset9'); - // GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive) - result = await contractOrg1.evaluateTransaction('GetAssetByRange', 'asset0', 'asset9'); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - if (!result || result.length === 0) { - doFail('recieved empty query list for GetAssetByRange'); - } - console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails from ' + org1PrivateCollectionName); - // ReadAssetPrivateDetails reads data from Org's private collection. Args: collectionName, assetID - result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - verifyAssetPrivateDetails(result, assetID1, 100); - - // Attempt Transfer the asset to Org2 , without Org2 adding AgreeToTransfer // - // Transaction should return an error: "failed transfer verification ..." - let buyerDetails = { assetID: assetID1, buyerMSP: mspOrg2 }; - try { - console.log('\n--> Attempt Submit Transaction: TransferAsset ' + assetID1); - statefulTxn = contractOrg1.createTransaction('TransferAsset'); - tmapData = Buffer.from(JSON.stringify(buyerDetails)); - statefulTxn.setTransient({ - asset_owner: tmapData - }); - result = await statefulTxn.submit(); - console.log('******** FAILED: above operation expected to return an error'); - } catch (error) { - console.log(` Successfully caught the error: \n ${error}`); - } - console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); - console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1); - result = await contractOrg2.evaluateTransaction('ReadAsset', assetID1); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - verifyAssetData(mspOrg2, result, assetID1, 'green', 20, Org1UserId); - - - // Org2 cannot ReadAssetPrivateDetails from Org1's private collection due to Collection policy - // Will fail: await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); - - // Buyer from Org2 agrees to buy the asset assetID1 // - // To purchase the asset, the buyer needs to agree to the same value as the asset owner - let dataForAgreement = { assetID: assetID1, appraisedValue: 100 }; - console.log('\n--> Submit Transaction: AgreeToTransfer payload ' + JSON.stringify(dataForAgreement)); - statefulTxn = contractOrg2.createTransaction('AgreeToTransfer'); - tmapData = Buffer.from(JSON.stringify(dataForAgreement)); - statefulTxn.setTransient({ - asset_value: tmapData - }); - result = await statefulTxn.submit(); - - //Buyer can withdraw the Agreement, using DeleteTranferAgreement - /*statefulTxn = contractOrg2.createTransaction('DeleteTranferAgreement'); - statefulTxn.setEndorsingOrganizations(mspOrg2); - let dataForDeleteAgreement = { assetID: assetID1 }; - tmapData = Buffer.from(JSON.stringify(dataForDeleteAgreement)); - statefulTxn.setTransient({ - agreement_delete: tmapData - }); - result = await statefulTxn.submit();*/ - - console.log('\n**************** As Org1 Client ****************'); - // All members can send txn ReadTransferAgreement, set by Org2 above - console.log('\n--> Evaluate Transaction: ReadTransferAgreement ' + assetID1); - result = await contractOrg1.evaluateTransaction('ReadTransferAgreement', assetID1); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - - // Transfer the asset to Org2 // - // To transfer the asset, the owner needs to pass the MSP ID of new asset owner, and initiate the transfer - console.log('\n--> Submit Transaction: TransferAsset ' + assetID1); - - statefulTxn = contractOrg1.createTransaction('TransferAsset'); - tmapData = Buffer.from(JSON.stringify(buyerDetails)); - statefulTxn.setTransient({ - asset_owner: tmapData - }); - result = await statefulTxn.submit(); - - // Again ReadAsset : results will show that the buyer identity now owns the asset: - console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1); - result = await contractOrg1.evaluateTransaction('ReadAsset', assetID1); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - verifyAssetData(mspOrg1, result, assetID1, 'green', 20, Org2UserId); - - // Confirm that transfer removed the private details from the Org1 collection: - console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails'); - // ReadAssetPrivateDetails reads data from Org's private collection: Should return empty - result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - if (result && result.length > 0) { - doFail('Expected empty data from ReadAssetPrivateDetails'); - } - console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2); - result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - verifyAssetData(mspOrg1, result, assetID2, 'blue', 35, Org1UserId); - - console.log('\n********* Demo deleting asset **************'); - let dataForDelete = { assetID: assetID2 }; - try { - // Non-owner Org2 should not be able to DeleteAsset. Expect an error from DeleteAsset - console.log('--> Attempt Transaction: as Org2 DeleteAsset ' + assetID2); - statefulTxn = contractOrg2.createTransaction('DeleteAsset'); - tmapData = Buffer.from(JSON.stringify(dataForDelete)); - statefulTxn.setTransient({ - asset_delete: tmapData - }); - result = await statefulTxn.submit(); - console.log('******** FAILED : expected to return an error'); - } catch (error) { - console.log(` Successfully caught the error: \n ${error}`); - } - // Delete Asset2 as Org1 - console.log('--> Submit Transaction: as Org1 DeleteAsset ' + assetID2); - statefulTxn = contractOrg1.createTransaction('DeleteAsset'); - tmapData = Buffer.from(JSON.stringify(dataForDelete)); - statefulTxn.setTransient({ - asset_delete: tmapData - }); - result = await statefulTxn.submit(); - - console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2); - result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - if (result && result.length > 0) { - doFail('Expected empty read, after asset is deleted'); - } - - console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); - // Org2 can ReadAssetPrivateDetails: Org2 is owner, and private details exist in new owner's Collection - console.log('\n--> Evaluate Transaction as Org2: ReadAssetPrivateDetails ' + assetID1 + ' from ' + org2PrivateCollectionName); - result = await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org2PrivateCollectionName, assetID1); - console.log(`<-- result: ${prettyJSONString(result.toString())}`); - verifyAssetPrivateDetails(result, assetID1, 100); - } finally { - // Disconnect from the gateway peer when all work for this client identity is complete - gatewayOrg1.disconnect(); - gatewayOrg2.disconnect(); - } - } catch (error) { - console.error(`Error in transaction: ${error}`); - if (error.stack) { - console.error(error.stack); - } - process.exit(1); - } -} - -main(); diff --git a/asset-transfer-private-data/application-javascript/package.json b/asset-transfer-private-data/application-javascript/package.json deleted file mode 100644 index 355adb3f0f..0000000000 --- a/asset-transfer-private-data/application-javascript/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "asset-transfer-private-data", - "version": "1.0.0", - "description": "asset-transfer-private-data application implemented in JavaScript", - "engines": { - "node": ">=12", - "npm": ">=5" - }, - "scripts": { - "lint": "eslint .", - "pretest": "npm run lint", - "test": "nyc mocha --recursive" - }, - "engineStrict": true, - "author": "Hyperledger", - "license": "Apache-2.0", - "dependencies": { - "fabric-ca-client": "^2.2.19", - "fabric-network": "^2.2.19" - }, - "devDependencies": { - "chai": "^4.2.0", - "eslint": "^5.9.0", - "mocha": "^5.2.0", - "nyc": "^14.1.1", - "sinon": "^7.1.1", - "sinon-chai": "^3.3.0" - }, - "nyc": { - "exclude": [ - "coverage/**", - "test/**" - ], - "reporter": [ - "text-summary", - "html" - ], - "all": true, - "check-coverage": true, - "statements": 100, - "branches": 100, - "functions": 100, - "lines": 100 - } -} diff --git a/asset-transfer-secured-agreement/README.md b/asset-transfer-secured-agreement/README.md index 2cf2acfacb..83e4c2fc6f 100644 --- a/asset-transfer-secured-agreement/README.md +++ b/asset-transfer-secured-agreement/README.md @@ -50,10 +50,6 @@ 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 Javascript sample application - cd application-javascript - node app.js ``` ## Clean up diff --git a/asset-transfer-secured-agreement/application-javascript/.eslintignore b/asset-transfer-secured-agreement/application-javascript/.eslintignore deleted file mode 100644 index 1595847010..0000000000 --- a/asset-transfer-secured-agreement/application-javascript/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -coverage diff --git a/asset-transfer-secured-agreement/application-javascript/.eslintrc.js b/asset-transfer-secured-agreement/application-javascript/.eslintrc.js deleted file mode 100644 index 072edaf65a..0000000000 --- a/asset-transfer-secured-agreement/application-javascript/.eslintrc.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - */ -'use strict'; - -module.exports = { - env: { - node: true, - mocha: true - }, - parserOptions: { - ecmaVersion: 8, - sourceType: 'script' - }, - extends: 'eslint:recommended', - rules: { - indent: ['error', 'tab'], - 'linebreak-style': ['error', 'unix'], - quotes: ['error', 'single'], - semi: ['error', 'always'], - 'no-unused-vars': ['error', { args: 'none' }], - 'no-console': 'off', - curly: 'error', - eqeqeq: 'error', - 'no-throw-literal': 'error', - strict: 'error', - 'no-var': 'error', - 'dot-notation': 'error', - 'no-trailing-spaces': 'error', - 'no-use-before-define': 'error', - 'no-useless-call': 'error', - 'no-with': 'error', - 'operator-linebreak': 'error', - yoda: 'error', - 'quote-props': ['error', 'as-needed'] - } -}; diff --git a/asset-transfer-secured-agreement/application-javascript/.gitignore b/asset-transfer-secured-agreement/application-javascript/.gitignore deleted file mode 100644 index 21b287f72d..0000000000 --- a/asset-transfer-secured-agreement/application-javascript/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# - -# Coverage directory used by tools like istanbul -coverage - -# Dependency directories -node_modules/ -jspm_packages/ -package-lock.json - -wallet -!wallet/.gitkeep diff --git a/asset-transfer-secured-agreement/application-javascript/app.js b/asset-transfer-secured-agreement/application-javascript/app.js deleted file mode 100644 index b8a0958227..0000000000 --- a/asset-transfer-secured-agreement/application-javascript/app.js +++ /dev/null @@ -1,544 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; - -/** - * Application that uses implicit private data collections, state-based endorsement, - * and organization-based ownership and access control to keep data private and securely - * transfer an asset with the consent of both the current owner and buyer - * -- How to submit a transaction - * -- How to query - * -- How to limit the organizations involved in a transaction - * - * To see the SDK workings, try setting the logging to show on the console before running - * export HFC_LOGGING='{"debug":"console"}' - */ - -// pre-requisites: -// - fabric-sample two organization test-network setup with two peers, ordering service, -// and 2 certificate authorities -// ===> from directory /fabric-samples/test-network -// ./network.sh up createChannel -ca -// - Use the asset-transfer-secured-agreement/chaincode-go chaincode deployed on -// the channel "mychannel". The following deploy command will package, install, -// approve, and commit the golang chaincode, all the actions it takes -// to deploy a chaincode to a channel with the endorsement and private collection -// settings. -// ===> from directory /fabric-samples/test-network -// ./network.sh deployCC -ccn secured -ccp ../asset-transfer-secured-agreement/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -// -// - Be sure that node.js is installed -// ===> from directory /fabric-samples/asset-transfer-secured-agreement/application-javascript -// node -v -// - npm installed code dependencies -// ===> from directory /fabric-samples/asset-transfer-secured-agreement/application-javascript -// npm install -// - to run this test application -// ===> from directory /fabric-samples/asset-transfer-secured-agreement/application-javascript -// node app.js - -// NOTE: If you see an error like these: -/* - - Error in setup: Error: DiscoveryService: mychannel error: access denied - - OR - - Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] - - */ -// Delete the /fabric-samples/asset-transfer-secured-agreement/application-javascript/wallet directory -// and retry this application. -// -// The certificate authority must have been restarted and the saved certificates for the -// admin and application user are not valid. Deleting the wallet store will force these to be reset -// with the new certificate authority. -// - -const { Gateway, Wallets } = require('fabric-network'); -const FabricCAServices = require('fabric-ca-client'); -const path = require('path'); -const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js'); -const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js'); - -const channelName = 'mychannel'; -const chaincodeName = 'secured'; - -const org1 = 'Org1MSP'; -const org2 = 'Org2MSP'; -const Org1UserId = 'appUser1'; -const Org2UserId = 'appUser2'; - -const RED = '\x1b[31m\n'; -const GREEN = '\x1b[32m\n'; -const RESET = '\x1b[0m'; - -async function initGatewayForOrg1() { - console.log(`${GREEN}--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer${RESET}`); - // build an in memory object with the network configuration (also known as a connection profile) - const ccpOrg1 = buildCCPOrg1(); - - // build an instance of the fabric ca services client based on - // the information in the network configuration - const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com'); - - // setup the wallet to cache the credentials of the application user, on the app server locally - const walletPathOrg1 = path.join(__dirname, 'wallet', 'org1'); - const walletOrg1 = await buildWallet(Wallets, walletPathOrg1); - - // in a real application this would be done on an administrative flow, and only once - // stores admin identity in local wallet, if needed - await enrollAdmin(caOrg1Client, walletOrg1, org1); - // register & enroll application user with CA, which is used as client identify to make chaincode calls - // and stores app user identity in local wallet - // In a real application this would be done only when a new user was required to be added - // and would be part of an administrative flow - await registerAndEnrollUser(caOrg1Client, walletOrg1, org1, Org1UserId, 'org1.department1'); - - try { - // Create a new gateway for connecting to Org's peer node. - const gatewayOrg1 = new Gateway(); - //connect using Discovery enabled - await gatewayOrg1.connect(ccpOrg1, - { wallet: walletOrg1, identity: Org1UserId, discovery: { enabled: true, asLocalhost: true } }); - - return gatewayOrg1; - } catch (error) { - console.error(`Error in connecting to gateway for Org1: ${error}`); - process.exit(1); - } -} - -async function initGatewayForOrg2() { - console.log(`${GREEN}--> Fabric client user & Gateway init: Using Org2 identity to Org2 Peer${RESET}`); - const ccpOrg2 = buildCCPOrg2(); - const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com'); - - const walletPathOrg2 = path.join(__dirname, 'wallet', 'org2'); - const walletOrg2 = await buildWallet(Wallets, walletPathOrg2); - - await enrollAdmin(caOrg2Client, walletOrg2, org2); - await registerAndEnrollUser(caOrg2Client, walletOrg2, org2, Org2UserId, 'org2.department1'); - - try { - // Create a new gateway for connecting to Org's peer node. - const gatewayOrg2 = new Gateway(); - await gatewayOrg2.connect(ccpOrg2, - { wallet: walletOrg2, identity: Org2UserId, discovery: { enabled: true, asLocalhost: true } }); - - return gatewayOrg2; - } catch (error) { - console.error(`Error in connecting to gateway for Org2: ${error}`); - process.exit(1); - } -} - -async function readPrivateAsset(assetKey, org, contract) { - console.log(`${GREEN}--> Evaluate Transaction: GetAssetPrivateProperties, - ${assetKey} from organization ${org}${RESET}`); - try { - const resultBuffer = await contract.evaluateTransaction('GetAssetPrivateProperties', assetKey); - const asset = JSON.parse(resultBuffer.toString('utf8')); - console.log(`*** Result: GetAssetPrivateProperties, ${JSON.stringify(asset)}`); - - } catch (evalError) { - console.log(`*** Failed evaluateTransaction readPrivateAsset: ${evalError}`); - } -} - -async function readBidPrice(assetKey, org, contract) { - console.log(`${GREEN}--> Evaluate Transaction: GetAssetBidPrice, - ${assetKey} from organization ${org}${RESET}`); - try { - const resultBuffer = await contract.evaluateTransaction('GetAssetBidPrice', assetKey); - const asset = JSON.parse(resultBuffer.toString('utf8')); - console.log(`*** Result: GetAssetBidPrice, ${JSON.stringify(asset)}`); - - } catch (evalError) { - console.log(`*** Failed evaluateTransaction GetAssetBidPrice: ${evalError}`); - } -} - -async function readSalePrice(assetKey, org, contract) { - console.log(`${GREEN}--> Evaluate Transaction: GetAssetSalesPrice, - ${assetKey} from organization ${org}${RESET}`); - try { - const resultBuffer = await contract.evaluateTransaction('GetAssetSalesPrice', assetKey); - const asset = JSON.parse(resultBuffer.toString('utf8')); - console.log(`*** Result: GetAssetSalesPrice, ${JSON.stringify(asset)}`); - - } catch (evalError) { - console.log(`*** Failed evaluateTransaction GetAssetSalesPrice: ${evalError}`); - } -} - -function checkAsset(org, resultBuffer, ownerOrg) { - let asset; - if (resultBuffer) { - asset = JSON.parse(resultBuffer.toString('utf8')); - } - - if (asset) { - if (asset.ownerOrg === ownerOrg) { - console.log(`*** Result from ${org} - asset ${asset.assetID} owned by ${asset.ownerOrg} DESC:${asset.publicDescription}`); - } else { - console.log(`${RED}*** Failed owner check from ${org} - asset ${asset.assetID} owned by ${asset.ownerOrg} DESC:${asset.publicDescription}${RESET}`); - } - } -} - -// This is not a real function for an application, this simulates when two applications are running -// from different organizations and what they would see if they were to both query the asset -async function readAssetByBothOrgs(assetKey, ownerOrg, contractOrg1, contractOrg2) { - console.log(`${GREEN}--> Evaluate Transactions: ReadAsset, - ${assetKey} should be owned by ${ownerOrg}${RESET}`); - let resultBuffer; - resultBuffer = await contractOrg1.evaluateTransaction('ReadAsset', assetKey); - checkAsset('Org1', resultBuffer, ownerOrg); - resultBuffer = await contractOrg2.evaluateTransaction('ReadAsset', assetKey); - checkAsset('Org2', resultBuffer, ownerOrg); -} - -// This application uses fabric-samples/test-network based setup and the companion chaincode -// For this illustration, both Org1 & Org2 client identities will be used, however -// notice they are used by two different "gateway"s to simulate two different running -// applications from two different organizations. -async function main() { - console.log(`${GREEN} **** START ****${RESET}`); - try { - const randomNumber = Math.floor(Math.random() * 100) + 1; - let assetKey; - - /** ******* Fabric client init: Using Org1 identity to Org1 Peer ******* */ - const gatewayOrg1 = await initGatewayForOrg1(); - const networkOrg1 = await gatewayOrg1.getNetwork(channelName); - const contractOrg1 = networkOrg1.getContract(chaincodeName); - - /** ******* Fabric client init: Using Org2 identity to Org2 Peer ******* */ - const gatewayOrg2 = await initGatewayForOrg2(); - const networkOrg2 = await gatewayOrg2.getNetwork(channelName); - const contractOrg2 = networkOrg2.getContract(chaincodeName); - - try { - let transaction; - - try { - // Create an asset by organization Org1, this only requires the owning - // organization to endorse. - // With the gateway using discovery, we should limit the organizations used - // to endorse. This only requires knowledge of the Organizations and not - // the actual peers that may be active at any given time. - const asset_properties = { - object_type: 'asset_properties', - color: 'blue', - size: 35, - salt: Buffer.from(randomNumber.toString()).toString('hex') - }; - const asset_properties_string = JSON.stringify(asset_properties); - console.log(`${GREEN}--> Submit Transaction: CreateAsset as Org1 - endorsed by Org1${RESET}`); - console.log(`${asset_properties_string}`); - transaction = contractOrg1.createTransaction('CreateAsset'); - transaction.setEndorsingOrganizations(org1); - transaction.setTransient({ - asset_properties: Buffer.from(asset_properties_string) - }); - assetKey = await transaction.submit( `Asset owned by ${org1} is not for sale`); - console.log(`*** Result: committed, asset ${assetKey} is owned by Org1`); - } catch (createError) { - console.log(`${RED}*** Failed: CreateAsset - ${createError}${RESET}`); - } - - // read the public details by both orgs - await readAssetByBothOrgs(assetKey, org1, contractOrg1, contractOrg2); - // Org1 should be able to read the private data details of this asset - await readPrivateAsset(assetKey, org1, contractOrg1); - // Org2 is not the owner and does not have the private details, this should fail - await readPrivateAsset(assetKey, org2, contractOrg2); - - try { - // This is an update to the public state and requires only the owner to endorse. - console.log(`${GREEN}--> Submit Transaction: ChangePublicDescription ${assetKey}, as Org1 - endorse by Org1${RESET}`); - transaction = contractOrg1.createTransaction('ChangePublicDescription'); - transaction.setEndorsingOrganizations(org1); - await transaction.submit(assetKey, `Asset ${assetKey} owned by ${org1} is for sale`); - console.log(`*** Result: committed, asset ${assetKey} is now for sale by Org1`); - } catch (updateError) { - console.log(`${RED}*** Failed: ChangePublicDescription - ${updateError}${RESET}`); - } - - // read the public details by both orgs - await readAssetByBothOrgs(assetKey, org1, contractOrg1, contractOrg2); - - try { - // This is an update to the public state and requires the owner(Org1) to endorse and - // sent by the owner org client (Org1). - // Since the client is from Org2, which is not the owner, this will fail - console.log(`${GREEN}--> Submit Transaction: ChangePublicDescription ${assetKey}, as Org2 - endorse by Org2${RESET}`); - transaction = contractOrg2.createTransaction('ChangePublicDescription'); - transaction.setEndorsingOrganizations(org2); - await transaction.submit(assetKey, `Asset ${assetKey} owned by ${org2} is NOT for sale`); - console.log(`${RESET}*** Failed: Org2 is not the owner and this should have failed${RESET}`); - } catch (updateError) { - console.log(`*** Success: ChangePublicDescription has failed endorsememnt by Org2 sent by Org2 - ${updateError}`); - } - - try { - // This is an update to the public state and requires the owner(Org1) to endorse and - // sent by the owner org client (Org1). - // Since this is being sent by Org2, which is not the owner, this will fail - console.log(`${GREEN}--> Submit Transaction: ChangePublicDescription ${assetKey}, as Org2 - endorse by Org1${RESET}`); - transaction = contractOrg2.createTransaction('ChangePublicDescription'); - transaction.setEndorsingOrganizations(org1); - await transaction.submit(assetKey, `Asset ${assetKey} owned by ${org2} is NOT for sale`); - console.log(`${RESET}*** Failed: Org2 is not the owner and this should have failed${RESET}`); - } catch (updateError) { - console.log(`*** Success: ChangePublicDescription has failed endorsement by Org1 sent by Org2 - ${updateError}`); - } - - // read the public details by both orgs - await readAssetByBothOrgs(assetKey, org1, contractOrg1, contractOrg2); - - try { - // Agree to a sell by Org1 - const asset_price = { - asset_id: assetKey.toString(), - price: 110, - trade_id: randomNumber.toString() - }; - const asset_price_string = JSON.stringify(asset_price); - console.log(`${GREEN}--> Submit Transaction: AgreeToSell, ${assetKey} as Org1 - endorsed by Org1${RESET}`); - transaction = contractOrg1.createTransaction('AgreeToSell'); - transaction.setEndorsingOrganizations(org1); - transaction.setTransient({ - asset_price: Buffer.from(asset_price_string) - }); - //call agree to sell with desired price - await transaction.submit(assetKey); - console.log(`*** Result: committed, Org1 has agreed to sell asset ${assetKey} for 110`); - } catch (sellError) { - console.log(`${RED}*** Failed: AgreeToSell - ${sellError}${RESET}`); - } - - try { - // check the private information about the asset from Org2 - // Org1 would have to send Org2 these details, so the hash of the - // details may be checked by the chaincode. - const asset_properties = { - object_type: 'asset_properties', - color: 'blue', - size: 35, - salt: Buffer.from(randomNumber.toString()).toString('hex') - }; - const asset_properties_string = JSON.stringify(asset_properties); - console.log(`${GREEN}--> Evalute: VerifyAssetProperties, ${assetKey} as Org2 - endorsed by Org2${RESET}`); - console.log(`${asset_properties_string}`); - transaction = contractOrg2.createTransaction('VerifyAssetProperties'); - transaction.setTransient({ - asset_properties: Buffer.from(asset_properties_string) - }); - const verifyResultBuffer = await transaction.evaluate(assetKey); - if (verifyResultBuffer) { - const verifyResult = Boolean(verifyResultBuffer.toString()); - if (verifyResult) { - console.log(`*** Successfully VerifyAssetProperties, private information about asset ${assetKey} has been verified by Org2`); - } else { - console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetKey} has not been verified by Org2`); - } - } else { - console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetKey} has not been verified by Org2`); - } - } catch (verifyError) { - console.log(`${RED}*** Failed: VerifyAssetProperties - ${verifyError}${RESET}`); - } - - try { - // Agree to a buy by Org2 - const asset_price = { - asset_id: assetKey.toString(), - price: 100, - trade_id: randomNumber.toString() - }; - const asset_price_string = JSON.stringify(asset_price); - const asset_properties = { - object_type: 'asset_properties', - color: 'blue', - size: 35, - salt: Buffer.from(randomNumber.toString()).toString('hex') - }; - const asset_properties_string = JSON.stringify(asset_properties); - console.log(`${GREEN}--> Submit Transaction: AgreeToBuy, ${assetKey} as Org2 - endorsed by Org2${RESET}`); - transaction = contractOrg2.createTransaction('AgreeToBuy'); - transaction.setEndorsingOrganizations(org2); - transaction.setTransient({ - asset_price: Buffer.from(asset_price_string), - asset_properties: Buffer.from(asset_properties_string) - }); - await transaction.submit(assetKey); - console.log(`*** Result: committed, Org2 has agreed to buy asset ${assetKey} for 100`); - } catch (buyError) { - console.log(`${RED}*** Failed: AgreeToBuy - ${buyError}${RESET}`); - } - - // read the public details by both orgs - await readAssetByBothOrgs(assetKey, org1, contractOrg1, contractOrg2); - - // Org1 should be able to read the private data details of this asset - await readPrivateAsset(assetKey, org1, contractOrg1); - // Org2 is not the owner and does not have the private details, this should fail - await readPrivateAsset(assetKey, org2, contractOrg2); - - // Org1 should be able to read the sale price of this asset - await readSalePrice(assetKey, org1, contractOrg1); - // Org2 has not set a sale price and this should fail - await readSalePrice(assetKey, org2, contractOrg2); - - // Org1 has not agreed to buy so this should fail - await readBidPrice(assetKey, org1, contractOrg1); - // Org2 should be able to see the price it has agreed - await readBidPrice(assetKey, org2, contractOrg2); - - try { - // Org1 will try to transfer the asset to Org2 - // This will fail due to the sell price and the bid price - // are not the same - const asset_price = { - asset_id: assetKey.toString(), - price: 110, - trade_id: randomNumber.toString() - }; - const asset_price_string = JSON.stringify(asset_price); - - console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetKey} as Org1 - endorsed by Org1${RESET}`); - transaction = contractOrg1.createTransaction('TransferAsset'); - transaction.setEndorsingOrganizations(org1); - transaction.setTransient({ - asset_price: Buffer.from(asset_price_string) - }); - await transaction.submit(assetKey, org2); - console.log(`${RED}*** Failed: committed, TransferAsset should have failed for asset ${assetKey}${RESET}`); - } catch (transferError) { - console.log(`*** Success: TransferAsset - ${transferError}`); - } - - try { - // Agree to a sell by Org1 - // Org1, the seller will agree to the bid price of Org2 - const asset_price = { - asset_id: assetKey.toString(), - price: 100, - trade_id: randomNumber.toString() - }; - const asset_price_string = JSON.stringify(asset_price); - console.log(`${GREEN}--> Submit Transaction: AgreeToSell, ${assetKey} as Org1 - endorsed by Org1${RESET}`); - transaction = contractOrg1.createTransaction('AgreeToSell'); - transaction.setEndorsingOrganizations(org1); - transaction.setTransient({ - asset_price: Buffer.from(asset_price_string) - }); - await transaction.submit(assetKey); - console.log(`*** Result: committed, Org1 has agreed to sell asset ${assetKey} for 100`); - } catch (sellError) { - console.log(`${RED}*** Failed: AgreeToSell - ${sellError}${RESET}`); - } - - // read the public details by both orgs - await readAssetByBothOrgs(assetKey, org1, contractOrg1, contractOrg2); - - // Org1 should be able to read the private data details of this asset - await readPrivateAsset(assetKey, org1, contractOrg1); - - // Org1 should be able to read the sale price of this asset - await readSalePrice(assetKey, org1, contractOrg1); - - // Org2 should be able to see the price it has agreed - await readBidPrice(assetKey, org2, contractOrg2); - - try { - // Org2 user will try to transfer the asset to Org2 - // This will fail as the owner is Org1 - const asset_price = { - asset_id: assetKey.toString(), - price: 100, - trade_id: randomNumber.toString() - }; - const asset_price_string = JSON.stringify(asset_price); - - console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetKey} as Org2 - endorsed by Org1${RESET}`); - transaction = contractOrg2.createTransaction('TransferAsset'); - transaction.setEndorsingOrganizations(org1, org2); - transaction.setTransient({ - asset_price: Buffer.from(asset_price_string) - }); - await transaction.submit(assetKey, org2); - console.log(`${RED}*** FAILED: committed, TransferAsset - Org2 now owns the asset ${assetKey}${RESET}`); - } catch (transferError) { - console.log(`*** Succeded: TransferAsset - ${transferError}`); - } - - try { - // Org1 will transfer the asset to Org2 - // This will now complete as the sell price and the bid price are the same - const asset_price = { - asset_id: assetKey.toString(), - price: 100, - trade_id: randomNumber.toString() - }; - const asset_price_string = JSON.stringify(asset_price); - - console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetKey} as Org1 - endorsed by Org1${RESET}`); - - transaction = contractOrg1.createTransaction('TransferAsset'); - transaction.setEndorsingOrganizations(org1, org2); - transaction.setTransient({ - asset_price: Buffer.from(asset_price_string) - }); - await transaction.submit(assetKey, org2); - console.log(`*** Results: committed, TransferAsset - Org2 now owns the asset ${assetKey}`); - } catch (transferError) { - console.log(`${RED}*** Failed: TransferAsset - ${transferError}${RESET}`); - } - - // read the public details by both orgs - await readAssetByBothOrgs(assetKey, org2, contractOrg1, contractOrg2); - - // Org2 should be able to read the private data details of this asset - await readPrivateAsset(assetKey, org2, contractOrg2); - // Org1 should not be able to read the private data details of this asset - await readPrivateAsset(assetKey, org1, contractOrg1); - - try { - // This is an update to the public state and requires only the owner to endorse. - // Org2 wants to indicate that the items is no longer for sale - console.log(`${GREEN}--> Submit Transaction: ChangePublicDescription ${assetKey}, as Org2 - endorse by Org2${RESET}`); - transaction = contractOrg2.createTransaction('ChangePublicDescription'); - transaction.setEndorsingOrganizations(org2); - await transaction.submit(assetKey, `Asset ${assetKey} owned by ${org2} is NOT for sale`); - console.log('*** Results: committed - Org2 is now the owner and asset is not for sale'); - } catch (updateError) { - console.log(`${RED}*** Failed: ChangePublicDescription has failed by Org2 - ${updateError}${RESET}`); - } - - // read the public details by both orgs - await readAssetByBothOrgs(assetKey, org2, contractOrg1, contractOrg2); - } catch (runError) { - console.error(`Error in transaction: ${runError}`); - if (runError.stack) { - console.error(runError.stack); - } - process.exit(1); - } finally { - // Disconnect from the gateway peer when all work for this client identity is complete - console.log(`${GREEN}--> Close gateways`); - gatewayOrg1.disconnect(); - gatewayOrg2.disconnect(); - } - } catch (error) { - console.error(`Error in setup: ${error}`); - if (error.stack) { - console.error(error.stack); - } - process.exit(1); - } - console.log(`${GREEN} **** END ****${RESET}`); -} -main(); diff --git a/asset-transfer-secured-agreement/application-javascript/package.json b/asset-transfer-secured-agreement/application-javascript/package.json deleted file mode 100644 index 44f47295e6..0000000000 --- a/asset-transfer-secured-agreement/application-javascript/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "asset-transfer-secured-agreement", - "version": "1.0.0", - "description": "Javascript application that uses implicit private data collections, state-based endorsement, and organization-based ownership and access control to keep data private and securely transfer an asset with the consent of both the current owner and buyer", - "engines": { - "node": ">=12", - "npm": ">=5" - }, - "engineStrict": true, - "author": "Hyperledger", - "license": "Apache-2.0", - "scripts": { - "lint": "eslint *.js" - }, - "dependencies": { - "fabric-ca-client": "^2.2.19", - "fabric-network": "^2.2.19" - }, - "devDependencies": { - "eslint": "^7.32.0" - } -} diff --git a/ci/scripts/run-test-network-basic.sh b/ci/scripts/run-test-network-basic.sh index 966bcc8a83..023acaddd9 100755 --- a/ci/scripts/run-test-network-basic.sh +++ b/ci/scripts/run-test-network-basic.sh @@ -38,44 +38,34 @@ set -x createNetwork -# Run Go application -print "Initializing Go application" -export CHAINCODE_NAME=basic_${CHAINCODE_LANGUAGE}_for_go_app +# Run Go gateway application +print "Initializing Go gateway application" +export CHAINCODE_NAME=go_gateway deployChaincode -pushd ../asset-transfer-basic/application-go +pushd ../asset-transfer-basic/application-gateway-go print "Executing AssetTransfer.go" go run . popd -# Run Java application -print "Initializing Java application" -export CHAINCODE_NAME=basic_${CHAINCODE_LANGUAGE}_for_java_app -deployChaincode -pushd ../asset-transfer-basic/application-java -print "Executing Gradle Run" -gradle run -popd -# Run Javascript application -print "Initializing Javascript application" -export CHAINCODE_NAME=basic_${CHAINCODE_LANGUAGE}_for_javascript_app +# Run gateway typescript application +print "Initializing Typescript gateway application" +export CHAINCODE_NAME=typescript_gateway deployChaincode -pushd ../asset-transfer-basic/application-javascript +pushd ../asset-transfer-basic/application-gateway-typescript npm install -print "Executing app.js" -node app.js +print "Start application" +npm start popd -# Run typescript application -print "Initializing Typescript application" -export CHAINCODE_NAME=basic_${CHAINCODE_LANGUAGE}_for_typescript_app + +# Run Java application using gateway +print "Initializing Java application" +export CHAINCODE_NAME=java_gateway deployChaincode -pushd ../asset-transfer-basic/application-typescript -npm install -print "Building app.ts" -npm run build -print "Running the output app" -node dist/app.js +pushd ../asset-transfer-basic/application-gateway-java +print "Executing Gradle Run" +./gradlew run popd diff --git a/ci/scripts/run-test-network-events.sh b/ci/scripts/run-test-network-events.sh index 03242d833a..cf7fb2374e 100755 --- a/ci/scripts/run-test-network-events.sh +++ b/ci/scripts/run-test-network-events.sh @@ -1,3 +1,5 @@ +#!/bin/bash + set -euo pipefail CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-javascript} @@ -23,27 +25,12 @@ function stopNetwork() { ./network.sh down } -# Run Javascript application -createNetwork -print "Initializing Javascript application" -pushd ../asset-transfer-events/application-javascript -npm install -print "Executing app.js" -node app.js -popd -stopNetwork -print "Remove wallet storage" -rm -R ../asset-transfer-events/application-javascript/wallet - - # Run typescript gateway application createNetwork print "Initializing TypeScript gateway application" pushd ../asset-transfer-events/application-gateway-typescript npm install -print "Build app" -npm run build -print "Executing dist/app.js" +print "Start application" npm start popd stopNetwork diff --git a/ci/scripts/run-test-network-gateway.sh b/ci/scripts/run-test-network-gateway.sh deleted file mode 100755 index 3174816aab..0000000000 --- a/ci/scripts/run-test-network-gateway.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-go} -CHAINCODE_PATH=${CHAINCODE_PATH:-../asset-transfer-basic} - -function print() { - GREEN='\033[0;32m' - NC='\033[0m' - echo - echo -e "${GREEN}${1}${NC}" -} - -function createNetwork() { - print "Creating 3 Org network" - ./network.sh up createChannel -ca -s couchdb - cd addOrg3 - ./addOrg3.sh up -ca -s couchdb - cd .. -} - -function deployChaincode() { - print "Deploying ${CHAINCODE_NAME} chaincode" - ./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccp "${CHAINCODE_PATH}/chaincode-${CHAINCODE_LANGUAGE}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}" -} - -function stopNetwork() { - print "Stopping network" - ./network.sh down -} - -# print all executed commands to assist with debug in CI environment -set -x - -# Set up one test network to run each test scenario. -# Each test will create an independent scope by installing a new chaincode contract to the channel. -createNetwork - - -# Run Go gateway application -print "Initializing Go gateway application" -export CHAINCODE_NAME=go_gateway -deployChaincode -pushd ../asset-transfer-basic/application-gateway-go -print "Executing AssetTransfer.go" -go run . -popd - - -# Run gateway typescript application -print "Initializing Typescript gateway application" -export CHAINCODE_NAME=typescript_gateway -deployChaincode -pushd ../asset-transfer-basic/application-gateway-typescript -npm install -print "Building app.ts" -npm run build -print "Running the output app" -node dist/app.js -popd - - -# Run Java application using gateway -print "Initializing Java application" -export CHAINCODE_NAME=java_gateway -deployChaincode -pushd ../asset-transfer-basic/application-gateway-java -print "Executing Gradle Run" -./gradlew run -popd - - -stopNetwork - -{ set +x; } 2>/dev/null diff --git a/ci/scripts/run-test-network-hsm.sh b/ci/scripts/run-test-network-hsm.sh index 5ecd171538..39bfd5770a 100755 --- a/ci/scripts/run-test-network-hsm.sh +++ b/ci/scripts/run-test-network-hsm.sh @@ -34,31 +34,14 @@ function stopNetwork() { # Each test will create an independent scope by installing a new chaincode contract to the channel. createNetwork - -# Run typescript HSM application -print "Initializing Typescript HSM application" -export CHAINCODE_NAME=typescript_hsm -deployChaincode -pushd ../asset-transfer-basic/application-typescript-hsm -print "Setup SoftHSM" -export SOFTHSM2_CONF=$PWD/softhsm2.conf -print "install dependencies" -npm install -print "Building app.ts" -npm run build -print "Running the output app" -node dist/app.js -popd +echo 'go install pkcs11 enabled fabric-ca-client' +GOBIN=${PWD}/../bin go install -tags pkcs11 github.com/hyperledger/fabric-ca/cmd/fabric-ca-client@latest +fabric-ca-client version # Run Typescript HSM gateway application print "Initializing Typescript HSM Gateway application" export CHAINCODE_NAME=ts_hsm_gateway deployChaincode -echo 'Delete fabric-ca-client from samples bin' -rm ../bin/fabric-ca-client -echo 'go install pkcs11 enabled fabric-ca-client' -GOBIN=${PWD}/../bin go install -tags pkcs11 github.com/hyperledger/fabric-ca/cmd/fabric-ca-client@latest -fabric-ca-client version print "Initializing Typescript HSM gateway application" pushd ../hardware-security-module/scripts/ print "Enroll and register User in HSM" diff --git a/ci/scripts/run-test-network-private.sh b/ci/scripts/run-test-network-private.sh index 33b1c13d41..c973409b82 100755 --- a/ci/scripts/run-test-network-private.sh +++ b/ci/scripts/run-test-network-private.sh @@ -1,3 +1,5 @@ +#!/bin/bash + set -euo pipefail CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-go} @@ -23,25 +25,12 @@ function stopNetwork() { ./network.sh down } -# Run Javascript application -createNetwork -print "Initializing Javascript application" -pushd ../asset-transfer-private-data/application-javascript -npm install -print "Executing app.js" -node app.js -popd -stopNetwork - - # Run typescript gateway application createNetwork print "Initializing typescript application" pushd ../asset-transfer-private-data/application-gateway-typescript npm install -print "Build typescript app" -npm run build -print "Executing app.js" +print "Start application" npm start popd stopNetwork diff --git a/ci/scripts/run-test-network-secured.sh b/ci/scripts/run-test-network-secured.sh index 6450ee984f..19d13f1f93 100755 --- a/ci/scripts/run-test-network-secured.sh +++ b/ci/scripts/run-test-network-secured.sh @@ -1,3 +1,5 @@ +#!/bin/bash + set -euo pipefail CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-go} @@ -23,26 +25,12 @@ function stopNetwork() { ./network.sh down } -# Run Javascript application -createNetwork -print "Initializing Javascript application" -pushd ../asset-transfer-secured-agreement/application-javascript -npm install -print "Executing app.js" -node app.js -popd -stopNetwork -print "Remove wallet storage" -rm -R ../asset-transfer-secured-agreement/application-javascript/wallet - # Run Typescript Gateway application createNetwork print "Initializing typescript application" pushd ../asset-transfer-secured-agreement/application-gateway-typescript npm install -print "Build app" -npm run build -print "Executing dist/app.js" +print "Start application" npm start popd stopNetwork diff --git a/hardware-security-module/application-typescript/package.json b/hardware-security-module/application-typescript/package.json index e57d185303..afc5d051a0 100644 --- a/hardware-security-module/application-typescript/package.json +++ b/hardware-security-module/application-typescript/package.json @@ -11,7 +11,7 @@ "prepare": "npm run build", "clean": "rimraf dist", "lint": "eslint . --ext .ts", - "start": "SOFTHSM2_CONF=${HOME}/softhsm2.conf node dist/hsm-sample.js", + "start": "SOFTHSM2_CONF=\"${SOFTHSM2_CONF:-${HOME}/softhsm2.conf}\" node dist/hsm-sample.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", diff --git a/hardware-security-module/scripts/generate-hsm-user.sh b/hardware-security-module/scripts/generate-hsm-user.sh index faeefe820d..709ccb0ab1 100755 --- a/hardware-security-module/scripts/generate-hsm-user.sh +++ b/hardware-security-module/scripts/generate-hsm-user.sh @@ -1,19 +1,16 @@ #!/usr/bin/env bash set -eo pipefail # script directory -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" # define the CA setup CA_HOST=localhost -CA_URL=${CA_HOST}:7054 +CA_URL="${CA_HOST}:7054" TLS_CERT="${SCRIPT_DIR}/../../test-network/organizations/fabric-ca/org1/tls-cert.pem" -LocateHsmLib() { - if [[ -n "${PKCS11_LIB}" && -f "${PKCS11_LIB}" ]]; then - echo "${PKCS11_LIB}" - return - fi +export SOFTHSM2_CONF="${SOFTHSM2_CONF:-${HOME}/softhsm2.conf}" +LocateHsmLib() { local POSSIBLE_LIB_LOC=( \ '/usr/lib/softhsm/libsofthsm2.so' \ '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so' \ @@ -29,35 +26,56 @@ LocateHsmLib() { done } -HSM2_LIB=$(LocateHsmLib) -[ -z "$HSM2_LIB" ] && echo No SoftHSM PKCS11 Library found, ensure you have installed softhsm2 && exit 1 +HSM2_LIB="${PKCS11_LIB:-$(LocateHsmLib)}" +[ -z "${HSM2_LIB}" ] && echo No SoftHSM PKCS11 Library found, ensure you have installed softhsm2 && exit 1 # create a softhsm2.conf file if one doesn't exist -HSM2_CONF=$HOME/softhsm2.conf -[ ! -f "$HSM2_CONF" ] && echo directories.tokendir = /tmp > "$HSM2_CONF" +if [ ! -f "${SOFTHSM2_CONF}" ]; then + TMPDIR="${TMPDIR:-/tmp}" + mkdir -p "${TMPDIR}/softhsm" + echo "directories.tokendir = ${TMPDIR}/softhsm" > "${SOFTHSM2_CONF}" + softhsm2-util --init-token --slot 0 --label 'ForFabric' --pin 98765432 --so-pin 1234 +fi # Update the client config file to point to the softhsm pkcs11 library # which must be in $HOME/softhsm directory -CLIENT_CONFIG_TEMPLATE=${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config-template.yaml -CLIENT_CONFIG=${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config.yaml -cp $CLIENT_CONFIG_TEMPLATE $CLIENT_CONFIG +CLIENT_CONFIG_TEMPLATE="${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config-template.yaml" +CLIENT_CONFIG="${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config.yaml" -if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' s+REPLACE_ME_HSMLIB+"${HSM2_LIB}"+g $CLIENT_CONFIG -else - sed -i s+REPLACE_ME_HSMLIB+"${HSM2_LIB}"+g $CLIENT_CONFIG -fi +CLIENT_CONFIG_CONTENT="$( sed "s+REPLACE_ME_HSMLIB+${HSM2_LIB}+g" "${CLIENT_CONFIG_TEMPLATE}" )" +echo "${CLIENT_CONFIG_CONTENT}" > "${CLIENT_CONFIG}" # create the users, remove any existing users -CRYPTO_PATH=$SCRIPT_DIR/../crypto-material/hsm -[ -d "$CRYPTO_PATH" ] && rm -fr "$CRYPTO_PATH" +CRYPTO_PATH="${SCRIPT_DIR}/../crypto-material/hsm" +[ -d "${CRYPTO_PATH}" ] && rm -fr "${CRYPTO_PATH}" # user passed in as parameter CAADMIN="admin" CAADMIN_PW="adminpw" -HSMUSER=$1 +HSMUSER="$1" + +fabric-ca-client enroll \ + -c "${CLIENT_CONFIG}" \ + -u "https://${CAADMIN}:${CAADMIN_PW}@${CA_URL}" \ + --mspdir "${CRYPTO_PATH}/${CAADMIN}" \ + --tls.certfiles "${TLS_CERT}" + +! fabric-ca-client register \ + -c "${CLIENT_CONFIG}" \ + --mspdir "${CRYPTO_PATH}/${CAADMIN}" \ + --id.name "${HSMUSER}" \ + --id.secret "${HSMUSER}" \ + --id.type client \ + --caname ca-org1 \ + --id.maxenrollments 0 \ + -m example.com \ + -u "https://${CA_URL}" \ + --tls.certfiles "${TLS_CERT}" \ + && echo user probably already registered, continuing -SOFTHSM2_CONF=$HSM2_CONF fabric-ca-client enroll -c $CLIENT_CONFIG -u https://$CAADMIN:$CAADMIN_PW@$CA_URL --mspdir "$CRYPTO_PATH"/$CAADMIN --tls.certfiles "${TLS_CERT}" -! SOFTHSM2_CONF=$HSM2_CONF fabric-ca-client register -c $CLIENT_CONFIG --mspdir "$CRYPTO_PATH"/$CAADMIN --id.name "$HSMUSER" --id.secret "$HSMUSER" --id.type client --caname ca-org1 --id.maxenrollments 0 -m example.com -u https://$CA_URL --tls.certfiles "${TLS_CERT}" && echo user probably already registered, continuing -SOFTHSM2_CONF=$HSM2_CONF fabric-ca-client enroll -c $CLIENT_CONFIG -u https://"$HSMUSER":"$HSMUSER"@$CA_URL --mspdir "$CRYPTO_PATH"/"$HSMUSER" --tls.certfiles "${TLS_CERT}" +fabric-ca-client enroll \ + -c "${CLIENT_CONFIG}" \ + -u "https://${HSMUSER}:${HSMUSER}@${CA_URL}" \ + --mspdir "${CRYPTO_PATH}/${HSMUSER}" \ + --tls.certfiles "${TLS_CERT}" diff --git a/off_chain_data/legacy-application-javascript/.eslintrc.yaml b/off_chain_data/legacy-application-javascript/.eslintrc.yaml deleted file mode 100644 index 050ac5ccc2..0000000000 --- a/off_chain_data/legacy-application-javascript/.eslintrc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -env: - browser: true - commonjs: true - es6: true -globals: - Atomics: readonly - SharedArrayBuffer: readonly -parserOptions: - ecmaVersion: 2018 -rules: {} diff --git a/off_chain_data/legacy-application-javascript/.gitignore b/off_chain_data/legacy-application-javascript/.gitignore deleted file mode 100644 index cc6066a0be..0000000000 --- a/off_chain_data/legacy-application-javascript/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -addAssets.json -mychannel__lifecycle.log -mychannel_basic.log -nextblock.txt -wallet/ \ No newline at end of file diff --git a/off_chain_data/legacy-application-javascript/README.md b/off_chain_data/legacy-application-javascript/README.md deleted file mode 100644 index 27caee319c..0000000000 --- a/off_chain_data/legacy-application-javascript/README.md +++ /dev/null @@ -1,328 +0,0 @@ -# Off Chain data - -This sample demonstrates how you can use [Peer channel-based event services](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html) -to replicate the data on your blockchain network to an off chain database. -Using an off chain database allows you to analyze the data from your network or -build a dashboard without degrading the performance of your application. - -This sample uses the [Fabric network event listener](https://hyperledger.github.io/fabric-sdk-node/release-1.4/tutorial-channel-events.html) from the Node.JS Fabric SDK to write data to local instance of -CouchDB. - -## Getting started - -This sample uses Node Fabric SDK application code to connect to a running instance -of the Fabric test network. Make sure that you are running the following -commands from the `off_chain_data/legacy-javascript` directory. - -### Starting the Network - -Use the following command to start the sample network: - -``` -./startFabric.sh -``` - -This command will deploy an instance of the Fabric test network. The network -consists of an ordering service, two peer organizations with one peers each, and -a CA for each org. The command also creates a channel named `mychannel`. The -`asset-transfer-basic` chaincode will be installed on both peers and deployed to -the channel. - -### Configuration - -The configuration for the listener is stored in the `config.json` file: - -``` -{ - "peer_name": "peer0.org1.example.com", - "channelid": "mychannel", - "use_couchdb":true, - "create_history_log":true, - "couchdb_address": "http://admin:password@localhost:5990" -} -``` - -`peer_name:` is the target peer for the listener. -`channelid:` is the channel name for block events. -`use_couchdb:` If set to true, events will be stored in a local instance of -CouchDB. If set to false, only a local log of events will be stored. -`create_history_log:` If true, a local log file will be created with all of the -block changes. -`couchdb_address:` is the local address for an off chain CouchDB database with username and password. - -### Create an instance of CouchDB - -If you set the "use_couchdb" option to true in `config.json`, you can run the -following command start a local instance of CouchDB using docker: - -``` -docker run -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --publish 5990:5984 --detach --name offchaindb couchdb:3.2.2 -docker start offchaindb -``` - - -### Install dependencies - -You need to install Node.js version 8.9.x to use the sample application code. -Execute the following commands to install the required dependencies: - -``` -npm install -``` - -### Starting the Channel Event Listener - -After we have installed the application dependencies, we can use the Node.js SDK -to create the identity our listener application will use to interact with the -network. Run the following command to enroll the admin user: -``` -node enrollAdmin.js -``` - -You can then run the following command to register and enroll an application -user: - -``` -node registerUser.js -``` - -We can then use our application user to start the block event listener: - -``` -node blockEventListener.js -``` - -If the command is successful, you should see the output of the listener reading -the configuration blocks of `mychannel` in addition to the blocks that recorded -the approval and commitment of the assets chaincode definition. - -`blockEventListener.js` creates a listener named "offchain-listener" on the -channel `mychannel`. The listener writes each block added to the channel to a -processing map called BlockMap for temporary storage and ordering purposes. -`blockEventListener.js` uses `nextblock.txt` to keep track of the latest block -that was retrieved by the listener. The block number in `nextblock.txt` may be -set to a previous block number in order to replay previous blocks. The file -may also be deleted and all blocks will be replayed when the block listener is -started. - -`BlockProcessing.js` runs as a daemon and pulls each block in order from the -BlockMap. It then uses the read-write set of that block to extract the latest -key value data and store it in the database. The configuration blocks of -mychannel did not any data to the database because the blocks did not contain a -read-write set. - -The channel event listener also writes metadata from each block to a log file -defined as channelid_chaincodeid.log. In this example, events will be written to -a file named `mychannel_basic.log`. This allows you to record a history of -changes made by each block for each key in addition to storing the latest value -of the world state. - -**Note:** Leave the blockEventListener.js running in a terminal window. Open a -new window to execute the next parts of the demo. - -### Generate data on the blockchain - -Now that our listener is setup, we can generate data using the assets chaincode -and use our application to replicate the data to our database. Open a new -terminal and navigate to the `fabric-samples/off_chain_data/legacy-javascript` directory. - -You can use the `addAssets.js` file to add random sample data to blockchain. -The file uses the configuration information stored in `addAssets.json` to -create a series of assets. This file will be created during the first execution -of `addAssets.js` if it does not exist. This program can be run multiple times -without changing the properties. The `nextAssetNumber` will be incremented and -stored in the `addAssets.json` file. - -``` - { - "nextAssetNumber": 100, - "numberAssetsToAdd": 20 - } -``` - -Open a new window and run the following command to add 20 assets to the -blockchain: - -``` -node addAssets.js -``` - -After the assets have been added to the ledger, use the following command to -transfer one of the assets to a new owner: - -``` -node transferAsset.js asset110 james -``` - -Now run the following command to delete the asset that was transferred: - -``` -node deleteAsset.js asset110 -``` - -## Offchain CouchDB storage: - -If you followed the instructions above and set `use_couchdb` to true, -`blockEventListener.js` will create two tables in the local instance of CouchDB. -`blockEventListener.js` is written to create two tables for each channel and for -each chaincode. - -The first table is an offline representation of the current world state of the -blockchain ledger. This table was created using the read-write set data from -the blocks. If the listener is running, this table should be the same as the -latest values in the state database running on your peer. The table is named -after the channelid and chaincodeid, and is named mychannel_basic in this -example. You can navigate to this table using your browser: -http://127.0.0.1:5990/mychannel_basic/_all_docs - -A second table records each block as a historical record entry, and was created -using the block data that was recorded in the log file. The table name appends -history to the name of the first table, and is named mychannel_basic_history -in this example. You can also navigate to this table using your browser: -http://127.0.0.1:5990/mychannel_basic_history/_all_docs - -### Configure a map/reduce view for summarizing counts of assets by color: - -Now that we have state and history data replicated to tables in CouchDB, we -can use the following commands query our off-chain data. We will also add an -index to support a more complex query. Note that if the `blockEventListener.js` -is not running, the database commands below may fail since the database is only -created when events are received. - -Open a new terminal window and execute the following: - -``` -curl -X PUT http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign -d '{"views":{"colorview":{"map":"function (doc) { emit(doc.color, 1);}","reduce":"function ( keys , values , combine ) {return sum( values )}"}}}' -H 'Content-Type:application/json' -``` - -Execute a query to retrieve the total number of assets (reduce function): - -``` -curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?reduce=true -``` - -If successful, this command will return the number of assets in the blockchain -world state, without having to query the blockchain ledger: - -``` -{"rows":[ - {"key":null,"value":19} - ]} -``` - -Execute a new query to retrieve the number of assets by color (map function): - -``` -curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?group=true -``` - -The command will return a list of assets by color from the CouchDB database. - -``` -{"rows":[ - {"key":"blue","value":2}, - {"key":"green","value":2}, - {"key":"purple","value":3}, - {"key":"red","value":4}, - {"key":"white","value":6}, - {"key":"yellow","value":2} - ]} -``` - -To run a more complex command that reads through the block history database, we -will create an index of the blocknumber, sequence, and key fields. This index -will support a query that traces the history of each asset. Execute the -following command to create the index: - -``` -curl -X POST http://127.0.0.1:5990/mychannel_basic_history/_index -d '{"index":{"fields":["blocknumber", "sequence", "key"]},"name":"asset_history"}' -H 'Content-Type:application/json' -``` - -Now execute a query to retrieve the history for the asset we transferred and -then deleted: - -``` -curl -X POST http://127.0.0.1:5990/mychannel_basic_history/_find -d '{"selector":{"key":{"$eq":"asset110"}}, "fields":["blocknumber","is_delete","value"],"sort":[{"blocknumber":"asc"}, {"sequence":"asc"}]}' -H 'Content-Type:application/json' -``` - -You should see the transaction history of the asset that was created, -transferred, and then removed from the ledger. - -``` -{"docs":[ -{"blocknumber":12,"is_delete":false,"value":"{\"docType\":\"asset\",\"name\":\"asset110\",\"color\":\"blue\",\"size\":60,\"owner\":\"debra\"}"}, -{"blocknumber":22,"is_delete":false,"value":"{\"docType\":\"asset\",\"name\":\"asset110\",\"color\":\"blue\",\"size\":60,\"owner\":\"james\"}"}, -{"blocknumber":23,"is_delete":true,"value":""} - ]} -``` - -## Getting historical data from the network - -You can also use the `blockEventListener.js` program to retrieve historical data -from your network. This allows you to create a database that is up to date with -the latest data from the network or recover any blocks that the program may -have missed. - -If you ran through the example steps above, navigate back to the terminal window -where `blockEventListener.js` is running and close it. Once the listener is no -longer running, use the following command to add 20 more assets to the -ledger: - -``` -node addAssets.js -``` - -The listener will not be able to add the new assets to your CouchDB database. -If you check the current state table using the reduce command, you will only -be able to see the original assets in your database. - -``` -curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?reduce=true -``` - -To add the new data to your off-chain database, remove the `nextblock.txt` -file that kept track of the latest block read by `blockEventListener.js`: - -``` -rm nextblock.txt -``` - -You can new re-run the channel listener to read every block from the channel: - -``` -node blockEventListener.js -``` - -This will rebuild the CouchDB tables and include the 20 assets that have been -added to the ledger. If you run the reduce command against your database one -more time, - -``` -curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?reduce=true -``` - -you will be able to see that all of the assets have been added to your -database: - -``` -{"rows":[ -{"key":null,"value":39} -]} -``` - -## Clean up - -If you are finished using the sample application, you can bring down the network -and any accompanying artifacts by running the following command: -``` -./network-clean.sh -``` - -Running the script will complete the following actions: - -* Bring down the Fabric test network. -* Takes down the local CouchDB database. -* Remove the certificates you generated by deleting the `wallet` folder. -* Delete `nextblock.txt` so you can start with the first block next time you - operate the listener. -* Removes `addAssets.json`. diff --git a/off_chain_data/legacy-application-javascript/addAssets.js b/off_chain_data/legacy-application-javascript/addAssets.js deleted file mode 100644 index 38119571c1..0000000000 --- a/off_chain_data/legacy-application-javascript/addAssets.js +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -/* - * - * addAssets.js will add random sample data to blockchain. - * - * $ node addAssets.js - * - * addAssets will add 10 Assets by default with a starting Asset name of "Asset100". - * Additional Assets will be added by incrementing the number at the end of the Asset name. - * - * The properties for adding Assets are stored in addAssets.json. This file will be created - * during the first execution of the utility if it does not exist. The utility can be run - * multiple times without changing the properties. The nextAssetNumber will be incremented and - * stored in the JSON file. - * - * { - * "nextAssetNumber": 100, - * "numberAssetsToAdd": 10 - * } - * - */ - -'use strict'; - -const { Wallets, Gateway } = require('fabric-network'); -const fs = require('fs'); -const path = require('path'); - -const addAssetsConfigFile = path.resolve(__dirname, 'addAssets.json'); - -const colors=[ 'blue', 'red', 'yellow', 'green', 'white', 'purple' ]; -const owners=[ 'tom', 'fred', 'julie', 'james', 'janet', 'henry', 'alice', 'marie', 'sam', 'debra', 'nancy']; -const sizes=[ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ]; -const appraisedValues=[ 300, 310, 320, 330, 340, 350, 360, 370, 380, 390 ]; -const docType='asset' - -const config = require('./config.json'); -const channelid = config.channelid; - -async function main() { - - try { - - let nextAssetNumber; - let numberAssetsToAdd; - let addAssetsConfig; - - // check to see if there is a config json defined - if (fs.existsSync(addAssetsConfigFile)) { - // read file the next asset and number of assets to create - let addAssetsConfigJSON = fs.readFileSync(addAssetsConfigFile, 'utf8'); - addAssetsConfig = JSON.parse(addAssetsConfigJSON); - nextAssetNumber = addAssetsConfig.nextAssetNumber; - numberAssetsToAdd = addAssetsConfig.numberAssetsToAdd; - } else { - nextAssetNumber = 100; - numberAssetsToAdd = 20; - // create a default config and save - addAssetsConfig = new Object; - addAssetsConfig.nextAssetNumber = nextAssetNumber; - addAssetsConfig.numberAssetsToAdd = numberAssetsToAdd; - fs.writeFileSync(addAssetsConfigFile, JSON.stringify(addAssetsConfig, null, 2)); - } - - // Parse the connection profile. - const ccpPath = path.resolve(__dirname, '..', '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json'); - const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); - - // Configure a wallet. This wallet must already be primed with an identity that - // the application can use to interact with the peer node. - const walletPath = path.resolve(__dirname, 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - - // Create a new gateway, and connect to the gateway peer node(s). The identity - // specified must already exist in the specified wallet. - const gateway = new Gateway(); - await gateway.connect(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: true } }); - - // Get the network channel that the smart contract is deployed to. - const network = await gateway.getNetwork(channelid); - - // Get the smart contract from the network channel. - const contract = network.getContract('basic'); - - for (var counter = nextAssetNumber; counter < nextAssetNumber + numberAssetsToAdd; counter++) { - - var randomColor = Math.floor(Math.random() * (6)); - var randomOwner = Math.floor(Math.random() * (11)); - var randomSize = Math.floor(Math.random() * (10)); - var randomValue = Math.floor(Math.random() * (9)); - - // Submit the 'CreateAsset' transaction to the smart contract, and wait for it - // to be committed to the ledger. - await contract.submitTransaction('CreateAsset', docType+counter, colors[randomColor], ''+sizes[randomSize], owners[randomOwner],appraisedValues[randomValue]); - console.log("Adding asset: " + docType + counter + " owner:" + owners[randomOwner] + " color:" + colors[randomColor] + " size:" + '' + sizes[randomSize] + " appraised value:" + '' + appraisedValues[randomValue] ); - - } - - await gateway.disconnect(); - - addAssetsConfig.nextAssetNumber = nextAssetNumber + numberAssetsToAdd; - - fs.writeFileSync(addAssetsConfigFile, JSON.stringify(addAssetsConfig, null, 2)); - - } catch (error) { - console.error(`Failed to submit transaction: ${error}`); - process.exit(1); - } - -} - -main(); diff --git a/off_chain_data/legacy-application-javascript/blockEventListener.js b/off_chain_data/legacy-application-javascript/blockEventListener.js deleted file mode 100644 index 610ded88f7..0000000000 --- a/off_chain_data/legacy-application-javascript/blockEventListener.js +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -/* - -blockEventListener.js is an nodejs application to listen for block events from -a specified channel. - -Configuration is stored in config.json: - -{ - "peer_name": "peer0.org1.example.com", - "channelid": "mychannel", - "use_couchdb":false, - "couchdb_address": "http://localhost:5990" -} - -peer_name: target peer for the listener -channelid: channel name for block events -use_couchdb: if set to true, events will be stored in a local couchdb -couchdb_address: local address for an off chain couchdb database - -Note: If use_couchdb is set to false, only a local log of events will be stored. - -Usage: - -node bockEventListener.js - -The block event listener will log events received to the console and write event blocks to -a log file based on the channelid and chaincode name. - -The event listener stores the next block to retrieve in a file named nextblock.txt. This file -is automatically created and initialized to zero if it does not exist. - -*/ - -'use strict'; - -const { Wallets, Gateway } = require('fabric-network'); -const fs = require('fs'); -const path = require('path'); - -const couchdbutil = require('./couchdbutil.js'); -const blockProcessing = require('./blockProcessing.js'); - -const config = require('./config.json'); -const channelid = config.channelid; -const peer_name = config.peer_name; -const use_couchdb = config.use_couchdb; -const couchdb_address = config.couchdb_address; - -const configPath = path.resolve(__dirname, 'nextblock.txt'); - -const nano = require('nano')(couchdb_address); - -// simple map to hold blocks for processing -class BlockMap { - constructor() { - this.list = [] - } - get(key) { - key = parseInt(key, 10).toString(); - return this.list[`block${key}`]; - } - set(key, value) { - this.list[`block${key}`] = value; - } - remove(key) { - key = parseInt(key, 10).toString(); - delete this.list[`block${key}`]; - } -} - -let ProcessingMap = new BlockMap() - -async function main() { - try { - - // initialize the next block to be 0 - let nextBlock = 0; - - // check to see if there is a next block already defined - if (fs.existsSync(configPath)) { - // read file containing the next block to read - nextBlock = fs.readFileSync(configPath, 'utf8'); - } else { - // store the next block as 0 - fs.writeFileSync(configPath, parseInt(nextBlock, 10)) - } - - // Create a new file system based wallet for managing identities. - const walletPath = path.join(process.cwd(), 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - console.log(`Wallet path: ${walletPath}`); - - // Check to see if we've already enrolled the user. - const userExists = await wallet.get('appUser'); - if (!userExists) { - console.log('An identity for the user "appUser" does not exist in the wallet'); - console.log('Run the enrollUser.js application before retrying'); - return; - } - - // Parse the connection profile. This would be the path to the file downloaded - // from the IBM Blockchain Platform operational console. - const ccpPath = path.resolve(__dirname, '..', '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json'); - const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); - // Create a new gateway for connecting to our peer node. - const gateway = new Gateway(); - await gateway.connect(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: true } }); - - // Get the network (channel) our contract is deployed to. - const network = await gateway.getNetwork('mychannel'); - - const listener = async (event) => { - // Add the block to the processing map by block number - await ProcessingMap.set(event.blockNumber, event.blockData); - console.log(`Added block ${event.blockNumber} to ProcessingMap`); - }; - const options = { filtered: false, startBlock: parseInt(nextBlock, 10) }; - await network.addBlockListener(listener, options); - - console.log(`Listening for block events, nextblock: ${nextBlock}`); - - // start processing, looking for entries in the ProcessingMap - processPendingBlocks(ProcessingMap); - - } catch (error) { - console.error(`Failed to evaluate transaction: ${error}`); - process.exit(1); - } -} - -// listener function to check for blocks in the ProcessingMap -async function processPendingBlocks(ProcessingMap) { - - setTimeout(async () => { - - // get the next block number from nextblock.txt - let nextBlockNumber = fs.readFileSync(configPath, 'utf8'); - let processBlock; - - do { - - // get the next block to process from the ProcessingMap - processBlock = ProcessingMap.get(nextBlockNumber) - - if (processBlock == undefined) { - break; - } - - try { - await blockProcessing.processBlockEvent(channelid, processBlock, use_couchdb, nano) - } catch (error) { - console.error(`Failed to process block: ${error}`); - } - - // if successful, remove the block from the ProcessingMap - ProcessingMap.remove(nextBlockNumber); - - // increment the next block number to the next block - fs.writeFileSync(configPath, parseInt(nextBlockNumber, 10) + 1) - - // retrive the next block number to process - nextBlockNumber = fs.readFileSync(configPath, 'utf8'); - - } while (true); - - processPendingBlocks(ProcessingMap); - - }, 250); - -} - -main(); diff --git a/off_chain_data/legacy-application-javascript/blockProcessing.js b/off_chain_data/legacy-application-javascript/blockProcessing.js deleted file mode 100644 index 83275abcb6..0000000000 --- a/off_chain_data/legacy-application-javascript/blockProcessing.js +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -'use strict'; - -const fs = require('fs'); -const path = require('path'); - -const couchdbutil = require('./couchdbutil.js'); - -const configPath = path.resolve(__dirname, 'nextblock.txt'); - -exports.processBlockEvent = async function (channelname, block, use_couchdb, nano) { - - return new Promise((async (resolve, reject) => { - - // reject the block if the block number is not defined - if (block.header.number == undefined) { - reject(new Error('Undefined block number')); - } - - const blockNumber = block.header.number - - console.log(`------------------------------------------------`); - console.log(`Block Number: ${blockNumber}`); - - // reject if the data is not set - if (block.data.data == undefined) { - reject(new Error('Data block is not defined')); - } - - const dataArray = block.data.data; - - // transaction filter for each transaction in dataArray - const txSuccess = block.metadata.metadata[2]; - - for (var dataItem in dataArray) { - - // reject if a timestamp is not set - if (dataArray[dataItem].payload.header.channel_header.timestamp == undefined) { - reject(new Error('Transaction timestamp is not defined')); - } - - // tx may be rejected at commit stage by peers - // only valid transactions (code=0) update the word state and off-chain db - // filter through valid tx, refer below for list of error codes - // https://github.com/hyperledger/fabric-sdk-node/blob/release-1.4/fabric-client/lib/protos/peer/transaction.proto - if (txSuccess[dataItem] !== 0) { - continue; - } - - const timestamp = dataArray[dataItem].payload.header.channel_header.timestamp; - - // continue to next tx if no actions are set - if (dataArray[dataItem].payload.data.actions == undefined) { - continue; - } - - // actions are stored as an array. In Fabric 1.4.3 only one - // action exists per tx so we may simply use actions[0] - // in case Fabric adds support for multiple actions - // a for loop is used for demonstration - const actions = dataArray[dataItem].payload.data.actions; - - // iterate through all actions - for (var actionItem in actions) { - - // reject if a chaincode id is not defined - if (actions[actionItem].payload.chaincode_proposal_payload.input.chaincode_spec.chaincode_id.name == undefined) { - reject(new Error('Chaincode name is not defined')); - } - - const chaincodeID = actions[actionItem].payload.chaincode_proposal_payload.input.chaincode_spec.chaincode_id.name - - // reject if there is no readwrite set - if (actions[actionItem].payload.action.proposal_response_payload.extension.results.ns_rwset == undefined) { - reject(new Error('No readwrite set is defined')); - } - - const rwSet = actions[actionItem].payload.action.proposal_response_payload.extension.results.ns_rwset - - for (var record in rwSet) { - - // ignore lscc events - if (rwSet[record].namespace != 'lscc' && rwSet[record].namespace != '_lifecycle') { - // create object to store properties - const writeObject = new Object(); - writeObject.blocknumber = blockNumber; - writeObject.chaincodeid = chaincodeID; - writeObject.timestamp = timestamp; - writeObject.values = rwSet[record].rwset.writes; - - console.log(`Transaction Timestamp: ${writeObject.timestamp}`); - console.log(`ChaincodeID: ${writeObject.chaincodeid}`); - console.log(writeObject.values); - - const logfilePath = path.resolve(__dirname, 'nextblock.txt'); - - // send the object to a log file - fs.appendFileSync(channelname + '_' + chaincodeID + '.log', JSON.stringify(writeObject) + "\n"); - - // if couchdb is configured, then write to couchdb - if (use_couchdb) { - try { - await writeValuesToCouchDBP(nano, channelname, writeObject); - } catch (error) { - - } - } - } - }; - }; - }; - - // update the nextblock.txt file to retrieve the next block - fs.writeFileSync(configPath, parseInt(blockNumber, 10) + 1) - - resolve(true); - - })); -} - -async function writeValuesToCouchDBP(nano, channelname, writeObject) { - - return new Promise((async (resolve, reject) => { - - try { - - // define the database for saving block events by key - this emulates world state - const dbname = channelname + '_' + writeObject.chaincodeid; - // define the database for saving all block events - this emulates history - const historydbname = channelname + '_' + writeObject.chaincodeid + '_history'; - // set values to the array of values received - const values = writeObject.values; - - try { - for (var sequence in values) { - let keyvalue = - values[ - sequence - ]; - - if ( - keyvalue.is_delete == - true - ) { - await couchdbutil.deleteRecord( - nano, - dbname, - keyvalue.key - ); - } else { - if ( - isJSON( - keyvalue.value - ) - ) { - - // insert or update value by key - this emulates world state behavior - await couchdbutil.writeToCouchDB( - nano, - dbname, - keyvalue.key, - JSON.parse( - keyvalue.value - ) - ); - } - } - - // add additional fields for history - keyvalue.timestamp = - writeObject.timestamp; - keyvalue.blocknumber = parseInt( - writeObject.blocknumber, - 10 - ); - keyvalue.sequence = parseInt( - sequence, - 10 - ); - - await couchdbutil.writeToCouchDB( - nano, - historydbname, - null, - keyvalue - ); - } - } catch (error) { - console.log(error); - reject(error); - } - - } catch (error) { - console.error(`Failed to write to couchdb: ${error}`); - reject(error); - } - - resolve(true); - - })); - -} - -function isJSON(value) { - try { - JSON.parse(value); - } catch (e) { - return false; - } - return true; -} diff --git a/off_chain_data/legacy-application-javascript/config.json b/off_chain_data/legacy-application-javascript/config.json deleted file mode 100644 index 5852531f18..0000000000 --- a/off_chain_data/legacy-application-javascript/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "peer_name": "peer0.org1.example.com", - "channelid": "mychannel", - "use_couchdb":true, - "create_history_log":true, - "couchdb_address": "http://admin:password@localhost:5990" -} diff --git a/off_chain_data/legacy-application-javascript/couchdbutil.js b/off_chain_data/legacy-application-javascript/couchdbutil.js deleted file mode 100644 index c82c8b0113..0000000000 --- a/off_chain_data/legacy-application-javascript/couchdbutil.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -'use strict'; - -exports.createDatabaseIfNotExists = function (nano, dbname) { - - return new Promise((async (resolve, reject) => { - await nano.db.get(dbname, async function (err, body) { - if (err) { - if (err.statusCode == 404) { - await nano.db.create(dbname, function (err, body) { - if (!err) { - resolve(true); - } else { - reject(err); - } - }); - } else { - reject(err); - } - } else { - resolve(true); - } - }); - })); -} - -exports.writeToCouchDB = async function (nano, dbname, key, value) { - - return new Promise((async (resolve, reject) => { - - try { - await this.createDatabaseIfNotExists(nano, dbname); - } catch (error) { - console.log("Error creating the database-"+error) - } - - const db = nano.use(dbname); - - // If a key is not specified, then this is an insert - if (key == null) { - db.insert(value, async function (err, body, header) { - if (err) { - reject(err); - } - } - ); - } else { - - // If a key is specified, then attempt to retrieve the record by key - db.get(key, async function (err, body) { - // parse the value - const updateValue = value; - // if the record was found, then update the revision to allow the update - if (err == null) { - updateValue._rev = body._rev - } - // update or insert the value - db.insert(updateValue, key, async function (err, body, header) { - if (err) { - reject(err); - } - }); - }); - } - - resolve(true); - - })); -} - - -exports.deleteRecord = async function (nano, dbname, key) { - - return new Promise((async (resolve, reject) => { - - try { - await this.createDatabaseIfNotExists(nano, dbname); - } catch (error) { - console.log("Error creating the database-"+error) - } - - const db = nano.use(dbname); - - // If a key is specified, then attempt to retrieve the record by key - db.get(key, async function (err, body) { - - // if the record was found, then update the revision to allow the update - if (err == null) { - - let revision = body._rev - - // update or insert the value - db.destroy(key, revision, async function (err, body, header) { - if (err) { - reject(err); - } - }); - - } - }); - - resolve(true); - - })); -} diff --git a/off_chain_data/legacy-application-javascript/deleteAsset.js b/off_chain_data/legacy-application-javascript/deleteAsset.js deleted file mode 100644 index 37a8ff5884..0000000000 --- a/off_chain_data/legacy-application-javascript/deleteAsset.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -/* - * - * deleteAsset.js will delete a specified asset. Example: - * - * $ node deleteAsset.js asset100 - * - * The utility is meant to demonstrate delete block events. - */ - -'use strict'; - -const { Wallets, Gateway } = require('fabric-network'); -const fs = require('fs'); -const path = require('path'); - -const config = require('./config.json'); -const channelid = config.channelid; - -async function main() { - - if (process.argv[2] == undefined) { - console.log("Usage: node deleteAsset AssetId"); - process.exit(1); - } - - const deletekey = process.argv[2]; - - try { - - // Parse the connection profile. - const ccpPath = path.resolve(__dirname, '..', '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json'); - const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); - - // Configure a wallet. This wallet must already be primed with an identity that - // the application can use to interact with the peer node. - const walletPath = path.resolve(__dirname, 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - - // Create a new gateway, and connect to the gateway peer node(s). The identity - // specified must already exist in the specified wallet. - const gateway = new Gateway(); - await gateway.connect(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: true } }); - - // Get the network channel that the smart contract is deployed to. - const network = await gateway.getNetwork(channelid); - - // Get the smart contract from the network channel. - const contract = network.getContract('basic'); - - await contract.submitTransaction('DeleteAsset', deletekey); - console.log("Deleted asset: " + deletekey); - - await gateway.disconnect(); - - } catch (error) { - console.error(`Failed to submit transaction: ${error}`); - process.exit(1); - } - -} - -main(); diff --git a/off_chain_data/legacy-application-javascript/enrollAdmin.js b/off_chain_data/legacy-application-javascript/enrollAdmin.js deleted file mode 100644 index fe4577e641..0000000000 --- a/off_chain_data/legacy-application-javascript/enrollAdmin.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; - -const FabricCAServices = require('fabric-ca-client'); -const { Wallets, X509WalletMixin } = require('fabric-network'); -const fs = require('fs'); -const path = require('path'); - -async function main() { - try { - // load the network configuration - const ccpPath = path.resolve(__dirname, '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json'); - let ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); - - // Create a new CA client for interacting with the CA. - const caURL = ccp.certificateAuthorities['ca.org1.example.com'].url; - const ca = new FabricCAServices(caURL); - - // Create a new file system based wallet for managing identities. - const walletPath = path.join(process.cwd(), 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - console.log(`Wallet path: ${walletPath}`); - - // Check to see if we've already enrolled the admin user. - const adminExists = await wallet.get('admin'); - if (adminExists) { - console.log('An identity for the admin user "admin" already exists in the wallet'); - return; - } - - // Enroll the admin user, and import the new identity into the wallet. - const enrollment = await ca.enroll({ enrollmentID: 'admin', enrollmentSecret: 'adminpw' }); - const x509Identity = { - credentials: { - certificate: enrollment.certificate, - privateKey: enrollment.key.toBytes(), - }, - mspId: 'Org1MSP', - type: 'X.509', - }; - await wallet.put('admin', x509Identity); - console.log('Successfully enrolled admin user "admin" and imported it into the wallet'); - - } catch (error) { - console.error(`Failed to enroll admin user "admin": ${error}`); - process.exit(1); - } -} - -main(); diff --git a/off_chain_data/legacy-application-javascript/network-clean.sh b/off_chain_data/legacy-application-javascript/network-clean.sh deleted file mode 100755 index 1d24a2a742..0000000000 --- a/off_chain_data/legacy-application-javascript/network-clean.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# -# Copyright IBM Corp All Rights Reserved -# -# SPDX-License-Identifier: Apache-2.0 -# -# Exit on first error -set -ex - -# Bring the test network down -pushd ../test-network -./network.sh down -popd - -# clean out any old identites in the wallets -rm -rf wallet -rm -rf addAssets.json mychannel_basic.log mychannel__lifecycle.log nextblock.txt - -docker stop offchaindb -docker rm offchaindb diff --git a/off_chain_data/legacy-application-javascript/package.json b/off_chain_data/legacy-application-javascript/package.json deleted file mode 100644 index e4cb636e23..0000000000 --- a/off_chain_data/legacy-application-javascript/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "offchaindata", - "version": "1.0.0", - "description": "Offchain Data application implemented in JavaScript", - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "scripts": { - "lint": "eslint .", - "pretest": "npm run lint", - "test": "nyc mocha --recursive" - }, - "engineStrict": true, - "author": "Hyperledger", - "license": "Apache-2.0", - "dependencies": { - "fabric-ca-client": "^2.2.19", - "fabric-network": "^2.2.19" - }, - "devDependencies": { - "chai": "^4.2.0", - "eslint": "^5.9.0", - "mocha": "^5.2.0", - "nyc": "^13.1.0", - "sinon": "^7.1.1", - "sinon-chai": "^3.3.0" - }, - "nyc": { - "exclude": [ - "coverage/**", - "test/**" - ], - "reporter": [ - "text-summary", - "html" - ], - "all": true, - "check-coverage": true, - "statements": 100, - "branches": 100, - "functions": 100, - "lines": 100 - } -} diff --git a/off_chain_data/legacy-application-javascript/registerUser.js b/off_chain_data/legacy-application-javascript/registerUser.js deleted file mode 100644 index c1cefc5ba1..0000000000 --- a/off_chain_data/legacy-application-javascript/registerUser.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; - -const { Wallets, Gateway, X509WalletMixin } = require('fabric-network'); -const FabricCAServices = require('fabric-ca-client'); -const fs = require('fs'); -const path = require('path'); - -async function main() { - try { - // load the network configuration - const ccpPath = path.resolve(__dirname, '..', '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json'); - const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); - - // Create a new CA client for interacting with the CA. - const caURL = ccp.certificateAuthorities['ca.org1.example.com'].url; - const ca = new FabricCAServices(caURL); - - // Create a new file system based wallet for managing identities. - const walletPath = path.join(process.cwd(), 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - console.log(`Wallet path: ${walletPath}`); - - // Check to see if we've already enrolled the user. - const userExists = await wallet.get('appUser'); - if (userExists) { - console.log('An identity for the user "appUser" already exists in the wallet'); - return; - } - - // Check to see if we've already enrolled the admin user. - const adminIdentity = await wallet.get('admin'); - if (!adminIdentity) { - console.log('An identity for the admin user "admin" does not exist in the wallet'); - console.log('Run the enrollAdmin.js application before retrying'); - return; - } - - // build a user object for authenticating with the CA - const provider = wallet.getProviderRegistry().getProvider(adminIdentity.type); - const adminUser = await provider.getUserContext(adminIdentity, 'admin'); - - // Register the user, enroll the user, and import the new identity into the wallet. - const secret = await ca.register({ - affiliation: 'org1.department1', - enrollmentID: 'appUser', - role: 'client' - }, adminUser); - const enrollment = await ca.enroll({ - enrollmentID: 'appUser', - enrollmentSecret: secret - }); - const x509Identity = { - credentials: { - certificate: enrollment.certificate, - privateKey: enrollment.key.toBytes(), - }, - mspId: 'Org1MSP', - type: 'X.509', - }; - await wallet.put('appUser', x509Identity); - console.log('Successfully registered and enrolled admin user "appUser" and imported it into the wallet'); - - } catch (error) { - console.error(`Failed to register user "appUser": ${error}`); - process.exit(1); - } -} - -main(); diff --git a/off_chain_data/legacy-application-javascript/startFabric.sh b/off_chain_data/legacy-application-javascript/startFabric.sh deleted file mode 100755 index 3dc1a97a28..0000000000 --- a/off_chain_data/legacy-application-javascript/startFabric.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# -# Copyright IBM Corp All Rights Reserved -# -# SPDX-License-Identifier: Apache-2.0 -# -# Exit on first error -set -e pipefail - -starttime=$(date +%s) - -# launch network; create channel and join peer to channel -pushd ../../test-network - -# Fixes the issue of when running busybox in network down command on windows git bash -case "$(uname -s)" in - CYGWIN*|MINGW32*|MSYS*|MINGW*) - echo 'Running on MS Windows' - export MSYS_NO_PATHCONV=1 - ./network.sh down - unset MSYS_NO_PATHCONV - ;; - *) - ./network.sh down - ;; -esac -./network.sh up createChannel -ca -s couchdb -./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go - -popd - -cat <