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 <Mark.S.Lewis@outlook.com>
This commit is contained in:
Mark S. Lewis 2024-06-07 17:52:09 +01:00 committed by Dave Enyeart
parent e334b7527a
commit 76088d0273
80 changed files with 86 additions and 6000 deletions

View file

@ -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

View file

@ -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

View file

@ -1,4 +0,0 @@
wallet
!wallet/.gitkeep
keystore

View file

@ -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)
}

View file

@ -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
)

View file

@ -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=

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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" "$@"

View file

@ -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

View file

@ -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'

View file

@ -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);
}
}
}

View file

@ -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");
}
}

View file

@ -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<String> 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");
}
}

View file

@ -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

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -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']
}
};

View file

@ -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

View file

@ -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();

View file

@ -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"
}
}

View file

@ -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

View file

@ -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 <https://dist.opendnssec.org/source/softhsm-2.5.0.tar.gz>
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<node::http2::Http2Session, nghttp2_mem>::ReallocImpl(void*, unsigned long, void*) [node]
4: 0xa55ed3 node::mem::NgLibMemoryManager<node::http2::Http2Session, nghttp2_mem>::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.

View file

@ -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
}
}

View file

@ -1,5 +0,0 @@
directories.tokendir = /tmp/
objectstore.backend = file
# ERROR, WARNING, INFO, DEBUG
log.level = INFO

View file

@ -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<void> {
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();

View file

@ -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<string, any> => {
// 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<string, any> => {
// 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,
};

View file

@ -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<void> => {
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<void> => {
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;
}
}
};

View file

@ -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"
]
}

View file

@ -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": []
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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();

View file

@ -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<string, any> => {
// 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<string, any> => {
// 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<Wallet> => {
// 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,
};

View file

@ -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<string, any>, 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<void> => {
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<void> => {
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,
};

View file

@ -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"
]
}

View file

@ -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": []
}

View file

@ -159,16 +159,14 @@ Now that we have started the chaincode service and deployed it to the channel, w
## Using the Asset-Transfer-Basic external chaincode
Open yet another terminal and navigate to the `fabric-samples/asset-transfer-basic/application-javascript` directory:
Open yet another terminal and navigate to the `fabric-samples/asset-transfer-basic/application-gateway-go` directory:
```
cd fabric-samples/asset-transfer-basic/application-javascript
cd fabric-samples/asset-transfer-basic/application-gateway-go
```
Run the following commands to use the node application in this directory to test the external smart contract:
```
rm -rf wallet # in case you ran this before
npm install
node app.js
go run .
```
If all goes well, the program should run exactly the same as described in the "Writing Your First Application" tutorial.

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -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']
}
};

View file

@ -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

View file

@ -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();

View file

@ -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"
}
}

View file

@ -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

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -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']
}
};

View file

@ -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

View file

@ -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();

View file

@ -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
}
}

View file

@ -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

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -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']
}
};

View file

@ -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

View file

@ -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();

View file

@ -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"
}
}

View file

@ -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
# 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 "Start application"
npm start
popd
# Run Java application using gateway
print "Initializing Java application"
export CHAINCODE_NAME=basic_${CHAINCODE_LANGUAGE}_for_java_app
export CHAINCODE_NAME=java_gateway
deployChaincode
pushd ../asset-transfer-basic/application-java
pushd ../asset-transfer-basic/application-gateway-java
print "Executing Gradle Run"
gradle run
popd
# Run Javascript application
print "Initializing Javascript application"
export CHAINCODE_NAME=basic_${CHAINCODE_LANGUAGE}_for_javascript_app
deployChaincode
pushd ../asset-transfer-basic/application-javascript
npm install
print "Executing app.js"
node app.js
popd
# Run typescript application
print "Initializing Typescript application"
export CHAINCODE_NAME=basic_${CHAINCODE_LANGUAGE}_for_typescript_app
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
./gradlew run
popd

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -32,7 +32,7 @@ To be able to register and enroll identities using an HSM you need a PKCS#11 ena
To install this use the following command
```bash
go install -tags 'pkcs11' github.com/hyperledger/fabric-ca/cmd/fabric-ca-client@latest
go install -tags pkcs11 github.com/hyperledger/fabric-ca/cmd/fabric-ca-client@latest
```
## Create Fabric network and deploy the smart contract

View file

@ -11,7 +11,7 @@
"prepare": "npm run build",
"clean": "rimraf dist",
"lint": "eslint src",
"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": "",

View file

@ -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,57 @@ 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}"
fi
softhsm2-util --init-token --slot 0 --label 'ForFabric' --pin 98765432 --so-pin 1234 || true
# 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"
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://${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
fabric-ca-client enroll \
-c "${CLIENT_CONFIG}" \
-u "https://${HSMUSER}:${HSMUSER}@${CA_URL}" \
--mspdir "${CRYPTO_PATH}/${HSMUSER}" \
--tls.certfiles "${TLS_CERT}"

View file

@ -1,10 +0,0 @@
env:
browser: true
commonjs: true
es6: true
globals:
Atomics: readonly
SharedArrayBuffer: readonly
parserOptions:
ecmaVersion: 2018
rules: {}

View file

@ -1,5 +0,0 @@
addAssets.json
mychannel__lifecycle.log
mychannel_basic.log
nextblock.txt
wallet/

View file

@ -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`.

View file

@ -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();

View file

@ -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();

View file

@ -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;
}

View file

@ -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"
}

View file

@ -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);
}));
}

View file

@ -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();

View file

@ -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();

View file

@ -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

View file

@ -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
}
}

View file

@ -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();

View file

@ -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 <sh: cd: line 1: can't cd to /data: No such file or directory> 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 <<EOF
Total setup execution time : $(($(date +%s) - starttime)) secs ...
EOF

View file

@ -1,69 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
/*
* tranferAsset.js will transfer ownership a specified asset to a new ownder. Example:
*
* $ node transferAsset.js asset102 jimmy
*
* The utility is meant to demonstrate update 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 && process.argv[3] == undefined) {
console.log("Usage: node transferAsset.js assetId owner");
process.exit(1);
}
const updatekey = process.argv[2];
const newowner = process.argv[3];
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('TransferAsset', updatekey, newowner);
console.log("Transferred asset " + updatekey + " to " + newowner);
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
}
main();