├── contracts ├── asset-transfer-ts │ ├── README.md │ ├── .gitignore │ ├── dist │ │ ├── index.d.ts │ │ ├── asset.d.ts │ │ ├── assetTransfer.d.ts │ │ ├── index.js │ │ ├── asset.js │ │ └── assetTransfer.js │ ├── .prettierrc.js │ ├── .eslintignore │ ├── src │ │ ├── index.ts │ │ ├── asset.ts │ │ └── assetTransfer.ts │ ├── target │ │ └── npmlist.json │ ├── .editorconfig │ ├── .npmignore │ ├── docker │ │ └── docker-entrypoint.sh │ ├── .vscode │ │ └── launch.json │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── Dockerfile │ └── package.json ├── chaincode-java │ ├── .gitignore │ ├── .gradle │ │ ├── 6.5.1 │ │ │ ├── gc.properties │ │ │ ├── fileChanges │ │ │ │ └── last-build.bin │ │ │ ├── fileHashes │ │ │ │ └── fileHashes.lock │ │ │ └── executionHistory │ │ │ │ └── executionHistory.lock │ │ ├── vcs-1 │ │ │ └── gc.properties │ │ ├── buildOutputCleanup │ │ │ ├── cache.properties │ │ │ └── buildOutputCleanup.lock │ │ └── checksums │ │ │ └── checksums.lock │ ├── settings.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── .gitattributes │ ├── config │ │ └── checkstyle │ │ │ ├── suppressions.xml │ │ │ └── checkstyle.xml │ ├── docker │ │ └── docker-entrypoint.sh │ ├── Dockerfile │ ├── build.gradle │ ├── src │ │ ├── test │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── hyperledger │ │ │ │ └── fabric │ │ │ │ └── samples │ │ │ │ └── assettransfer │ │ │ │ ├── AssetTest.java │ │ │ │ └── AssetTransferTest.java │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ └── hyperledger │ │ │ └── fabric │ │ │ └── samples │ │ │ └── assettransfer │ │ │ ├── Asset.java │ │ │ └── AssetTransfer.java │ ├── gradlew.bat │ └── gradlew └── ccaas_pkg.sh ├── _docs ├── CCAASDebug.png ├── vscode-001.png ├── vscode-002.png ├── vscode-debug.png ├── vscode-debug-step.png └── Windows-WSL2-setup.md ├── .vscode └── launch.json ├── infrastructure └── dev-microfab │ └── oneorg.sh ├── LICENSE └── README.md /contracts/asset-transfer-ts/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contracts/chaincode-java/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/6.5.1/gc.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/vcs-1/gc.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/6.5.1/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | _tmp 3 | lib 4 | coverage 5 | .nyc_output 6 | -------------------------------------------------------------------------------- /_docs/CCAASDebug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/_docs/CCAASDebug.png -------------------------------------------------------------------------------- /_docs/vscode-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/_docs/vscode-001.png -------------------------------------------------------------------------------- /_docs/vscode-002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/_docs/vscode-002.png -------------------------------------------------------------------------------- /_docs/vscode-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/_docs/vscode-debug.png -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 15 13:53:26 GMT 2021 2 | gradle.version=6.5.1 3 | -------------------------------------------------------------------------------- /_docs/vscode-debug-step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/_docs/vscode-debug-step.png -------------------------------------------------------------------------------- /contracts/chaincode-java/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | rootProject.name = 'basic' 6 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export { AssetTransferContract } from './assetTransfer'; 2 | export declare const contracts: any[]; 3 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/checksums/checksums.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/contracts/chaincode-java/.gradle/checksums/checksums.lock -------------------------------------------------------------------------------- /contracts/chaincode-java/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/contracts/chaincode-java/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/6.5.1/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/contracts/chaincode-java/.gradle/6.5.1/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /contracts/chaincode-java/.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/contracts/chaincode-java/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /contracts/chaincode-java/.gradle/6.5.1/executionHistory/executionHistory.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/fabric-contract-debug-scenario/main/contracts/chaincode-java/.gradle/6.5.1/executionHistory/executionHistory.lock -------------------------------------------------------------------------------- /_docs/Windows-WSL2-setup.md: -------------------------------------------------------------------------------- 1 | # Windows with WSL2 2 | 3 | Install WSl2 with Linux distro 4 | Install docker within wsl2 5 | 6 | VSCode installed in windows and use the remote development features to WSL2 7 | 8 | Install the extension within the WSL2 environment -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | 4 | # don't lint build output (make sure it's set to your correct build folder name) 5 | dist 6 | 7 | # don't lint nyc coverage output 8 | coverage 9 | 10 | facade-14 -------------------------------------------------------------------------------- /contracts/chaincode-java/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | import { AssetTransferContract } from './assetTransfer'; 6 | 7 | export { AssetTransferContract } from './assetTransfer'; 8 | 9 | export const contracts: any[] = [AssetTransferContract]; 10 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/target/npmlist.json: -------------------------------------------------------------------------------- 1 | {"version":"1.0.0","name":"asset-transfer-basic","dependencies":{"fabric-contract-api":{"version":"2.4.1"},"fabric-shim":{"version":"2.4.1"},"json-stringify-deterministic":{"version":"1.0.2"},"sort-keys-recursive":{"version":"2.1.2"},"source-map-support":{"version":"0.5.19"}}} -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/dist/asset.d.ts: -------------------------------------------------------------------------------- 1 | export declare class Asset { 2 | docType?: string; 3 | id: string; 4 | color: string; 5 | size: number; 6 | owner: string; 7 | appraisedValue: number; 8 | constructor(id: string, color: string, size: number, owner: string, appraisedValue: number); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/.editorconfig: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /contracts/chaincode-java/config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/.npmignore: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | 5 | # don't package the connection details 6 | local_fabric 7 | 8 | # don't package the tests 9 | test 10 | functionalTests 11 | 12 | # don't package config files 13 | .vscode 14 | .editorconfig 15 | .eslintignore 16 | .eslintrc.js 17 | .gitignore 18 | .npmignore 19 | .nyc_output 20 | coverage 21 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | set -euo pipefail 6 | : ${CORE_PEER_TLS_ENABLED:="false"} 7 | : ${DEBUG:="false"} 8 | 9 | if [ "${DEBUG,,}" = "true" ]; then 10 | npm run start:server-debug 11 | elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then 12 | npm run start:server 13 | else 14 | npm run start:server-nontls 15 | fi 16 | 17 | -------------------------------------------------------------------------------- /contracts/chaincode-java/docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | set -euo pipefail 6 | : ${CORE_PEER_TLS_ENABLED:="false"} 7 | : ${DEBUG:="false"} 8 | 9 | if [ "${DEBUG,,}" = "true" ]; then 10 | java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar 11 | elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then 12 | java -jar /chaincode.jar # todo 13 | else 14 | java -jar /chaincode.jar 15 | fi 16 | 17 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach by Process ID", 9 | "processId": "${command:PickProcess}", 10 | "request": "attach", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "type": "pwa-node" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach by Process ID", 9 | "processId": "${command:PickProcess}", 10 | "request": "attach", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "type": "pwa-node" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /infrastructure/dev-microfab/oneorg.sh: -------------------------------------------------------------------------------- 1 | export MICROFAB_CONFIG='{ 2 | "endorsing_organizations":[ 3 | { 4 | "name": "DigiBank" 5 | } 6 | ], 7 | "channels":[ 8 | { 9 | "name": "assetnet", 10 | "endorsing_organizations":[ 11 | "DigiBank" 12 | ] 13 | } 14 | ], 15 | "capability_level":"V2_0" 16 | }' 17 | 18 | LOGGING_SPEC=debug 19 | docker run --name microfab --rm -ti -p2005:2005 -p 8080:8080 -e MICROFAB_CONFIG="${MICROFAB_CONFIG}" -e FABRIC_LOGGING_SPEC=$LOGGING_SPEC ibmcom/ibp-microfab-rc 20 | 21 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/dist/assetTransfer.d.ts: -------------------------------------------------------------------------------- 1 | import { Context, Contract } from 'fabric-contract-api'; 2 | export declare class AssetTransferContract extends Contract { 3 | InitLedger(ctx: Context): Promise; 4 | CreateAsset(ctx: Context, id: string, color: string, size: number, owner: string, appraisedValue: number): Promise; 5 | ReadAsset(ctx: Context, id: string): Promise; 6 | UpdateAsset(ctx: Context, id: string, color: string, size: number, owner: string, appraisedValue: number): Promise; 7 | DeleteAsset(ctx: Context, id: string): Promise; 8 | AssetExists(ctx: Context, id: string): Promise; 9 | TransferAsset(ctx: Context, id: string, newowner: string): Promise; 10 | GetAllAssets(ctx: Context): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 6 | ], 7 | parserOptions: { 8 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 9 | sourceType: 'module', // Allows for the use of imports 10 | }, 11 | rules: { 12 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 13 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/src/asset.ts: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | import { Object, Property } from 'fabric-contract-api'; 6 | 7 | @Object() 8 | export class Asset { 9 | @Property() 10 | public docType?: string; 11 | 12 | @Property() 13 | public id: string; 14 | 15 | @Property() 16 | public color: string; 17 | 18 | @Property() 19 | public size: number; 20 | 21 | @Property() 22 | public owner: string; 23 | 24 | @Property() 25 | public appraisedValue: number; 26 | 27 | public constructor(id: string, color: string, size: number, owner: string, appraisedValue: number) { 28 | this.docType = 'Asset'; 29 | this.id = id; 30 | this.color = color; 31 | this.size = size; 32 | this.owner = owner; 33 | this.appraisedValue = appraisedValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext", 8 | "es2017.object" 9 | ], 10 | "target": "es2015", 11 | "outDir": "./dist", 12 | "removeComments": true, 13 | "inlineSourceMap": true, 14 | "inlineSources": true, 15 | "preserveConstEnums": true, 16 | "experimentalDecorators": true, 17 | "emitDecoratorMetadata": true, 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "esModuleInterop": true, 24 | 25 | }, 26 | "include": [ 27 | "src/**/*" 28 | ], 29 | "exclude": [ 30 | "node_modules", 31 | "**/*-spec.ts" 32 | ] 33 | } -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | FROM node:16 AS builder 5 | 6 | WORKDIR /usr/src/app 7 | 8 | # Copy node.js source and build, changing owner as well 9 | COPY --chown=node:node . /usr/src/app 10 | RUN npm ci && npm run build && npm shrinkwrap 11 | 12 | FROM node:16 AS production 13 | 14 | # Setup tini to work better handle signals 15 | ENV TINI_VERSION v0.19.0 16 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 17 | RUN chmod +x /tini 18 | 19 | 20 | WORKDIR /usr/src/app 21 | COPY --chown=node:node --from=builder /usr/src/app/dist ./dist 22 | COPY --chown=node:node --from=builder /usr/src/app/package.json ./ 23 | COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./ 24 | COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh 25 | RUN npm ci --only=production 26 | 27 | ENV PORT 9999 28 | EXPOSE 9999 29 | ENV NODE_ENV=production 30 | 31 | USER node 32 | ENTRYPOINT [ "/tini", "--", "/usr/src/app/docker-entrypoint.sh" ] 33 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.contracts = exports.AssetTransferContract = void 0; 4 | const assetTransfer_1 = require("./assetTransfer"); 5 | var assetTransfer_2 = require("./assetTransfer"); 6 | Object.defineProperty(exports, "AssetTransferContract", { enumerable: true, get: function () { return assetTransfer_2.AssetTransferContract; } }); 7 | exports.contracts = [assetTransfer_1.AssetTransferContract]; 8 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBSUEsbURBQXdEO0FBRXhELGlEQUF3RDtBQUEvQyxzSEFBQSxxQkFBcUIsT0FBQTtBQUVqQixRQUFBLFNBQVMsR0FBVSxDQUFDLHFDQUFxQixDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcbiAqL1xuXG5pbXBvcnQgeyBBc3NldFRyYW5zZmVyQ29udHJhY3QgfSBmcm9tICcuL2Fzc2V0VHJhbnNmZXInO1xuXG5leHBvcnQgeyBBc3NldFRyYW5zZmVyQ29udHJhY3QgfSBmcm9tICcuL2Fzc2V0VHJhbnNmZXInO1xuXG5leHBvcnQgY29uc3QgY29udHJhY3RzOiBhbnlbXSA9IFtBc3NldFRyYW5zZmVyQ29udHJhY3RdO1xuIl19 -------------------------------------------------------------------------------- /contracts/chaincode-java/Dockerfile: -------------------------------------------------------------------------------- 1 | # the first stage 2 | FROM gradle:jdk11 AS GRADLE_BUILD 3 | 4 | # copy the build.gradle and src code to the container 5 | COPY src/ src/ 6 | COPY build.gradle ./ 7 | 8 | # Build and package our code 9 | RUN gradle --no-daemon build shadowJar -x checkstyleMain -x checkstyleTest 10 | 11 | 12 | # the second stage of our build just needs the compiled files 13 | FROM openjdk:11-jre 14 | ARG CC_SERVER_PORT=9999 15 | 16 | # Setup tini to work better handle signals 17 | ENV TINI_VERSION v0.19.0 18 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 19 | RUN chmod +x /tini 20 | 21 | RUN addgroup --system javauser && useradd -g javauser javauser 22 | 23 | # copy only the artifacts we need from the first stage and discard the rest 24 | COPY --chown=javauser:javauser --from=GRADLE_BUILD /home/gradle/build/libs/chaincode.jar /chaincode.jar 25 | COPY --chown=javauser:javauser docker/docker-entrypoint.sh /docker-entrypoint.sh 26 | 27 | ENV PORT $CC_SERVER_PORT 28 | EXPOSE $CC_SERVER_PORT 29 | 30 | USER javauser 31 | ENTRYPOINT [ "/tini", "--", "/docker-entrypoint.sh" ] 32 | -------------------------------------------------------------------------------- /contracts/ccaas_pkg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | 7 | set -eo pipefail 8 | 9 | # Where am I? 10 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/." && pwd )" 11 | 12 | : ${CC_NAME:="assettransfer"} 13 | : ${CCAAS_SERVER_PORT:=9999} 14 | : ${CC_VERSION:=1} 15 | 16 | 17 | packageChaincode() { 18 | 19 | # address="{{.peername}}_${CC_NAME}_ccaas:${CCAAS_SERVER_PORT}" 20 | address="172.17.0.1:${CCAAS_SERVER_PORT}" 21 | prefix=$(basename "$0") 22 | tempdir=$(mktemp -d -t "$prefix.XXXXXXXX") || error_exit "Error creating temporary directory" 23 | label=${CC_NAME}_${CC_VERSION} 24 | mkdir -p "$tempdir/src" 25 | 26 | cat > "$tempdir/src/connection.json" < "$tempdir/pkg/metadata.json" 37 | { 38 | "type": "ccaas", 39 | "label": "$label" 40 | } 41 | METADATA-EOF 42 | 43 | tar -C "$tempdir/src" -czf "$tempdir/pkg/code.tar.gz" . 44 | tar -C "$tempdir/pkg" -czf "$CC_NAME.tar.gz" metadata.json code.tar.gz 45 | rm -Rf "$tempdir" 46 | 47 | ls -l "$CC_NAME.tar.gz" 48 | } 49 | 50 | getPackageId() { 51 | local cc_package="$CC_NAME.tar.gz" 52 | cc_sha256=$(shasum -a 256 ${cc_package} | tr -s ' ' | cut -d ' ' -f 1) 53 | 54 | label=${CC_NAME}_${CC_VERSION} 55 | CHAINCODE_ID=${label}:${cc_sha256} 56 | echo "CHAINCODE_ID=${CHAINCODE_ID}" 57 | } 58 | 59 | 60 | packageChaincode 61 | getPackageId 62 | -------------------------------------------------------------------------------- /contracts/chaincode-java/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | plugins { 6 | id 'com.github.johnrengelman.shadow' version '5.1.0' 7 | id 'application' 8 | id 'checkstyle' 9 | id 'jacoco' 10 | } 11 | 12 | group 'org.hyperledger.fabric.samples' 13 | version '1.0-SNAPSHOT' 14 | 15 | dependencies { 16 | 17 | implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.4.1' 18 | implementation 'org.json:json:+' 19 | implementation 'com.owlike:genson:1.5' 20 | testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.4.1' 21 | testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' 22 | testImplementation 'org.assertj:assertj-core:3.11.1' 23 | testImplementation 'org.mockito:mockito-core:2.+' 24 | } 25 | 26 | repositories { 27 | mavenCentral() 28 | maven { 29 | url 'https://jitpack.io' 30 | } 31 | } 32 | 33 | application { 34 | mainClass = 'org.hyperledger.fabric.contract.ContractRouter' 35 | } 36 | 37 | checkstyle { 38 | toolVersion '8.21' 39 | configFile file("config/checkstyle/checkstyle.xml") 40 | } 41 | 42 | checkstyleMain { 43 | source ='src/main/java' 44 | } 45 | 46 | checkstyleTest { 47 | source ='src/test/java' 48 | } 49 | 50 | jacocoTestReport { 51 | dependsOn test 52 | } 53 | 54 | jacocoTestCoverageVerification { 55 | violationRules { 56 | rule { 57 | limit { 58 | minimum = 0.9 59 | } 60 | } 61 | } 62 | 63 | finalizedBy jacocoTestReport 64 | } 65 | 66 | test { 67 | useJUnitPlatform() 68 | testLogging { 69 | events "passed", "skipped", "failed" 70 | } 71 | } 72 | 73 | mainClassName = 'org.hyperledger.fabric.contract.ContractRouter' 74 | 75 | shadowJar { 76 | baseName = 'chaincode' 77 | version = null 78 | classifier = null 79 | 80 | manifest { 81 | attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter' 82 | } 83 | } 84 | 85 | check.dependsOn jacocoTestCoverageVerification 86 | installDist.dependsOn check 87 | -------------------------------------------------------------------------------- /contracts/chaincode-java/src/test/java/org/hyperledger/fabric/samples/assettransfer/AssetTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | package org.hyperledger.fabric.samples.assettransfer; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | import org.junit.jupiter.api.Nested; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public final class AssetTest { 13 | 14 | @Nested 15 | class Equality { 16 | 17 | @Test 18 | public void isReflexive() { 19 | Asset asset = new Asset("asset1", "Blue", 20, "Guy", 100); 20 | 21 | assertThat(asset).isEqualTo(asset); 22 | } 23 | 24 | @Test 25 | public void isSymmetric() { 26 | Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100); 27 | Asset assetB = new Asset("asset1", "Blue", 20, "Guy", 100); 28 | 29 | assertThat(assetA).isEqualTo(assetB); 30 | assertThat(assetB).isEqualTo(assetA); 31 | } 32 | 33 | @Test 34 | public void isTransitive() { 35 | Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100); 36 | Asset assetB = new Asset("asset1", "Blue", 20, "Guy", 100); 37 | Asset assetC = new Asset("asset1", "Blue", 20, "Guy", 100); 38 | 39 | assertThat(assetA).isEqualTo(assetB); 40 | assertThat(assetB).isEqualTo(assetC); 41 | assertThat(assetA).isEqualTo(assetC); 42 | } 43 | 44 | @Test 45 | public void handlesInequality() { 46 | Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100); 47 | Asset assetB = new Asset("asset2", "Red", 40, "Lady", 200); 48 | 49 | assertThat(assetA).isNotEqualTo(assetB); 50 | } 51 | 52 | @Test 53 | public void handlesOtherObjects() { 54 | Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100); 55 | String assetB = "not a asset"; 56 | 57 | assertThat(assetA).isNotEqualTo(assetB); 58 | } 59 | 60 | @Test 61 | public void handlesNull() { 62 | Asset asset = new Asset("asset1", "Blue", 20, "Guy", 100); 63 | 64 | assertThat(asset).isNotEqualTo(null); 65 | } 66 | } 67 | 68 | @Test 69 | public void toStringIdentifiesAsset() { 70 | Asset asset = new Asset("asset1", "Blue", 20, "Guy", 100); 71 | 72 | assertThat(asset.toString()).isEqualTo("Asset@e04f6c53 [assetID=asset1, color=Blue, size=20, owner=Guy, appraisedValue=100]"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/Asset.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | package org.hyperledger.fabric.samples.assettransfer; 6 | 7 | import java.util.Objects; 8 | 9 | import org.hyperledger.fabric.contract.annotation.DataType; 10 | import org.hyperledger.fabric.contract.annotation.Property; 11 | 12 | import com.owlike.genson.annotation.JsonProperty; 13 | 14 | @DataType() 15 | public final class Asset { 16 | 17 | @Property() 18 | private final String assetID; 19 | 20 | @Property() 21 | private final String color; 22 | 23 | @Property() 24 | private final int size; 25 | 26 | @Property() 27 | private final String owner; 28 | 29 | @Property() 30 | private final int appraisedValue; 31 | 32 | public String getAssetID() { 33 | return assetID; 34 | } 35 | 36 | public String getColor() { 37 | return color; 38 | } 39 | 40 | public int getSize() { 41 | return size; 42 | } 43 | 44 | public String getOwner() { 45 | return owner; 46 | } 47 | 48 | public int getAppraisedValue() { 49 | return appraisedValue; 50 | } 51 | 52 | public Asset(@JsonProperty("assetID") final String assetID, @JsonProperty("color") final String color, 53 | @JsonProperty("size") final int size, @JsonProperty("owner") final String owner, 54 | @JsonProperty("appraisedValue") final int appraisedValue) { 55 | this.assetID = assetID; 56 | this.color = color; 57 | this.size = size; 58 | this.owner = owner; 59 | this.appraisedValue = appraisedValue; 60 | } 61 | 62 | @Override 63 | public boolean equals(final Object obj) { 64 | if (this == obj) { 65 | return true; 66 | } 67 | 68 | if ((obj == null) || (getClass() != obj.getClass())) { 69 | return false; 70 | } 71 | 72 | Asset other = (Asset) obj; 73 | 74 | return Objects.deepEquals( 75 | new String[] {getAssetID(), getColor(), getOwner()}, 76 | new String[] {other.getAssetID(), other.getColor(), other.getOwner()}) 77 | && 78 | Objects.deepEquals( 79 | new int[] {getSize(), getAppraisedValue()}, 80 | new int[] {other.getSize(), other.getAppraisedValue()}); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Objects.hash(getAssetID(), getColor(), getSize(), getOwner(), getAppraisedValue()); 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return this.getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + " [assetID=" + assetID + ", color=" 91 | + color + ", size=" + size + ", owner=" + owner + ", appraisedValue=" + appraisedValue + "]"; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asset-transfer-basic", 3 | "version": "1.0.0", 4 | "description": "Asset Transfer Basic contract implemented in TypeScript", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "lib/**/*" 9 | ], 10 | "engines": { 11 | "node": ">=12.15.0" 12 | }, 13 | "fabric":{ 14 | "files": [ 15 | "lib/**/*" 16 | ], 17 | "label":"asset-transfer-basic" 18 | }, 19 | "scripts": { 20 | "clean": "rimraf lib", 21 | "test": "nyc mocha -r ts-node/register src/**/*.spec.ts", 22 | "meta": "fabric-chaincode-node metadata generate", 23 | "format": "prettier --write \"{src,test}/**/*.ts\"", 24 | "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix", 25 | "build": "npm run format && npm run clean && npm run lint && echo Using TypeScript && tsc --version && tsc --pretty", 26 | "package:cc": "../ccaas_pkg.sh", 27 | "package:image": "npm run build && npm shrinkwrap && docker build -f ./Dockerfile -t asset-transfer-basic .", 28 | "release": "standard-version", 29 | "start": "fabric-chaincode-node start", 30 | "start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID", 31 | "start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem", 32 | "start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID" 33 | }, 34 | "engineStrict": true, 35 | "author": "whitemat@uk.ibm.com", 36 | "license": "Apache-2.0", 37 | "dependencies": { 38 | "fabric-contract-api": "^2.4.0", 39 | "fabric-shim": "^2.4.0", 40 | "source-map-support": "^0.5.19", 41 | "json-stringify-deterministic": "^1.0.1", 42 | "sort-keys-recursive": "^2.1.2" 43 | }, 44 | "devDependencies": { 45 | "@hyperledgendary/weftility": "^1.0.1-beta.0", 46 | "@types/mkdirp": "^1.0.1", 47 | "@types/node": "^14.0.11", 48 | "@types/rimraf": "^3.0.0", 49 | "@types/yargs": "^15.0.3", 50 | "@typescript-eslint/eslint-plugin": "^4.3.0", 51 | "@typescript-eslint/parser": "^4.3.0", 52 | "chai": "^4.2.0", 53 | "chai-as-promised": "^7.1.1", 54 | "eslint": "^7.2.0", 55 | "eslint-config-prettier": "^6.11.0", 56 | "eslint-plugin-prettier": "^3.1.3", 57 | "mocha": "^7.1.1", 58 | "nyc": "^15.0.0", 59 | "prettier": "^2.0.5", 60 | "sinon": "^9.0.1", 61 | "sinon-chai": "^3.5.0", 62 | "standard-version": "^9.0.0", 63 | "typescript": "^4.0.3" 64 | }, 65 | "nyc": { 66 | "exclude": [ 67 | ".eslintrc.js", 68 | "coverage/**", 69 | "test/**" 70 | ], 71 | "reporter": [ 72 | "text-summary", 73 | "html" 74 | ], 75 | "all": true, 76 | "check-coverage": true, 77 | "statements": 100, 78 | "branches": 100, 79 | "functions": 100, 80 | "lines": 100 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/chaincode-java/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/dist/asset.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.Asset = void 0; 13 | const fabric_contract_api_1 = require("fabric-contract-api"); 14 | let Asset = class Asset { 15 | constructor(id, color, size, owner, appraisedValue) { 16 | this.docType = 'Asset'; 17 | this.id = id; 18 | this.color = color; 19 | this.size = size; 20 | this.owner = owner; 21 | this.appraisedValue = appraisedValue; 22 | } 23 | }; 24 | __decorate([ 25 | fabric_contract_api_1.Property(), 26 | __metadata("design:type", String) 27 | ], Asset.prototype, "docType", void 0); 28 | __decorate([ 29 | fabric_contract_api_1.Property(), 30 | __metadata("design:type", String) 31 | ], Asset.prototype, "id", void 0); 32 | __decorate([ 33 | fabric_contract_api_1.Property(), 34 | __metadata("design:type", String) 35 | ], Asset.prototype, "color", void 0); 36 | __decorate([ 37 | fabric_contract_api_1.Property(), 38 | __metadata("design:type", Number) 39 | ], Asset.prototype, "size", void 0); 40 | __decorate([ 41 | fabric_contract_api_1.Property(), 42 | __metadata("design:type", String) 43 | ], Asset.prototype, "owner", void 0); 44 | __decorate([ 45 | fabric_contract_api_1.Property(), 46 | __metadata("design:type", Number) 47 | ], Asset.prototype, "appraisedValue", void 0); 48 | Asset = __decorate([ 49 | fabric_contract_api_1.Object(), 50 | __metadata("design:paramtypes", [String, String, Number, String, Number]) 51 | ], Asset); 52 | exports.Asset = Asset; 53 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvYXNzZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBSUEsNkRBQXVEO0FBR3ZELElBQWEsS0FBSyxHQUFsQixNQUFhLEtBQUs7SUFtQmQsWUFBbUIsRUFBVSxFQUFFLEtBQWEsRUFBRSxJQUFZLEVBQUUsS0FBYSxFQUFFLGNBQXNCO1FBQzdGLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO1FBQ2IsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7UUFDbkIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7UUFDbkIsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7SUFDekMsQ0FBQztDQUNKLENBQUE7QUF6Qkc7SUFEQyw4QkFBUSxFQUFFOztzQ0FDYTtBQUd4QjtJQURDLDhCQUFRLEVBQUU7O2lDQUNPO0FBR2xCO0lBREMsOEJBQVEsRUFBRTs7b0NBQ1U7QUFHckI7SUFEQyw4QkFBUSxFQUFFOzttQ0FDUztBQUdwQjtJQURDLDhCQUFRLEVBQUU7O29DQUNVO0FBR3JCO0lBREMsOEJBQVEsRUFBRTs7NkNBQ21CO0FBakJyQixLQUFLO0lBRGpCLDRCQUFNLEVBQUU7O0dBQ0ksS0FBSyxDQTJCakI7QUEzQlksc0JBQUsiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuKi9cblxuaW1wb3J0IHsgT2JqZWN0LCBQcm9wZXJ0eSB9IGZyb20gJ2ZhYnJpYy1jb250cmFjdC1hcGknO1xuXG5AT2JqZWN0KClcbmV4cG9ydCBjbGFzcyBBc3NldCB7XG4gICAgQFByb3BlcnR5KClcbiAgICBwdWJsaWMgZG9jVHlwZT86IHN0cmluZztcblxuICAgIEBQcm9wZXJ0eSgpXG4gICAgcHVibGljIGlkOiBzdHJpbmc7XG5cbiAgICBAUHJvcGVydHkoKVxuICAgIHB1YmxpYyBjb2xvcjogc3RyaW5nO1xuXG4gICAgQFByb3BlcnR5KClcbiAgICBwdWJsaWMgc2l6ZTogbnVtYmVyO1xuXG4gICAgQFByb3BlcnR5KClcbiAgICBwdWJsaWMgb3duZXI6IHN0cmluZztcblxuICAgIEBQcm9wZXJ0eSgpXG4gICAgcHVibGljIGFwcHJhaXNlZFZhbHVlOiBudW1iZXI7XG5cbiAgICBwdWJsaWMgY29uc3RydWN0b3IoaWQ6IHN0cmluZywgY29sb3I6IHN0cmluZywgc2l6ZTogbnVtYmVyLCBvd25lcjogc3RyaW5nLCBhcHByYWlzZWRWYWx1ZTogbnVtYmVyKSB7XG4gICAgICAgIHRoaXMuZG9jVHlwZSA9ICdBc3NldCc7XG4gICAgICAgIHRoaXMuaWQgPSBpZDtcbiAgICAgICAgdGhpcy5jb2xvciA9IGNvbG9yO1xuICAgICAgICB0aGlzLnNpemUgPSBzaXplO1xuICAgICAgICB0aGlzLm93bmVyID0gb3duZXI7XG4gICAgICAgIHRoaXMuYXBwcmFpc2VkVmFsdWUgPSBhcHByYWlzZWRWYWx1ZTtcbiAgICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/src/assetTransfer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | import { Context, Contract, Info, Returns, Transaction } from 'fabric-contract-api'; 6 | import { Asset } from './asset'; 7 | 8 | @Info({ title: 'AssetTransfer', description: 'Smart contract for trading assets' }) 9 | export class AssetTransferContract extends Contract { 10 | @Transaction() 11 | public async InitLedger(ctx: Context): Promise { 12 | const assets: Asset[] = [ 13 | { 14 | id: 'asset1', 15 | color: 'blue', 16 | size: 5, 17 | owner: 'Tomoko', 18 | appraisedValue: 300, 19 | }, 20 | { 21 | id: 'asset2', 22 | color: 'red', 23 | size: 5, 24 | owner: 'Brad', 25 | appraisedValue: 400, 26 | }, 27 | { 28 | id: 'asset3', 29 | color: 'green', 30 | size: 10, 31 | owner: 'Jin Soo', 32 | appraisedValue: 500, 33 | }, 34 | { 35 | id: 'asset4', 36 | color: 'yellow', 37 | size: 10, 38 | owner: 'Max', 39 | appraisedValue: 600, 40 | }, 41 | { 42 | id: 'asset5', 43 | color: 'black', 44 | size: 15, 45 | owner: 'Adriana', 46 | appraisedValue: 700, 47 | }, 48 | { 49 | id: 'asset6', 50 | color: 'white', 51 | size: 15, 52 | owner: 'Michel', 53 | appraisedValue: 800, 54 | }, 55 | ]; 56 | 57 | for (const asset of assets) { 58 | asset.docType = 'asset'; 59 | await ctx.stub.putState(asset.id, Buffer.from(JSON.stringify(asset))); 60 | console.info(`Asset ${asset.id} initialized`); 61 | } 62 | } 63 | 64 | // CreateAsset issues a new asset to the world state with given details. 65 | @Transaction() 66 | public async CreateAsset( 67 | ctx: Context, 68 | id: string, 69 | color: string, 70 | size: number, 71 | owner: string, 72 | appraisedValue: number, 73 | ): Promise { 74 | const asset = { 75 | id: id, 76 | color: color, 77 | size: size, 78 | owner: owner, 79 | appraisedValue: appraisedValue, 80 | }; 81 | await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); 82 | } 83 | 84 | // ReadAsset returns the asset stored in the world state with given id. 85 | @Transaction(false) 86 | public async ReadAsset(ctx: Context, id: string): Promise { 87 | const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state 88 | if (!assetJSON || assetJSON.length === 0) { 89 | throw new Error(`The asset ${id} does not exist`); 90 | } 91 | return assetJSON.toString(); 92 | } 93 | 94 | // UpdateAsset updates an existing asset in the world state with provided parameters. 95 | @Transaction() 96 | public async UpdateAsset( 97 | ctx: Context, 98 | id: string, 99 | color: string, 100 | size: number, 101 | owner: string, 102 | appraisedValue: number, 103 | ): Promise { 104 | const exists = await this.AssetExists(ctx, id); 105 | if (!exists) { 106 | throw new Error(`The asset ${id} does not exist`); 107 | } 108 | 109 | // overwriting original asset with new asset 110 | const updatedAsset = { 111 | id: id, 112 | color: color, 113 | size: size, 114 | owner: owner, 115 | appraisedValue: appraisedValue, 116 | }; 117 | return ctx.stub.putState(id, Buffer.from(JSON.stringify(updatedAsset))); 118 | } 119 | 120 | // DeleteAsset deletes an given asset from the world state. 121 | @Transaction() 122 | public async DeleteAsset(ctx: Context, id: string): Promise { 123 | const exists = await this.AssetExists(ctx, id); 124 | if (!exists) { 125 | throw new Error(`The asset ${id} does not exist`); 126 | } 127 | return ctx.stub.deleteState(id); 128 | } 129 | 130 | // AssetExists returns true when asset with given id exists in world state. 131 | @Transaction(false) 132 | @Returns('boolean') 133 | public async AssetExists(ctx: Context, id: string): Promise { 134 | const assetJSON = await ctx.stub.getState(id); 135 | return assetJSON && assetJSON.length > 0; 136 | } 137 | 138 | // TransferAsset updates the owner field of asset with given id in the world state. 139 | @Transaction() 140 | public async TransferAsset(ctx: Context, id: string, newowner: string): Promise { 141 | const assetString = await this.ReadAsset(ctx, id); 142 | const asset = JSON.parse(assetString); 143 | asset.owner = newowner; 144 | await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); 145 | } 146 | 147 | // GetAllAssets returns all assets found in the world state. 148 | @Transaction(false) 149 | @Returns('string') 150 | public async GetAllAssets(ctx: Context): Promise { 151 | const allResults = []; 152 | // range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace. 153 | const iterator = await ctx.stub.getStateByRange('', ''); 154 | let result = await iterator.next(); 155 | while (!result.done) { 156 | const strValue = Buffer.from(result.value.value.toString()).toString('utf8'); 157 | let record; 158 | try { 159 | record = JSON.parse(strValue); 160 | } catch (err) { 161 | console.log(err); 162 | record = strValue; 163 | } 164 | allResults.push({ Key: result.value.key, Record: record }); 165 | result = await iterator.next(); 166 | } 167 | return JSON.stringify(allResults); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /contracts/chaincode-java/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /contracts/chaincode-java/config/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /contracts/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | package org.hyperledger.fabric.samples.assettransfer; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | 11 | import org.hyperledger.fabric.contract.Context; 12 | import org.hyperledger.fabric.contract.ContractInterface; 13 | import org.hyperledger.fabric.contract.annotation.Contact; 14 | import org.hyperledger.fabric.contract.annotation.Contract; 15 | import org.hyperledger.fabric.contract.annotation.Default; 16 | import org.hyperledger.fabric.contract.annotation.Info; 17 | import org.hyperledger.fabric.contract.annotation.License; 18 | import org.hyperledger.fabric.contract.annotation.Transaction; 19 | import org.hyperledger.fabric.shim.ChaincodeException; 20 | import org.hyperledger.fabric.shim.ChaincodeStub; 21 | import org.hyperledger.fabric.shim.ledger.KeyValue; 22 | import org.hyperledger.fabric.shim.ledger.QueryResultsIterator; 23 | 24 | import com.owlike.genson.Genson; 25 | 26 | @Contract( 27 | name = "basic", 28 | info = @Info( 29 | title = "Asset Transfer", 30 | description = "The hyperlegendary asset transfer", 31 | version = "0.0.1-SNAPSHOT", 32 | license = @License( 33 | name = "Apache 2.0 License", 34 | url = "http://www.apache.org/licenses/LICENSE-2.0.html"), 35 | contact = @Contact( 36 | email = "a.transfer@example.com", 37 | name = "Adrian Transfer", 38 | url = "https://hyperledger.example.com"))) 39 | @Default 40 | public final class AssetTransfer implements ContractInterface { 41 | 42 | private final Genson genson = new Genson(); 43 | 44 | private enum AssetTransferErrors { 45 | ASSET_NOT_FOUND, 46 | ASSET_ALREADY_EXISTS 47 | } 48 | 49 | /** 50 | * Creates some initial assets on the ledger. 51 | * 52 | * @param ctx the transaction context 53 | */ 54 | @Transaction(intent = Transaction.TYPE.SUBMIT) 55 | public void InitLedger(final Context ctx) { 56 | ChaincodeStub stub = ctx.getStub(); 57 | 58 | CreateAsset(ctx, "asset1", "blue", 5, "Tomoko", 300); 59 | CreateAsset(ctx, "asset2", "red", 5, "Brad", 400); 60 | CreateAsset(ctx, "asset3", "green", 10, "Jin Soo", 500); 61 | CreateAsset(ctx, "asset4", "yellow", 10, "Max", 600); 62 | CreateAsset(ctx, "asset5", "black", 15, "Adrian", 700); 63 | CreateAsset(ctx, "asset6", "white", 15, "Michel", 700); 64 | 65 | } 66 | 67 | /** 68 | * Creates a new asset on the ledger. 69 | * 70 | * @param ctx the transaction context 71 | * @param assetID the ID of the new asset 72 | * @param color the color of the new asset 73 | * @param size the size for the new asset 74 | * @param owner the owner of the new asset 75 | * @param appraisedValue the appraisedValue of the new asset 76 | * @return the created asset 77 | */ 78 | @Transaction(intent = Transaction.TYPE.SUBMIT) 79 | public Asset CreateAsset(final Context ctx, final String assetID, final String color, final int size, 80 | final String owner, final int appraisedValue) { 81 | ChaincodeStub stub = ctx.getStub(); 82 | 83 | if (AssetExists(ctx, assetID)) { 84 | String errorMessage = String.format("Asset %s already exists", assetID); 85 | System.out.println(errorMessage); 86 | throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_ALREADY_EXISTS.toString()); 87 | } 88 | 89 | Asset asset = new Asset(assetID, color, size, owner, appraisedValue); 90 | //Use Genson to convert the Asset into string, sort it alphabetically and serialize it into a json string 91 | String sortedJson = genson.serialize(asset); 92 | stub.putStringState(assetID, sortedJson); 93 | 94 | return asset; 95 | } 96 | 97 | /** 98 | * Retrieves an asset with the specified ID from the ledger. 99 | * 100 | * @param ctx the transaction context 101 | * @param assetID the ID of the asset 102 | * @return the asset found on the ledger if there was one 103 | */ 104 | @Transaction(intent = Transaction.TYPE.EVALUATE) 105 | public Asset ReadAsset(final Context ctx, final String assetID) { 106 | ChaincodeStub stub = ctx.getStub(); 107 | String assetJSON = stub.getStringState(assetID); 108 | 109 | if (assetJSON == null || assetJSON.isEmpty()) { 110 | String errorMessage = String.format("Asset %s does not exist", assetID); 111 | System.out.println(errorMessage); 112 | throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); 113 | } 114 | 115 | Asset asset = genson.deserialize(assetJSON, Asset.class); 116 | return asset; 117 | } 118 | 119 | /** 120 | * Updates the properties of an asset on the ledger. 121 | * 122 | * @param ctx the transaction context 123 | * @param assetID the ID of the asset being updated 124 | * @param color the color of the asset being updated 125 | * @param size the size of the asset being updated 126 | * @param owner the owner of the asset being updated 127 | * @param appraisedValue the appraisedValue of the asset being updated 128 | * @return the transferred asset 129 | */ 130 | @Transaction(intent = Transaction.TYPE.SUBMIT) 131 | public Asset UpdateAsset(final Context ctx, final String assetID, final String color, final int size, 132 | final String owner, final int appraisedValue) { 133 | ChaincodeStub stub = ctx.getStub(); 134 | 135 | if (!AssetExists(ctx, assetID)) { 136 | String errorMessage = String.format("Asset %s does not exist", assetID); 137 | System.out.println(errorMessage); 138 | throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); 139 | } 140 | 141 | Asset newAsset = new Asset(assetID, color, size, owner, appraisedValue); 142 | //Use Genson to convert the Asset into string, sort it alphabetically and serialize it into a json string 143 | String sortedJson = genson.serialize(newAsset); 144 | stub.putStringState(assetID, sortedJson); 145 | return newAsset; 146 | } 147 | 148 | /** 149 | * Deletes asset on the ledger. 150 | * 151 | * @param ctx the transaction context 152 | * @param assetID the ID of the asset being deleted 153 | */ 154 | @Transaction(intent = Transaction.TYPE.SUBMIT) 155 | public void DeleteAsset(final Context ctx, final String assetID) { 156 | ChaincodeStub stub = ctx.getStub(); 157 | 158 | if (!AssetExists(ctx, assetID)) { 159 | String errorMessage = String.format("Asset %s does not exist", assetID); 160 | System.out.println(errorMessage); 161 | throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); 162 | } 163 | 164 | stub.delState(assetID); 165 | } 166 | 167 | /** 168 | * Checks the existence of the asset on the ledger 169 | * 170 | * @param ctx the transaction context 171 | * @param assetID the ID of the asset 172 | * @return boolean indicating the existence of the asset 173 | */ 174 | @Transaction(intent = Transaction.TYPE.EVALUATE) 175 | public boolean AssetExists(final Context ctx, final String assetID) { 176 | ChaincodeStub stub = ctx.getStub(); 177 | String assetJSON = stub.getStringState(assetID); 178 | 179 | return (assetJSON != null && !assetJSON.isEmpty()); 180 | } 181 | 182 | /** 183 | * Changes the owner of a asset on the ledger. 184 | * 185 | * @param ctx the transaction context 186 | * @param assetID the ID of the asset being transferred 187 | * @param newOwner the new owner 188 | * @return the old owner 189 | */ 190 | @Transaction(intent = Transaction.TYPE.SUBMIT) 191 | public String TransferAsset(final Context ctx, final String assetID, final String newOwner) { 192 | ChaincodeStub stub = ctx.getStub(); 193 | String assetJSON = stub.getStringState(assetID); 194 | 195 | if (assetJSON == null || assetJSON.isEmpty()) { 196 | String errorMessage = String.format("Asset %s does not exist", assetID); 197 | System.out.println(errorMessage); 198 | throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); 199 | } 200 | 201 | Asset asset = genson.deserialize(assetJSON, Asset.class); 202 | 203 | Asset newAsset = new Asset(asset.getAssetID(), asset.getColor(), asset.getSize(), newOwner, asset.getAppraisedValue()); 204 | //Use a Genson to conver the Asset into string, sort it alphabetically and serialize it into a json string 205 | String sortedJson = genson.serialize(newAsset); 206 | stub.putStringState(assetID, sortedJson); 207 | 208 | return asset.getOwner(); 209 | } 210 | 211 | /** 212 | * Retrieves all assets from the ledger. 213 | * 214 | * @param ctx the transaction context 215 | * @return array of assets found on the ledger 216 | */ 217 | @Transaction(intent = Transaction.TYPE.EVALUATE) 218 | public String GetAllAssets(final Context ctx) { 219 | ChaincodeStub stub = ctx.getStub(); 220 | 221 | List queryResults = new ArrayList(); 222 | 223 | // To retrieve all assets from the ledger use getStateByRange with empty startKey & endKey. 224 | // Giving empty startKey & endKey is interpreted as all the keys from beginning to end. 225 | // As another example, if you use startKey = 'asset0', endKey = 'asset9' , 226 | // then getStateByRange will retrieve asset with keys between asset0 (inclusive) and asset9 (exclusive) in lexical order. 227 | QueryResultsIterator results = stub.getStateByRange("", ""); 228 | 229 | for (KeyValue result: results) { 230 | Asset asset = genson.deserialize(result.getStringValue(), Asset.class); 231 | System.out.println(asset); 232 | queryResults.add(asset); 233 | } 234 | 235 | final String response = genson.serialize(queryResults); 236 | 237 | return response; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /contracts/chaincode-java/src/test/java/org/hyperledger/fabric/samples/assettransfer/AssetTransferTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | package org.hyperledger.fabric.samples.assettransfer; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.assertj.core.api.ThrowableAssert.catchThrowable; 9 | import static org.mockito.Mockito.inOrder; 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.verifyZeroInteractions; 12 | import static org.mockito.Mockito.when; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Iterator; 16 | import java.util.List; 17 | 18 | import org.hyperledger.fabric.contract.Context; 19 | import org.hyperledger.fabric.shim.ChaincodeException; 20 | import org.hyperledger.fabric.shim.ChaincodeStub; 21 | import org.hyperledger.fabric.shim.ledger.KeyValue; 22 | import org.hyperledger.fabric.shim.ledger.QueryResultsIterator; 23 | import org.junit.jupiter.api.Nested; 24 | import org.junit.jupiter.api.Test; 25 | import org.mockito.InOrder; 26 | 27 | public final class AssetTransferTest { 28 | 29 | private final class MockKeyValue implements KeyValue { 30 | 31 | private final String key; 32 | private final String value; 33 | 34 | MockKeyValue(final String key, final String value) { 35 | super(); 36 | this.key = key; 37 | this.value = value; 38 | } 39 | 40 | @Override 41 | public String getKey() { 42 | return this.key; 43 | } 44 | 45 | @Override 46 | public String getStringValue() { 47 | return this.value; 48 | } 49 | 50 | @Override 51 | public byte[] getValue() { 52 | return this.value.getBytes(); 53 | } 54 | 55 | } 56 | 57 | private final class MockAssetResultsIterator implements QueryResultsIterator { 58 | 59 | private final List assetList; 60 | 61 | MockAssetResultsIterator() { 62 | super(); 63 | 64 | assetList = new ArrayList(); 65 | 66 | assetList.add(new MockKeyValue("asset1", 67 | "{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }")); 68 | assetList.add(new MockKeyValue("asset2", 69 | "{ \"assetID\": \"asset2\", \"color\": \"red\", \"size\": 5,\"owner\": \"Brad\", \"appraisedValue\": 400 }")); 70 | assetList.add(new MockKeyValue("asset3", 71 | "{ \"assetID\": \"asset3\", \"color\": \"green\", \"size\": 10,\"owner\": \"Jin Soo\", \"appraisedValue\": 500 }")); 72 | assetList.add(new MockKeyValue("asset4", 73 | "{ \"assetID\": \"asset4\", \"color\": \"yellow\", \"size\": 10,\"owner\": \"Max\", \"appraisedValue\": 600 }")); 74 | assetList.add(new MockKeyValue("asset5", 75 | "{ \"assetID\": \"asset5\", \"color\": \"black\", \"size\": 15,\"owner\": \"Adrian\", \"appraisedValue\": 700 }")); 76 | assetList.add(new MockKeyValue("asset6", 77 | "{ \"assetID\": \"asset6\", \"color\": \"white\", \"size\": 15,\"owner\": \"Michel\", \"appraisedValue\": 800 }")); 78 | } 79 | 80 | @Override 81 | public Iterator iterator() { 82 | return assetList.iterator(); 83 | } 84 | 85 | @Override 86 | public void close() throws Exception { 87 | // do nothing 88 | } 89 | 90 | } 91 | 92 | @Test 93 | public void invokeUnknownTransaction() { 94 | AssetTransfer contract = new AssetTransfer(); 95 | Context ctx = mock(Context.class); 96 | 97 | Throwable thrown = catchThrowable(() -> { 98 | contract.unknownTransaction(ctx); 99 | }); 100 | 101 | assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause() 102 | .hasMessage("Undefined contract method called"); 103 | assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo(null); 104 | 105 | verifyZeroInteractions(ctx); 106 | } 107 | 108 | @Nested 109 | class InvokeReadAssetTransaction { 110 | 111 | @Test 112 | public void whenAssetExists() { 113 | AssetTransfer contract = new AssetTransfer(); 114 | Context ctx = mock(Context.class); 115 | ChaincodeStub stub = mock(ChaincodeStub.class); 116 | when(ctx.getStub()).thenReturn(stub); 117 | when(stub.getStringState("asset1")) 118 | .thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }"); 119 | 120 | Asset asset = contract.ReadAsset(ctx, "asset1"); 121 | 122 | assertThat(asset).isEqualTo(new Asset("asset1", "blue", 5, "Tomoko", 300)); 123 | } 124 | 125 | @Test 126 | public void whenAssetDoesNotExist() { 127 | AssetTransfer contract = new AssetTransfer(); 128 | Context ctx = mock(Context.class); 129 | ChaincodeStub stub = mock(ChaincodeStub.class); 130 | when(ctx.getStub()).thenReturn(stub); 131 | when(stub.getStringState("asset1")).thenReturn(""); 132 | 133 | Throwable thrown = catchThrowable(() -> { 134 | contract.ReadAsset(ctx, "asset1"); 135 | }); 136 | 137 | assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause() 138 | .hasMessage("Asset asset1 does not exist"); 139 | assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes()); 140 | } 141 | } 142 | 143 | @Test 144 | void invokeInitLedgerTransaction() { 145 | AssetTransfer contract = new AssetTransfer(); 146 | Context ctx = mock(Context.class); 147 | ChaincodeStub stub = mock(ChaincodeStub.class); 148 | when(ctx.getStub()).thenReturn(stub); 149 | 150 | contract.InitLedger(ctx); 151 | 152 | InOrder inOrder = inOrder(stub); 153 | inOrder.verify(stub).putStringState("asset1", "{\"appraisedValue\":300,\"assetID\":\"asset1\",\"color\":\"blue\",\"owner\":\"Tomoko\",\"size\":5}"); 154 | inOrder.verify(stub).putStringState("asset2", "{\"appraisedValue\":400,\"assetID\":\"asset2\",\"color\":\"red\",\"owner\":\"Brad\",\"size\":5}"); 155 | inOrder.verify(stub).putStringState("asset3", "{\"appraisedValue\":500,\"assetID\":\"asset3\",\"color\":\"green\",\"owner\":\"Jin Soo\",\"size\":10}"); 156 | inOrder.verify(stub).putStringState("asset4", "{\"appraisedValue\":600,\"assetID\":\"asset4\",\"color\":\"yellow\",\"owner\":\"Max\",\"size\":10}"); 157 | inOrder.verify(stub).putStringState("asset5", "{\"appraisedValue\":700,\"assetID\":\"asset5\",\"color\":\"black\",\"owner\":\"Adrian\",\"size\":15}"); 158 | 159 | } 160 | 161 | @Nested 162 | class InvokeCreateAssetTransaction { 163 | 164 | @Test 165 | public void whenAssetExists() { 166 | AssetTransfer contract = new AssetTransfer(); 167 | Context ctx = mock(Context.class); 168 | ChaincodeStub stub = mock(ChaincodeStub.class); 169 | when(ctx.getStub()).thenReturn(stub); 170 | when(stub.getStringState("asset1")) 171 | .thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }"); 172 | 173 | Throwable thrown = catchThrowable(() -> { 174 | contract.CreateAsset(ctx, "asset1", "blue", 45, "Siobhán", 60); 175 | }); 176 | 177 | assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause() 178 | .hasMessage("Asset asset1 already exists"); 179 | assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_ALREADY_EXISTS".getBytes()); 180 | } 181 | 182 | @Test 183 | public void whenAssetDoesNotExist() { 184 | AssetTransfer contract = new AssetTransfer(); 185 | Context ctx = mock(Context.class); 186 | ChaincodeStub stub = mock(ChaincodeStub.class); 187 | when(ctx.getStub()).thenReturn(stub); 188 | when(stub.getStringState("asset1")).thenReturn(""); 189 | 190 | Asset asset = contract.CreateAsset(ctx, "asset1", "blue", 45, "Siobhán", 60); 191 | 192 | assertThat(asset).isEqualTo(new Asset("asset1", "blue", 45, "Siobhán", 60)); 193 | } 194 | } 195 | 196 | @Test 197 | void invokeGetAllAssetsTransaction() { 198 | AssetTransfer contract = new AssetTransfer(); 199 | Context ctx = mock(Context.class); 200 | ChaincodeStub stub = mock(ChaincodeStub.class); 201 | when(ctx.getStub()).thenReturn(stub); 202 | when(stub.getStateByRange("", "")).thenReturn(new MockAssetResultsIterator()); 203 | 204 | String assets = contract.GetAllAssets(ctx); 205 | 206 | assertThat(assets).isEqualTo("[{\"appraisedValue\":300,\"assetID\":\"asset1\",\"color\":\"blue\",\"owner\":\"Tomoko\",\"size\":5}," 207 | + "{\"appraisedValue\":400,\"assetID\":\"asset2\",\"color\":\"red\",\"owner\":\"Brad\",\"size\":5}," 208 | + "{\"appraisedValue\":500,\"assetID\":\"asset3\",\"color\":\"green\",\"owner\":\"Jin Soo\",\"size\":10}," 209 | + "{\"appraisedValue\":600,\"assetID\":\"asset4\",\"color\":\"yellow\",\"owner\":\"Max\",\"size\":10}," 210 | + "{\"appraisedValue\":700,\"assetID\":\"asset5\",\"color\":\"black\",\"owner\":\"Adrian\",\"size\":15}," 211 | + "{\"appraisedValue\":800,\"assetID\":\"asset6\",\"color\":\"white\",\"owner\":\"Michel\",\"size\":15}]"); 212 | 213 | } 214 | 215 | @Nested 216 | class TransferAssetTransaction { 217 | 218 | @Test 219 | public void whenAssetExists() { 220 | AssetTransfer contract = new AssetTransfer(); 221 | Context ctx = mock(Context.class); 222 | ChaincodeStub stub = mock(ChaincodeStub.class); 223 | when(ctx.getStub()).thenReturn(stub); 224 | when(stub.getStringState("asset1")) 225 | .thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }"); 226 | 227 | String oldOwner = contract.TransferAsset(ctx, "asset1", "Dr Evil"); 228 | 229 | assertThat(oldOwner).isEqualTo("Tomoko"); 230 | } 231 | 232 | @Test 233 | public void whenAssetDoesNotExist() { 234 | AssetTransfer contract = new AssetTransfer(); 235 | Context ctx = mock(Context.class); 236 | ChaincodeStub stub = mock(ChaincodeStub.class); 237 | when(ctx.getStub()).thenReturn(stub); 238 | when(stub.getStringState("asset1")).thenReturn(""); 239 | 240 | Throwable thrown = catchThrowable(() -> { 241 | contract.TransferAsset(ctx, "asset1", "Dr Evil"); 242 | }); 243 | 244 | assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause() 245 | .hasMessage("Asset asset1 does not exist"); 246 | assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes()); 247 | } 248 | } 249 | 250 | @Nested 251 | class UpdateAssetTransaction { 252 | 253 | @Test 254 | public void whenAssetExists() { 255 | AssetTransfer contract = new AssetTransfer(); 256 | Context ctx = mock(Context.class); 257 | ChaincodeStub stub = mock(ChaincodeStub.class); 258 | when(ctx.getStub()).thenReturn(stub); 259 | when(stub.getStringState("asset1")) 260 | .thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 45, \"owner\": \"Arturo\", \"appraisedValue\": 60 }"); 261 | 262 | Asset asset = contract.UpdateAsset(ctx, "asset1", "pink", 45, "Arturo", 600); 263 | 264 | assertThat(asset).isEqualTo(new Asset("asset1", "pink", 45, "Arturo", 600)); 265 | } 266 | 267 | @Test 268 | public void whenAssetDoesNotExist() { 269 | AssetTransfer contract = new AssetTransfer(); 270 | Context ctx = mock(Context.class); 271 | ChaincodeStub stub = mock(ChaincodeStub.class); 272 | when(ctx.getStub()).thenReturn(stub); 273 | when(stub.getStringState("asset1")).thenReturn(""); 274 | 275 | Throwable thrown = catchThrowable(() -> { 276 | contract.TransferAsset(ctx, "asset1", "Alex"); 277 | }); 278 | 279 | assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause() 280 | .hasMessage("Asset asset1 does not exist"); 281 | assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes()); 282 | } 283 | } 284 | 285 | @Nested 286 | class DeleteAssetTransaction { 287 | 288 | @Test 289 | public void whenAssetDoesNotExist() { 290 | AssetTransfer contract = new AssetTransfer(); 291 | Context ctx = mock(Context.class); 292 | ChaincodeStub stub = mock(ChaincodeStub.class); 293 | when(ctx.getStub()).thenReturn(stub); 294 | when(stub.getStringState("asset1")).thenReturn(""); 295 | 296 | Throwable thrown = catchThrowable(() -> { 297 | contract.DeleteAsset(ctx, "asset1"); 298 | }); 299 | 300 | assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause() 301 | .hasMessage("Asset asset1 does not exist"); 302 | assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes()); 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /contracts/asset-transfer-ts/dist/assetTransfer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 13 | return new (P || (P = Promise))(function (resolve, reject) { 14 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 15 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 16 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 17 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 18 | }); 19 | }; 20 | Object.defineProperty(exports, "__esModule", { value: true }); 21 | exports.AssetTransferContract = void 0; 22 | const fabric_contract_api_1 = require("fabric-contract-api"); 23 | let AssetTransferContract = class AssetTransferContract extends fabric_contract_api_1.Contract { 24 | InitLedger(ctx) { 25 | return __awaiter(this, void 0, void 0, function* () { 26 | const assets = [ 27 | { 28 | id: 'asset1', 29 | color: 'blue', 30 | size: 5, 31 | owner: 'Tomoko', 32 | appraisedValue: 300, 33 | }, 34 | { 35 | id: 'asset2', 36 | color: 'red', 37 | size: 5, 38 | owner: 'Brad', 39 | appraisedValue: 400, 40 | }, 41 | { 42 | id: 'asset3', 43 | color: 'green', 44 | size: 10, 45 | owner: 'Jin Soo', 46 | appraisedValue: 500, 47 | }, 48 | { 49 | id: 'asset4', 50 | color: 'yellow', 51 | size: 10, 52 | owner: 'Max', 53 | appraisedValue: 600, 54 | }, 55 | { 56 | id: 'asset5', 57 | color: 'black', 58 | size: 15, 59 | owner: 'Adriana', 60 | appraisedValue: 700, 61 | }, 62 | { 63 | id: 'asset6', 64 | color: 'white', 65 | size: 15, 66 | owner: 'Michel', 67 | appraisedValue: 800, 68 | }, 69 | ]; 70 | for (const asset of assets) { 71 | asset.docType = 'asset'; 72 | yield ctx.stub.putState(asset.id, Buffer.from(JSON.stringify(asset))); 73 | console.info(`Asset ${asset.id} initialized`); 74 | } 75 | }); 76 | } 77 | CreateAsset(ctx, id, color, size, owner, appraisedValue) { 78 | return __awaiter(this, void 0, void 0, function* () { 79 | const asset = { 80 | id: id, 81 | color: color, 82 | size: size, 83 | owner: owner, 84 | appraisedValue: appraisedValue, 85 | }; 86 | yield ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); 87 | }); 88 | } 89 | ReadAsset(ctx, id) { 90 | return __awaiter(this, void 0, void 0, function* () { 91 | const assetJSON = yield ctx.stub.getState(id); 92 | if (!assetJSON || assetJSON.length === 0) { 93 | throw new Error(`The asset ${id} does not exist`); 94 | } 95 | return assetJSON.toString(); 96 | }); 97 | } 98 | UpdateAsset(ctx, id, color, size, owner, appraisedValue) { 99 | return __awaiter(this, void 0, void 0, function* () { 100 | const exists = yield this.AssetExists(ctx, id); 101 | if (!exists) { 102 | throw new Error(`The asset ${id} does not exist`); 103 | } 104 | const updatedAsset = { 105 | id: id, 106 | color: color, 107 | size: size, 108 | owner: owner, 109 | appraisedValue: appraisedValue, 110 | }; 111 | return ctx.stub.putState(id, Buffer.from(JSON.stringify(updatedAsset))); 112 | }); 113 | } 114 | DeleteAsset(ctx, id) { 115 | return __awaiter(this, void 0, void 0, function* () { 116 | const exists = yield this.AssetExists(ctx, id); 117 | if (!exists) { 118 | throw new Error(`The asset ${id} does not exist`); 119 | } 120 | return ctx.stub.deleteState(id); 121 | }); 122 | } 123 | AssetExists(ctx, id) { 124 | return __awaiter(this, void 0, void 0, function* () { 125 | const assetJSON = yield ctx.stub.getState(id); 126 | return assetJSON && assetJSON.length > 0; 127 | }); 128 | } 129 | TransferAsset(ctx, id, newowner) { 130 | return __awaiter(this, void 0, void 0, function* () { 131 | const assetString = yield this.ReadAsset(ctx, id); 132 | const asset = JSON.parse(assetString); 133 | asset.owner = newowner; 134 | yield ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); 135 | }); 136 | } 137 | GetAllAssets(ctx) { 138 | return __awaiter(this, void 0, void 0, function* () { 139 | const allResults = []; 140 | const iterator = yield ctx.stub.getStateByRange('', ''); 141 | let result = yield iterator.next(); 142 | while (!result.done) { 143 | const strValue = Buffer.from(result.value.value.toString()).toString('utf8'); 144 | let record; 145 | try { 146 | record = JSON.parse(strValue); 147 | } 148 | catch (err) { 149 | console.log(err); 150 | record = strValue; 151 | } 152 | allResults.push({ Key: result.value.key, Record: record }); 153 | result = yield iterator.next(); 154 | } 155 | return JSON.stringify(allResults); 156 | }); 157 | } 158 | }; 159 | __decorate([ 160 | fabric_contract_api_1.Transaction(), 161 | __metadata("design:type", Function), 162 | __metadata("design:paramtypes", [fabric_contract_api_1.Context]), 163 | __metadata("design:returntype", Promise) 164 | ], AssetTransferContract.prototype, "InitLedger", null); 165 | __decorate([ 166 | fabric_contract_api_1.Transaction(), 167 | __metadata("design:type", Function), 168 | __metadata("design:paramtypes", [fabric_contract_api_1.Context, String, String, Number, String, Number]), 169 | __metadata("design:returntype", Promise) 170 | ], AssetTransferContract.prototype, "CreateAsset", null); 171 | __decorate([ 172 | fabric_contract_api_1.Transaction(false), 173 | __metadata("design:type", Function), 174 | __metadata("design:paramtypes", [fabric_contract_api_1.Context, String]), 175 | __metadata("design:returntype", Promise) 176 | ], AssetTransferContract.prototype, "ReadAsset", null); 177 | __decorate([ 178 | fabric_contract_api_1.Transaction(), 179 | __metadata("design:type", Function), 180 | __metadata("design:paramtypes", [fabric_contract_api_1.Context, String, String, Number, String, Number]), 181 | __metadata("design:returntype", Promise) 182 | ], AssetTransferContract.prototype, "UpdateAsset", null); 183 | __decorate([ 184 | fabric_contract_api_1.Transaction(), 185 | __metadata("design:type", Function), 186 | __metadata("design:paramtypes", [fabric_contract_api_1.Context, String]), 187 | __metadata("design:returntype", Promise) 188 | ], AssetTransferContract.prototype, "DeleteAsset", null); 189 | __decorate([ 190 | fabric_contract_api_1.Transaction(false), 191 | fabric_contract_api_1.Returns('boolean'), 192 | __metadata("design:type", Function), 193 | __metadata("design:paramtypes", [fabric_contract_api_1.Context, String]), 194 | __metadata("design:returntype", Promise) 195 | ], AssetTransferContract.prototype, "AssetExists", null); 196 | __decorate([ 197 | fabric_contract_api_1.Transaction(), 198 | __metadata("design:type", Function), 199 | __metadata("design:paramtypes", [fabric_contract_api_1.Context, String, String]), 200 | __metadata("design:returntype", Promise) 201 | ], AssetTransferContract.prototype, "TransferAsset", null); 202 | __decorate([ 203 | fabric_contract_api_1.Transaction(false), 204 | fabric_contract_api_1.Returns('string'), 205 | __metadata("design:type", Function), 206 | __metadata("design:paramtypes", [fabric_contract_api_1.Context]), 207 | __metadata("design:returntype", Promise) 208 | ], AssetTransferContract.prototype, "GetAllAssets", null); 209 | AssetTransferContract = __decorate([ 210 | fabric_contract_api_1.Info({ title: 'AssetTransfer', description: 'Smart contract for trading assets' }) 211 | ], AssetTransferContract); 212 | exports.AssetTransferContract = AssetTransferContract; 213 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXNzZXRUcmFuc2Zlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9hc3NldFRyYW5zZmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUlBLDZEQUFvRjtBQUlwRixJQUFhLHFCQUFxQixHQUFsQyxNQUFhLHFCQUFzQixTQUFRLDhCQUFRO0lBRWxDLFVBQVUsQ0FBQyxHQUFZOztZQUNoQyxNQUFNLE1BQU0sR0FBWTtnQkFDcEI7b0JBQ0ksRUFBRSxFQUFFLFFBQVE7b0JBQ1osS0FBSyxFQUFFLE1BQU07b0JBQ2IsSUFBSSxFQUFFLENBQUM7b0JBQ1AsS0FBSyxFQUFFLFFBQVE7b0JBQ2YsY0FBYyxFQUFFLEdBQUc7aUJBQ3RCO2dCQUNEO29CQUNJLEVBQUUsRUFBRSxRQUFRO29CQUNaLEtBQUssRUFBRSxLQUFLO29CQUNaLElBQUksRUFBRSxDQUFDO29CQUNQLEtBQUssRUFBRSxNQUFNO29CQUNiLGNBQWMsRUFBRSxHQUFHO2lCQUN0QjtnQkFDRDtvQkFDSSxFQUFFLEVBQUUsUUFBUTtvQkFDWixLQUFLLEVBQUUsT0FBTztvQkFDZCxJQUFJLEVBQUUsRUFBRTtvQkFDUixLQUFLLEVBQUUsU0FBUztvQkFDaEIsY0FBYyxFQUFFLEdBQUc7aUJBQ3RCO2dCQUNEO29CQUNJLEVBQUUsRUFBRSxRQUFRO29CQUNaLEtBQUssRUFBRSxRQUFRO29CQUNmLElBQUksRUFBRSxFQUFFO29CQUNSLEtBQUssRUFBRSxLQUFLO29CQUNaLGNBQWMsRUFBRSxHQUFHO2lCQUN0QjtnQkFDRDtvQkFDSSxFQUFFLEVBQUUsUUFBUTtvQkFDWixLQUFLLEVBQUUsT0FBTztvQkFDZCxJQUFJLEVBQUUsRUFBRTtvQkFDUixLQUFLLEVBQUUsU0FBUztvQkFDaEIsY0FBYyxFQUFFLEdBQUc7aUJBQ3RCO2dCQUNEO29CQUNJLEVBQUUsRUFBRSxRQUFRO29CQUNaLEtBQUssRUFBRSxPQUFPO29CQUNkLElBQUksRUFBRSxFQUFFO29CQUNSLEtBQUssRUFBRSxRQUFRO29CQUNmLGNBQWMsRUFBRSxHQUFHO2lCQUN0QjthQUNKLENBQUM7WUFFRixLQUFLLE1BQU0sS0FBSyxJQUFJLE1BQU0sRUFBRTtnQkFDeEIsS0FBSyxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7Z0JBQ3hCLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUN0RSxPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsS0FBSyxDQUFDLEVBQUUsY0FBYyxDQUFDLENBQUM7YUFDakQ7UUFDTCxDQUFDO0tBQUE7SUFJWSxXQUFXLENBQ3BCLEdBQVksRUFDWixFQUFVLEVBQ1YsS0FBYSxFQUNiLElBQVksRUFDWixLQUFhLEVBQ2IsY0FBc0I7O1lBRXRCLE1BQU0sS0FBSyxHQUFHO2dCQUNWLEVBQUUsRUFBRSxFQUFFO2dCQUNOLEtBQUssRUFBRSxLQUFLO2dCQUNaLElBQUksRUFBRSxJQUFJO2dCQUNWLEtBQUssRUFBRSxLQUFLO2dCQUNaLGNBQWMsRUFBRSxjQUFjO2FBQ2pDLENBQUM7WUFDRixNQUFNLEdBQUcsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3BFLENBQUM7S0FBQTtJQUlZLFNBQVMsQ0FBQyxHQUFZLEVBQUUsRUFBVTs7WUFDM0MsTUFBTSxTQUFTLEdBQUcsTUFBTSxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUM5QyxJQUFJLENBQUMsU0FBUyxJQUFJLFNBQVMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO2dCQUN0QyxNQUFNLElBQUksS0FBSyxDQUFDLGFBQWEsRUFBRSxpQkFBaUIsQ0FBQyxDQUFDO2FBQ3JEO1lBQ0QsT0FBTyxTQUFTLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDaEMsQ0FBQztLQUFBO0lBSVksV0FBVyxDQUNwQixHQUFZLEVBQ1osRUFBVSxFQUNWLEtBQWEsRUFDYixJQUFZLEVBQ1osS0FBYSxFQUNiLGNBQXNCOztZQUV0QixNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQy9DLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQ1QsTUFBTSxJQUFJLEtBQUssQ0FBQyxhQUFhLEVBQUUsaUJBQWlCLENBQUMsQ0FBQzthQUNyRDtZQUdELE1BQU0sWUFBWSxHQUFHO2dCQUNqQixFQUFFLEVBQUUsRUFBRTtnQkFDTixLQUFLLEVBQUUsS0FBSztnQkFDWixJQUFJLEVBQUUsSUFBSTtnQkFDVixLQUFLLEVBQUUsS0FBSztnQkFDWixjQUFjLEVBQUUsY0FBYzthQUNqQyxDQUFDO1lBQ0YsT0FBTyxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM1RSxDQUFDO0tBQUE7SUFJWSxXQUFXLENBQUMsR0FBWSxFQUFFLEVBQVU7O1lBQzdDLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDL0MsSUFBSSxDQUFDLE1BQU0sRUFBRTtnQkFDVCxNQUFNLElBQUksS0FBSyxDQUFDLGFBQWEsRUFBRSxpQkFBaUIsQ0FBQyxDQUFDO2FBQ3JEO1lBQ0QsT0FBTyxHQUFHLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUNwQyxDQUFDO0tBQUE7SUFLWSxXQUFXLENBQUMsR0FBWSxFQUFFLEVBQVU7O1lBQzdDLE1BQU0sU0FBUyxHQUFHLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDOUMsT0FBTyxTQUFTLElBQUksU0FBUyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUM7UUFDN0MsQ0FBQztLQUFBO0lBSVksYUFBYSxDQUFDLEdBQVksRUFBRSxFQUFVLEVBQUUsUUFBZ0I7O1lBQ2pFLE1BQU0sV0FBVyxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDbEQsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUN0QyxLQUFLLENBQUMsS0FBSyxHQUFHLFFBQVEsQ0FBQztZQUN2QixNQUFNLEdBQUcsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3BFLENBQUM7S0FBQTtJQUtZLFlBQVksQ0FBQyxHQUFZOztZQUNsQyxNQUFNLFVBQVUsR0FBRyxFQUFFLENBQUM7WUFFdEIsTUFBTSxRQUFRLEdBQUcsTUFBTSxHQUFHLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDeEQsSUFBSSxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDbkMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUU7Z0JBQ2pCLE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQzdFLElBQUksTUFBTSxDQUFDO2dCQUNYLElBQUk7b0JBQ0EsTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7aUJBQ2pDO2dCQUFDLE9BQU8sR0FBRyxFQUFFO29CQUNWLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7b0JBQ2pCLE1BQU0sR0FBRyxRQUFRLENBQUM7aUJBQ3JCO2dCQUNELFVBQVUsQ0FBQyxJQUFJLENBQUMsRUFBRSxHQUFHLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7Z0JBQzNELE1BQU0sR0FBRyxNQUFNLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQzthQUNsQztZQUNELE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUN0QyxDQUFDO0tBQUE7Q0FDSixDQUFBO0FBOUpHO0lBREMsaUNBQVcsRUFBRTs7cUNBQ2UsNkJBQU87O3VEQW1EbkM7QUFJRDtJQURDLGlDQUFXLEVBQUU7O3FDQUVMLDZCQUFPOzt3REFlZjtBQUlEO0lBREMsaUNBQVcsQ0FBQyxLQUFLLENBQUM7O3FDQUNTLDZCQUFPOztzREFNbEM7QUFJRDtJQURDLGlDQUFXLEVBQUU7O3FDQUVMLDZCQUFPOzt3REFxQmY7QUFJRDtJQURDLGlDQUFXLEVBQUU7O3FDQUNnQiw2QkFBTzs7d0RBTXBDO0FBS0Q7SUFGQyxpQ0FBVyxDQUFDLEtBQUssQ0FBQztJQUNsQiw2QkFBTyxDQUFDLFNBQVMsQ0FBQzs7cUNBQ1csNkJBQU87O3dEQUdwQztBQUlEO0lBREMsaUNBQVcsRUFBRTs7cUNBQ2tCLDZCQUFPOzswREFLdEM7QUFLRDtJQUZDLGlDQUFXLENBQUMsS0FBSyxDQUFDO0lBQ2xCLDZCQUFPLENBQUMsUUFBUSxDQUFDOztxQ0FDYSw2QkFBTzs7eURBa0JyQztBQS9KUSxxQkFBcUI7SUFEakMsMEJBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxlQUFlLEVBQUUsV0FBVyxFQUFFLG1DQUFtQyxFQUFFLENBQUM7R0FDdEUscUJBQXFCLENBZ0tqQztBQWhLWSxzREFBcUIiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcbiAqL1xuXG5pbXBvcnQgeyBDb250ZXh0LCBDb250cmFjdCwgSW5mbywgUmV0dXJucywgVHJhbnNhY3Rpb24gfSBmcm9tICdmYWJyaWMtY29udHJhY3QtYXBpJztcbmltcG9ydCB7IEFzc2V0IH0gZnJvbSAnLi9hc3NldCc7XG5cbkBJbmZvKHsgdGl0bGU6ICdBc3NldFRyYW5zZmVyJywgZGVzY3JpcHRpb246ICdTbWFydCBjb250cmFjdCBmb3IgdHJhZGluZyBhc3NldHMnIH0pXG5leHBvcnQgY2xhc3MgQXNzZXRUcmFuc2ZlckNvbnRyYWN0IGV4dGVuZHMgQ29udHJhY3Qge1xuICAgIEBUcmFuc2FjdGlvbigpXG4gICAgcHVibGljIGFzeW5jIEluaXRMZWRnZXIoY3R4OiBDb250ZXh0KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IGFzc2V0czogQXNzZXRbXSA9IFtcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBpZDogJ2Fzc2V0MScsXG4gICAgICAgICAgICAgICAgY29sb3I6ICdibHVlJyxcbiAgICAgICAgICAgICAgICBzaXplOiA1LFxuICAgICAgICAgICAgICAgIG93bmVyOiAnVG9tb2tvJyxcbiAgICAgICAgICAgICAgICBhcHByYWlzZWRWYWx1ZTogMzAwLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBpZDogJ2Fzc2V0MicsXG4gICAgICAgICAgICAgICAgY29sb3I6ICdyZWQnLFxuICAgICAgICAgICAgICAgIHNpemU6IDUsXG4gICAgICAgICAgICAgICAgb3duZXI6ICdCcmFkJyxcbiAgICAgICAgICAgICAgICBhcHByYWlzZWRWYWx1ZTogNDAwLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBpZDogJ2Fzc2V0MycsXG4gICAgICAgICAgICAgICAgY29sb3I6ICdncmVlbicsXG4gICAgICAgICAgICAgICAgc2l6ZTogMTAsXG4gICAgICAgICAgICAgICAgb3duZXI6ICdKaW4gU29vJyxcbiAgICAgICAgICAgICAgICBhcHByYWlzZWRWYWx1ZTogNTAwLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBpZDogJ2Fzc2V0NCcsXG4gICAgICAgICAgICAgICAgY29sb3I6ICd5ZWxsb3cnLFxuICAgICAgICAgICAgICAgIHNpemU6IDEwLFxuICAgICAgICAgICAgICAgIG93bmVyOiAnTWF4JyxcbiAgICAgICAgICAgICAgICBhcHByYWlzZWRWYWx1ZTogNjAwLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBpZDogJ2Fzc2V0NScsXG4gICAgICAgICAgICAgICAgY29sb3I6ICdibGFjaycsXG4gICAgICAgICAgICAgICAgc2l6ZTogMTUsXG4gICAgICAgICAgICAgICAgb3duZXI6ICdBZHJpYW5hJyxcbiAgICAgICAgICAgICAgICBhcHByYWlzZWRWYWx1ZTogNzAwLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBpZDogJ2Fzc2V0NicsXG4gICAgICAgICAgICAgICAgY29sb3I6ICd3aGl0ZScsXG4gICAgICAgICAgICAgICAgc2l6ZTogMTUsXG4gICAgICAgICAgICAgICAgb3duZXI6ICdNaWNoZWwnLFxuICAgICAgICAgICAgICAgIGFwcHJhaXNlZFZhbHVlOiA4MDAsXG4gICAgICAgICAgICB9LFxuICAgICAgICBdO1xuXG4gICAgICAgIGZvciAoY29uc3QgYXNzZXQgb2YgYXNzZXRzKSB7XG4gICAgICAgICAgICBhc3NldC5kb2NUeXBlID0gJ2Fzc2V0JztcbiAgICAgICAgICAgIGF3YWl0IGN0eC5zdHViLnB1dFN0YXRlKGFzc2V0LmlkLCBCdWZmZXIuZnJvbShKU09OLnN0cmluZ2lmeShhc3NldCkpKTtcbiAgICAgICAgICAgIGNvbnNvbGUuaW5mbyhgQXNzZXQgJHthc3NldC5pZH0gaW5pdGlhbGl6ZWRgKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIENyZWF0ZUFzc2V0IGlzc3VlcyBhIG5ldyBhc3NldCB0byB0aGUgd29ybGQgc3RhdGUgd2l0aCBnaXZlbiBkZXRhaWxzLlxuICAgIEBUcmFuc2FjdGlvbigpXG4gICAgcHVibGljIGFzeW5jIENyZWF0ZUFzc2V0KFxuICAgICAgICBjdHg6IENvbnRleHQsXG4gICAgICAgIGlkOiBzdHJpbmcsXG4gICAgICAgIGNvbG9yOiBzdHJpbmcsXG4gICAgICAgIHNpemU6IG51bWJlcixcbiAgICAgICAgb3duZXI6IHN0cmluZyxcbiAgICAgICAgYXBwcmFpc2VkVmFsdWU6IG51bWJlcixcbiAgICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgYXNzZXQgPSB7XG4gICAgICAgICAgICBpZDogaWQsXG4gICAgICAgICAgICBjb2xvcjogY29sb3IsXG4gICAgICAgICAgICBzaXplOiBzaXplLFxuICAgICAgICAgICAgb3duZXI6IG93bmVyLFxuICAgICAgICAgICAgYXBwcmFpc2VkVmFsdWU6IGFwcHJhaXNlZFZhbHVlLFxuICAgICAgICB9O1xuICAgICAgICBhd2FpdCBjdHguc3R1Yi5wdXRTdGF0ZShpZCwgQnVmZmVyLmZyb20oSlNPTi5zdHJpbmdpZnkoYXNzZXQpKSk7XG4gICAgfVxuXG4gICAgLy8gUmVhZEFzc2V0IHJldHVybnMgdGhlIGFzc2V0IHN0b3JlZCBpbiB0aGUgd29ybGQgc3RhdGUgd2l0aCBnaXZlbiBpZC5cbiAgICBAVHJhbnNhY3Rpb24oZmFsc2UpXG4gICAgcHVibGljIGFzeW5jIFJlYWRBc3NldChjdHg6IENvbnRleHQsIGlkOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgICAgICBjb25zdCBhc3NldEpTT04gPSBhd2FpdCBjdHguc3R1Yi5nZXRTdGF0ZShpZCk7IC8vIGdldCB0aGUgYXNzZXQgZnJvbSBjaGFpbmNvZGUgc3RhdGVcbiAgICAgICAgaWYgKCFhc3NldEpTT04gfHwgYXNzZXRKU09OLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBUaGUgYXNzZXQgJHtpZH0gZG9lcyBub3QgZXhpc3RgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gYXNzZXRKU09OLnRvU3RyaW5nKCk7XG4gICAgfVxuXG4gICAgLy8gVXBkYXRlQXNzZXQgdXBkYXRlcyBhbiBleGlzdGluZyBhc3NldCBpbiB0aGUgd29ybGQgc3RhdGUgd2l0aCBwcm92aWRlZCBwYXJhbWV0ZXJzLlxuICAgIEBUcmFuc2FjdGlvbigpXG4gICAgcHVibGljIGFzeW5jIFVwZGF0ZUFzc2V0KFxuICAgICAgICBjdHg6IENvbnRleHQsXG4gICAgICAgIGlkOiBzdHJpbmcsXG4gICAgICAgIGNvbG9yOiBzdHJpbmcsXG4gICAgICAgIHNpemU6IG51bWJlcixcbiAgICAgICAgb3duZXI6IHN0cmluZyxcbiAgICAgICAgYXBwcmFpc2VkVmFsdWU6IG51bWJlcixcbiAgICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgZXhpc3RzID0gYXdhaXQgdGhpcy5Bc3NldEV4aXN0cyhjdHgsIGlkKTtcbiAgICAgICAgaWYgKCFleGlzdHMpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVGhlIGFzc2V0ICR7aWR9IGRvZXMgbm90IGV4aXN0YCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBvdmVyd3JpdGluZyBvcmlnaW5hbCBhc3NldCB3aXRoIG5ldyBhc3NldFxuICAgICAgICBjb25zdCB1cGRhdGVkQXNzZXQgPSB7XG4gICAgICAgICAgICBpZDogaWQsXG4gICAgICAgICAgICBjb2xvcjogY29sb3IsXG4gICAgICAgICAgICBzaXplOiBzaXplLFxuICAgICAgICAgICAgb3duZXI6IG93bmVyLFxuICAgICAgICAgICAgYXBwcmFpc2VkVmFsdWU6IGFwcHJhaXNlZFZhbHVlLFxuICAgICAgICB9O1xuICAgICAgICByZXR1cm4gY3R4LnN0dWIucHV0U3RhdGUoaWQsIEJ1ZmZlci5mcm9tKEpTT04uc3RyaW5naWZ5KHVwZGF0ZWRBc3NldCkpKTtcbiAgICB9XG5cbiAgICAvLyBEZWxldGVBc3NldCBkZWxldGVzIGFuIGdpdmVuIGFzc2V0IGZyb20gdGhlIHdvcmxkIHN0YXRlLlxuICAgIEBUcmFuc2FjdGlvbigpXG4gICAgcHVibGljIGFzeW5jIERlbGV0ZUFzc2V0KGN0eDogQ29udGV4dCwgaWQ6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBleGlzdHMgPSBhd2FpdCB0aGlzLkFzc2V0RXhpc3RzKGN0eCwgaWQpO1xuICAgICAgICBpZiAoIWV4aXN0cykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBUaGUgYXNzZXQgJHtpZH0gZG9lcyBub3QgZXhpc3RgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gY3R4LnN0dWIuZGVsZXRlU3RhdGUoaWQpO1xuICAgIH1cblxuICAgIC8vIEFzc2V0RXhpc3RzIHJldHVybnMgdHJ1ZSB3aGVuIGFzc2V0IHdpdGggZ2l2ZW4gaWQgZXhpc3RzIGluIHdvcmxkIHN0YXRlLlxuICAgIEBUcmFuc2FjdGlvbihmYWxzZSlcbiAgICBAUmV0dXJucygnYm9vbGVhbicpXG4gICAgcHVibGljIGFzeW5jIEFzc2V0RXhpc3RzKGN0eDogQ29udGV4dCwgaWQ6IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICBjb25zdCBhc3NldEpTT04gPSBhd2FpdCBjdHguc3R1Yi5nZXRTdGF0ZShpZCk7XG4gICAgICAgIHJldHVybiBhc3NldEpTT04gJiYgYXNzZXRKU09OLmxlbmd0aCA+IDA7XG4gICAgfVxuXG4gICAgLy8gVHJhbnNmZXJBc3NldCB1cGRhdGVzIHRoZSBvd25lciBmaWVsZCBvZiBhc3NldCB3aXRoIGdpdmVuIGlkIGluIHRoZSB3b3JsZCBzdGF0ZS5cbiAgICBAVHJhbnNhY3Rpb24oKVxuICAgIHB1YmxpYyBhc3luYyBUcmFuc2ZlckFzc2V0KGN0eDogQ29udGV4dCwgaWQ6IHN0cmluZywgbmV3b3duZXI6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBhc3NldFN0cmluZyA9IGF3YWl0IHRoaXMuUmVhZEFzc2V0KGN0eCwgaWQpO1xuICAgICAgICBjb25zdCBhc3NldCA9IEpTT04ucGFyc2UoYXNzZXRTdHJpbmcpO1xuICAgICAgICBhc3NldC5vd25lciA9IG5ld293bmVyO1xuICAgICAgICBhd2FpdCBjdHguc3R1Yi5wdXRTdGF0ZShpZCwgQnVmZmVyLmZyb20oSlNPTi5zdHJpbmdpZnkoYXNzZXQpKSk7XG4gICAgfVxuXG4gICAgLy8gR2V0QWxsQXNzZXRzIHJldHVybnMgYWxsIGFzc2V0cyBmb3VuZCBpbiB0aGUgd29ybGQgc3RhdGUuXG4gICAgQFRyYW5zYWN0aW9uKGZhbHNlKVxuICAgIEBSZXR1cm5zKCdzdHJpbmcnKVxuICAgIHB1YmxpYyBhc3luYyBHZXRBbGxBc3NldHMoY3R4OiBDb250ZXh0KTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICAgICAgY29uc3QgYWxsUmVzdWx0cyA9IFtdO1xuICAgICAgICAvLyByYW5nZSBxdWVyeSB3aXRoIGVtcHR5IHN0cmluZyBmb3Igc3RhcnRLZXkgYW5kIGVuZEtleSBkb2VzIGFuIG9wZW4tZW5kZWQgcXVlcnkgb2YgYWxsIGFzc2V0cyBpbiB0aGUgY2hhaW5jb2RlIG5hbWVzcGFjZS5cbiAgICAgICAgY29uc3QgaXRlcmF0b3IgPSBhd2FpdCBjdHguc3R1Yi5nZXRTdGF0ZUJ5UmFuZ2UoJycsICcnKTtcbiAgICAgICAgbGV0IHJlc3VsdCA9IGF3YWl0IGl0ZXJhdG9yLm5leHQoKTtcbiAgICAgICAgd2hpbGUgKCFyZXN1bHQuZG9uZSkge1xuICAgICAgICAgICAgY29uc3Qgc3RyVmFsdWUgPSBCdWZmZXIuZnJvbShyZXN1bHQudmFsdWUudmFsdWUudG9TdHJpbmcoKSkudG9TdHJpbmcoJ3V0ZjgnKTtcbiAgICAgICAgICAgIGxldCByZWNvcmQ7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHJlY29yZCA9IEpTT04ucGFyc2Uoc3RyVmFsdWUpO1xuICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5sb2coZXJyKTtcbiAgICAgICAgICAgICAgICByZWNvcmQgPSBzdHJWYWx1ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGFsbFJlc3VsdHMucHVzaCh7IEtleTogcmVzdWx0LnZhbHVlLmtleSwgUmVjb3JkOiByZWNvcmQgfSk7XG4gICAgICAgICAgICByZXN1bHQgPSBhd2FpdCBpdGVyYXRvci5uZXh0KCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KGFsbFJlc3VsdHMpO1xuICAgIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Develop, Debug and Deploy Smart Contracts - DRAFT 2 | 3 | **Aim:** Stand up a Hyperledger Fabric Smart Contract so it can easily debugged 4 | **Objectives:** 5 | 6 | - Introduce what Chaincode-as-a-Service is, and how it helps 7 | - Show how to build & configure a Chaincode to run like this 8 | - How to deploy these a running Hyperledger Fabric network 9 | - How then to debug this running Chaincode. 10 | 11 | ## Overview 12 | 13 | It helps to think of three 'parts' 14 | 15 | - The Fabric network, consisting of the peers, orderers, certificate authorities etc. Along with configured channels and identities. For our purposes here, this can be considered as a 'black box'. The 'black box' can be configured a number of different ways, but typically will be one or docker containers. 16 | - The Chaincode - this will be running in it's own docker container. 17 | - The editor - VSCode is covered here, but the approach should hold with other debuggers and editors. 18 | 19 | ![](_docs/CCAASDebug.png) 20 | 21 | The _high level process_ is 22 | 23 | 0. Stand up Fabric 24 | 1. Develop the Smart Contract 25 | 3. Create a chaincode package but using the chaincode-as-a-service approach 26 | Install/Approve/Commit this package 27 | 4. Stand up the Chaincode using the chaincode-as-a-service approach 28 | Attach your debugger to the running chaincode 29 | 5. Invoke a transaction, this will then halt in the debugger to let you step over the code 30 | 5. Find the bugs and repeat **from step 4** - note step 4... you don't need to go back to a new package or approval cycle. 31 | 32 | ### What do you need? 33 | 34 | You'll need to have docker available to you, along with VSCode and the IBM Blockchain Platofrm Extension installed. Also, install the VSCode extensions you prefer for debugging your preferred language. 35 | 36 | - For TypeScript and JavaScript VSCode has built-in support. 37 | - For Java the [JavaExtension pack](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack) is suggested 38 | - For go [_to be added_] 39 | 40 | Finally, clone this repo to a directory of your own choice if you want to use the example contract code here. 41 | ### What is Chaincode as Service? 42 | 43 | The chaincode-as-a-service feature is a very useful and practical way to run 'Smart Contracts'. Traditionally the Fabric Peer has taken on the role of orchestrating the complete lifecycle of the chaincode. It required access to the Docker Daemon to create images, and start containers. Java, Node.js and Go chaincode frameworks were explicitly known to the peer including how they should be built and started. 44 | 45 | As a result this makes it very hard to deploy into Kubernetes (K8S) style environments, or to run in any form of debug mode. Additionally, the code is being rebuilt by the peer therefore there is some degree of uncertainty about what dependencies have been pulled in. 46 | 47 | Chaincode-as-service requires you to orchestrate the build and deployment phase yourself. Whilst this is an additional step, it gives control back. The Peer still requires a 'chaincode package' to be installed. In this case this doesn't contain code, but the information about where the chaincode is hosted. (Hostname,Port,TLS config etc) 48 | 49 | #### Fabric v2.4.1 Improvements 50 | 51 | We need to use the latest 2.4.1 release as this contains some improvements to make this process easier. The core functionality is available in earlier releases but requires more configuration. 52 | 53 | - The docker image for the peer contains a builder for chaincode-as-a-service preconfigured. This is named 'ccaasbuilder'. This removes the need to build your own external builder and repackage and configure the peer 54 | - The `ccaasbuilder` applications are included in the binary tgz archive download for use in other circumstances. The `sampleconfig/core.yaml` is updated as well to refer to 'ccaasbuilder' 55 | - The 2.4.1 Java Chaincode release has been updated to remove the need to write a custom bootstrap main class, similar to the Node.js Chaincode. It is intended that this will be added to the go chaincode as well. 56 | 57 | ## Which Fabric deployment to use? 58 | 59 | The core Peer, Orderer and Certificate Authority binaries can be deployed in many different configurations, for example 60 | 61 | - VSCode Extension, using it's built in templates for Microfab. All the Fabric binaries within 1 docker container 62 | - Running Microfab standalone from the extensions 63 | - Fabric's test network found within the fabric-samples repo 64 | - [_test-network-k8s?_] 65 | - [_ibp?_] 66 | - [_minifab?_] 67 | 68 | ## Creating the Smart Contracts 69 | 70 | An important point is that the code written for the Smart Contract is exactly the same, whether it's managed by the peer or Chaincode-as-a-Service. What is different is how that is started and packaged. This repo contains a chaincode for each language, these are copied from the Fabric-Samples repo. Note that in all cases, the Java/Typescript/Go code is the same, the difference is in the packaging. 71 | 72 | ### TypeScript/JavaScript 73 | 74 | Using the Typescript contract as an example, the difference is easier to see. The package.json contains 4 'start' commands 75 | 76 | ``` 77 | "start": "fabric-chaincode-node start", 78 | "start:server-nontls": "fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID", 79 | "start:server": "fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem", 80 | "start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID" 81 | ``` 82 | 83 | The first, is used when the peer is completely controlling the chaincode. The second `start:server-nontls` starts in the Chaincode-as-a-service mode (without using TLS). The command 84 | is very similar `fabric-chainmcode-node server` rather than `fabric-chaincode-node start`. Two options are provided here, these are the network address the chaincode will listen on and it's id. (aside when the Peer runs the chaincode, it does pass extra options, but they aren't seen in the package.json) 85 | 86 | The third `start:server` adds the required TLS configuration, but is otherwise the same. 87 | The forth `start:server-debug` is the same as the non-TLS case, but includes the environment variable required to get Node.js to open a port to allow a debugger to connect remotly. 88 | 89 | ### Java 90 | 91 | The changes for the Java chaincode are logically the same. The build.gradle (or use Maven if you wish) is exactly the same (like there were no changes to the TypeScript compilation). With the v2.4.1 Java Chaincode libraries, there are no code changes to make or build changes. The '-as-a-service' mode will be used if the environment variable `CHAINCODE_SERVER_ADDRESS` is set. 92 | 93 | For the non-TLS case the Java chaincode is started with `java -jar /chaincode.jar` - and will use the Chaincode-as-a-service mode _if_ the environment variable `CHAINCODE_SERVER_ADDRESS` is set. 94 | 95 | For the TLS case .[_to be added_] 96 | 97 | For debug, the JVM needs to put into debug mode `java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar` 98 | 99 | ### go 100 | 101 | [_go to follow_] 102 | 103 | 104 | ## How is the chaincode package different? 105 | 106 | A key difference is that the chaincode package, does not contain code. It is used as a holder of data that indicates to the peer where teh chaincode is. What host/port and what TLS configuration is needed. Chaincode packages already can hold data about the couchdn indicies to use or the private data collections. 107 | 108 | Within the package the `connection.json` is a key file. At it's simplest it would be 109 | 110 | ```json 111 | { 112 | "address": "assettransfer_ccaas:9999", 113 | "dial_timeout": "10s", 114 | "tls_required": false 115 | } 116 | ``` 117 | 118 | This is telling the peer the chaincode is on host `assettransfer_ccaas` port 9999. 10s timeout on connecting, and tls is not needed. 119 | 120 | The packager can be constructed by hand, it's a set of json files, collected together with `tgz`. To help there is [bash script](./contracts/ccaas_pkg.sh) to create the package in this repo. 121 | 122 | ### Important networking warning 123 | 124 | The chaincode package that is installed critically contains the hostname and port that the peer is expecting the chaincode to listening on. If nothing answers the peer, it obviously will fail the transaction. 125 | 126 | Note that it is ok not to have the chaincode running at all times, the peer won't complain until it is asked to actually connect to the chaincode. This is an important ability as it let's debug, and restart the container. 127 | 128 | The hostname that is supplied must be something that the peer, from it's perspective can resolve. Typically the peer will be inside a docker container, therefore suppling `localhost` or `127.0.0.1` will resolve to the same container the the peer is running in. 129 | 130 | Assuming that the peer is running in a docker container, the chaincode could either be run in it's down docker container, on the same docker network as the peers container, or it could be run directly on the host system. 131 | 132 | Depending your host OS, the 'specialhostname' that is used from within the docker container to access the host varies. For example see this [stackoverflow post](https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach#:~:text=To%20access%20host%20machine%20from,using%20it%20to%20anything%20else.&text=Then%20make%20sure%20that%20you,0.0%20.) 133 | 134 | The advantage of this is the chaincode can run locally on your hostmachine and is simple to conenct to from a debugger. 135 | 136 | Alternatively, you can package the chaincode into it's own docker container, and run that. You can still debug into this, but need to ensure that the ports of the container are exposed correctly for your language runtime. 137 | 138 | The first practical will show the approach of using the 'specialhostname', the other examples show how to use a docker container. Neither Fabric, or the Chaincode or VSCode really care which approach is used, so long as the network connections can resolve themselves. 139 | 140 | ## The Practical - with VSCode's built-in Microfab 141 | 142 | Firstly using VSCode and MicroFab; the VSCode extension can create a simple Fabric network in Microfab for you. 143 | 144 | > Please do read this section as the later stages are very much the same for all the ways of running fabric. 145 | 146 | ### Use the latest Microfab version 147 | 148 | You need to use the latest microfab version; here I'm using a copy locally rebuilt and tagged with `ibmcom/ibp-microfab-rc`. 149 | 150 | In a new VSCode window, open the settings and find the Blockchain Configuration. Enter the name of the "Custom Local Environment Start Image", and select the Enable Custom Local Environment Start Image" 151 | 152 | ![](_docs/vscode-001.png) 153 | 154 | ### Running and deploying a contract 155 | 156 | Add the repo to the workspace, and in a terminal window (either inside or outside VSCode to your preference) change to contract directory, and compile the code. 157 | 158 | First we need to `npm install` and `npm build` the chaincode 159 | 160 | ```bash 161 | cd contracts/asset-transfer-ts 162 | npm install 163 | npm run build 164 | ``` 165 | 166 | This has built the typescript contract. Debug settings have already been included in the typescript configuration. If you get issues with later debugging, check the typescript configuration in `tsconfig.json` 167 | 168 | ### Create the Chaincode Package 169 | 170 | In common with the traditional ways of deploying chaincodes (containing SmartContracts) we need to have a chaincode package. In this case however this package primarily contains the host:port of where the chaincode is running. Currently the peer command can't properly create these, so a batch file has been included that does it. 171 | 172 | To create a package 173 | 174 | ```bash 175 | npm run package:cc 176 | 177 | > asset-transfer-basic@1.0.0 package:cc 178 | > ../ccaas_pkg.sh 179 | 180 | -rw-r--r-- 1 matthew matthew 461 Jan 27 15:25 asset-transfer.tar.gz 181 | CHAINCODE_ID=asset-transfer_1:5206e43f17e0532a67480d7a384dd402da15f23a713e75db5b5ef898627315a9 182 | ``` 183 | 184 | ### Start the local Fabric Network 185 | 186 | In the 'Fabric Environments' panel, select the `Add local or remote environment` and choose `Create new from template` and select the first opion - the 1 Org template. Enter a name of your choice, here `nx01`, and you must select the `v2_0` capability. 187 | 188 | ### Install the chaincode package 189 | 190 | In VSCode, click on the Blockchain Icon to get into the Blockchain View. From the Smart Contracts panel, import the `asset-transfer-tar.gz`. In this example this has already been imported 191 | 192 | ![](_docs/vscode-002.png) 193 | 194 | Then from the FabricEnvironment panel, click on the just created environment, and expand the 'channel, so see the `+ Deploy smart contract` option. Select the packaged smart contract, and accept all the default options. 195 | 196 | WHen this has deployed, you'll be able to connect to the Channel in the `Fabric Gateways` and expand the channel and see the contract. However if click on the contract, nothing will happen - and in fact in the log there will be an error. The chaincode isn't actually running at this point. 197 | 198 | 199 | ### Running the chaincode 200 | 201 | You can run the chaincode directly from a terminal, you'll need the `CHAINCODE_ID` from earlier. 202 | 203 | ``` 204 | export CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 205 | export CHAINCODE_ID=assettransfer_1:b4626214ee5d9a1425f938938905cd91e5b37a6b835553630ace71f0397a1025 206 | npm run start:server-debug 207 | ``` 208 | 209 | This will start in Chaincode-as-a-Service mode, with the node.js debug options. You can now go back to the `Fabric Gateways` view, and refresh. You'll be able to see all the transaction functions listed now. To get these the VSCode Extension sent a transaction to the chaincode, and it responded with information. 210 | 211 | ### Debugging the chaincode 212 | 213 | At this point we can issue transactions via the extension to test how things are working. As the chaincode is running separate from the peer's control it's a *lot* easier to attach say a debugger to it. 214 | 215 | In the right window, is the `assetTransfer.ts` file, with a breakpoint added in `InitLedger` (click in the margin to add the breakpoint). By clicking on the InitLedger in the edxpanded Fabric Gateway view, brings up the 'transaction exerciser'. 216 | 217 | ![](_docs/vscode-debug.png) 218 | 219 | To connect the debugger, click on the VSCode debugging icon in the toolbar. You'll need to have a configuration for attaching to a remote node process. 220 | ``` 221 | { 222 | "name": "Attach by Process ID", 223 | "processId": "${command:PickProcess}", 224 | "request": "attach", 225 | "skipFiles": [ 226 | "/**" 227 | ], 228 | "type": "pwa-node" 229 | } 230 | ``` 231 | 232 | Check the VSCode documentation on debugging for more info on how to connect - the chaincode here is, as far as VSCode is concerned, no different from any other node.js application. 233 | 234 | Once connected, in the transaction explorer view, `InitLedger` is already selected, and it doesn't need any arguments. Click on `SubmitTransaction`, and you'll find the debugger will stop at the set break point and you can step over the code. 235 | 236 | It's advisable not to take too long, as there is a transaction timeout! 237 | 238 | When you've identified any problems, and made code changes the approach is to rebuild the code, kill the running node.js process and re run it. 239 | 240 | > You don't need to install, approve or commit again. Just stop, rebuild and restart the chaincode. 241 | 242 | ## Practical - with Fabric's Test Network 243 | The `test-network` and some of the chaincodes have been updated to support running chaincode-as-a-service. The commands below assume that you've got the latest fabric-samples cloned, along with the latest Fabric docker images. 244 | 245 | It's useful to have two terminal windows open, one for starting the Fabric Network, and a second for monitoring all the docker containers. 246 | 247 | In your 'monitoring' window, run this to watch all activity from the all the docker containers on the `fabric_test` network; this will monitor all the docker containers that are added to the `fabric-test` network. The network is usually created by the `./network.sh up` command, so remember to delay running this until at least the network is created. It is possible to precreate the network with `docker network create fabric-test` if you wish. 248 | 249 | ```bash 250 | # from the fabric-samples repo 251 | ./test-network/monitordocker.sh 252 | ``` 253 | 254 | In the 'Fabric Network' window, start the test network 255 | 256 | ```bash 257 | cd test-network 258 | ./network.sh up createChannel 259 | ``` 260 | 261 | You can run other variants of this command, eg to use CouchDB or CAs, without affecting the '-as-a-service' feature. The three keys steps are: 262 | 263 | - Build a docker image of the contract. Both `/asset-transfer-basic/chaincode-typescript` and `/asset-transfer-basic/chaincode-java` have been updated with Dockerfiles 264 | - Install, Approve and Commit a chaincode definition. This is unchanged, but the chaincode package contains connection information (hostname,port,tls certificates etc.), not code 265 | - Start the docker container(s) containing the contract 266 | 267 | Note that the order listed isn't mandatory. The key thing is that the containers are running before the first transaction is set by the peer. Remember that this could be on the `commit` if the `initRequired` flag is set. 268 | 269 | This sequence can be run as follows 270 | 271 | ```bash 272 | ./network.sh deployCCAAS -ccn basicts -ccp ../asset-transfer-basic/chaincode-typescript 273 | ``` 274 | 275 | This is very similar to the `deployCC` command, it needs the name, and path. But also needs to have the port the chaincode container is going use. As each container is on the `fabric-test` network, you might wish to alter this so there are no collisions with other chaincode containers. 276 | 277 | You should be able to see the contract starting in the monitoring window. There will be two containers running, one for org1 and one for org2. The container names contain the organization/peer and the name of the chaincode. 278 | 279 | To test things are working you can invoke the 'Contract Metadata' function. For information on how to work as different organizations see [Interacting with the network](https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html#interacting-with-the-network) 280 | 281 | ```bash 282 | # Environment variables for Org1 283 | 284 | export CORE_PEER_TLS_ENABLED=true 285 | export CORE_PEER_LOCALMSPID="Org1MSP" 286 | export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem 287 | export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp 288 | export CORE_PEER_ADDRESS=localhost:7051 289 | export PATH=${PWD}/../bin:$PATH 290 | export FABRIC_CFG_PATH=${PWD}/../config 291 | 292 | # invoke the function 293 | peer chaincode query -C mychannel -n basicts -c '{"Args":["org.hyperledger.fabric:GetMetadata"]}' | jq 294 | ``` 295 | 296 | If you don't have `jq` installed omit `| jq`. The metadata shows the details of the deployed contract and is JSON, so jq makes it easier to read. You can repeat the above commands for org2 to confirm that is working. 297 | 298 | To run the Java example, change the `deployCCAAS` command as follows, This will create two new containers. 299 | 300 | ```bash 301 | ./network.sh deployCCAAS -ccn basicj -ccp ../asset-transfer-basic/chaincode-java 302 | ``` 303 | 304 | ### Troubleshooting 305 | 306 | If the JSON structure passed in is badly formatted JSON this error will be in the peer log: 307 | 308 | ``` 309 | ::Error: Failed to unmarshal json: cannot unmarshal string into Go value of type map[string]interface {} command=build 310 | ``` 311 | 312 | ## How to configure each language 313 | 314 | Each language can work in the '-as-a-service' mode. Note that the approaches here are based on the very latest libraries. 315 | When starting the image you can also specify any of the TLS options or additional logging options for the respective chaincode libraries. 316 | 317 | ### Java 318 | 319 | With the v2.4.1 Java Chaincode libraries, there are no code changes to make or build changes. The '-as-a-service' mode will be used if the environment variable `CHAINCODE_SERVER_ADDRESS` is set. 320 | 321 | A sample docker run command could be as follows. The two key variables that are needed are the `CHAINCODE_SERVER_ADDRESS` and `CORE_CHAICODE_ID_NAME` 322 | 323 | ```bash 324 | docker run --rm -d --name peer0org1_assettx_ccaas \ 325 | --network fabric_test \ 326 | -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 \ 327 | -e CORE_CHAINCODE_ID_NAME= \ 328 | assettx_ccaas_image:latest 329 | ``` 330 | 331 | ### Node.js 332 | 333 | For Node.js (JavaScript or TypeScript) chaincode, typically the `package.json` has `fabric-chaincode-node start` as the main start command. To run in the '-as-a-service' mode change this to `fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID` 334 | 335 | ## Debugging the Chaincode 336 | 337 | Running in the '-as-a-service' mode offers options, similar to how the Fabric 'dev' mode works on debugging code. The restrictions of the 'dev' mode don't apply. 338 | 339 | There is an option `-ccaasdocker false` that can be provided on the `deployCCAAS` command. This will _not_ build the docker image or start a docker container. It does output the commands it would have run. 340 | 341 | Run this command, and you'll see similar output 342 | 343 | ```bash 344 | ./network.sh deployCCAAS -ccn basicj -ccp ../asset-transfer-basic/chaincode-java -ccaasdocker false 345 | #.... 346 | Not building docker image; this the command we would have run 347 | docker build -f ../asset-transfer-basic/chaincode-java/Dockerfile -t basicj_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 ../asset-transfer-basic/chaincode-java 348 | #.... 349 | Not starting docker containers; these are the commands we would have run 350 | docker run --rm -d --name peer0org1_basicj_ccaas --network fabric_test -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 -e CHAINCODE_ID=basicj_1.0:59dcd73a14e2db8eab7f7683343ce27ac242b93b4e8075605a460d63a0438405 -e CORE_CHAINCODE_ID_NAME=basicj_1.0:59dcd73a14e2db8eab7f7683343ce27ac242b93b4e8075605a460d63a0438405 basicj_ccaas_image:latest 351 | ``` 352 | 353 | Depending on your directory, and what you need to debug you might need to adjust these commands. 354 | 355 | ### Building the docker image 356 | 357 | The first thing needed is to build the docker image. Remember that so long as the peer can connect to the hostname:port given in the `connection.json` the actual packaging of the chaincode is not important to the peer. You are at liberty to adjust the dockerfiles given hgere. 358 | 359 | To manually build the docker image for the `asset-transfer-basic/chaincode-java` 360 | 361 | ```bash 362 | docker build -f ../asset-transfer-basic/chaincode-java/Dockerfile -t basicj_ccaas_image:latest --build-arg CC_SERVER_PORT=9999 ../asset-transfer-basic/chaincode-java 363 | ``` 364 | 365 | ### Starting the docker container 366 | 367 | You need to start the docker container. 368 | 369 | NodeJs for example, could be started like this 370 | 371 | ```bash 372 | docker run --rm -it -p 9229:9229 --name peer0org2_basic_ccaas --network fabric_test -e DEBUG=true -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 -e CHAINCODE_ID=basic_1.0:7c7dff5cdc43c77ccea028c422b3348c3c1fb5a26ace0077cf3cc627bd355ef0 -e CORE_CHAINCODE_ID_NAME=basic_1.0:7c7dff5cdc43c77ccea028c422b3348c3c1fb5a26ace0077cf3cc627bd355ef0 basic_ccaas_image:latest 373 | ``` 374 | 375 | Java for example, could be started like this 376 | 377 | ```bash 378 | docker run --rm -it --name peer0org1_basicj_ccaas -p 8000:8000 --network fabric_test -e DEBUG=true -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 -e CHAINCODE_ID=basicj_1.0:b014a03d8eb1898535e25b4dfeeb3f8244c9f07d91a06aec03e2d19174c45e4f -e CORE_CHAINCODE_ID_NAME=basicj_1.0:b014a03d8e 379 | b1898535e25b4dfeeb3f8244c9f07d91a06aec03e2d19174c45e4f basicj_ccaas_image:latest 380 | ``` 381 | 382 | For all languages please note: 383 | 384 | - the name of the container needs to match what the peer has in the `connection.json` 385 | - the peer is connecting to the chaincode container via the docker network. Therefore port 9999 does not need to be forwarded to the host 386 | - If you are going to single step in a debugger, then you are likely to hit the Fabric transaction timeout value. By default this is 30 seconds, meaning the chaincode has to complete transactions in 30 seconds or less. In the `test-network/docker/docker-composer-test-net.yml` add `CORE_CHAINCODE_EXECUTETIMEOUT=300s` to the environment options of each peer. 387 | - In the command above, the `-d` option has been removed from the command the test-network would have used, and has been replaced with `-it`. This means that docker container will not run in detached mode, and will run in the foreground. 388 | 389 | For Node.js please note: 390 | 391 | - Port 9229 is forwarded however - this is the debug port used by Node.js 392 | - `-e DEBUG=true` will trigger the node runtime to be started in debug mode. This is encoded in the `docker/docker-entrypoint.sh` script - this is an example and you may wish to remove this in production images for security 393 | - If you are using typescript, ensure that the typescript has been compiled with sourcemaps, otherwise a debugger will struggle matching up the source code. 394 | 395 | For Java please note: 396 | 397 | - Port 800 is forwarded, the debug port for the JVM 398 | - `-e DEBUG=true` will trigger the node runtime to be started in debug mode. This is encoded in the `docker/docker-entrypoint.sh` script - this is an example and you may wish to remove this in production images for security 399 | - In the java command with the option to start the debugger is `java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar` Note the `0.0.0.0` as the debug port needs to be bound to all network adapters so the debugger can be attached from outside the container 400 | 401 | ## Running with multiple peers 402 | 403 | In the traditional approach, each peer that the chaincode is approved on will have a container running the chaincode. With the '-as-a-service' approach we need to achieve the same architecture. 404 | 405 | As the `connection.json` contains the address of the running chaincode container, it can be updated to ensure that each peer connects to a different container. However the as the `connection.json` in the chaincode package, Fabric mandates that the package id is consistent amongst all peers in an organization. To achieve that 406 | the the external builder supports a template capability. The context from this template is taken from an environment variable set on each Peer. `CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG` 407 | 408 | We can define the address to be a template in the `connection.json` 409 | 410 | ```json 411 | { 412 | "address": "{{.peername}}_assettransfer_ccaas:9999", 413 | "dial_timeout": "10s", 414 | "tls_required": false 415 | } 416 | ``` 417 | 418 | In the peer's environment configuration we then set for org1's peer1 419 | 420 | ```bash 421 | CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG="{\"peername\":\"org1peer1\"}" 422 | ``` 423 | 424 | The external builder will then resolve this address to be `org1peer1_assettransfer_ccaas:9999` for the peer to use. 425 | 426 | Each peer can have their own separate configuration, and therefore different addresses. The JSON string that is set can have any structure, so long as the templates (in golang template syntax) match. 427 | 428 | Any value in the `connection.json` can be templated - but only the values and not the keys.**** 429 | ## Practical - with standalone Microfab 430 | 431 | 432 | In this demo scenario, we'll be running two docker containers. As these need to interact we need to create a dedicated docker network 433 | 434 | ```bash 435 | docker network create audit_network 436 | ``` 437 | 438 | Startup the Fabric Infrastructure, we're using MicroFab here as it's a single container and fast to start. Plus it already has the configuration required within it to start external chaincodes. 439 | 440 | ```bash 441 | export MICROFAB_CONFIG='{ 442 | "endorsing_organizations":[ 443 | { 444 | "name": "Ampretia" 445 | } 446 | ], 447 | "channels":[ 448 | { 449 | "name": "auditnet", 450 | "endorsing_organizations":[ 451 | "Ampretia" 452 | ] 453 | } 454 | ], 455 | "capability_level":"V2_0" 456 | }' 457 | 458 | docker run --name microfab --network audit_network --rm -ti -p 8080:8080 -e MICROFAB_CONFIG="${MICROFAB_CONFIG}" ibmcom/ibp-microfab 459 | 460 | ``` 461 | 462 | --------------------------------------------------------------------------------