mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
Merge branch 'hyperledger:main' into main
This commit is contained in:
commit
1eb85e360d
1111 changed files with 171719 additions and 0 deletions
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{mj,cj,j,t}s]
|
||||
indent_size = 4
|
||||
quote_type = single
|
||||
|
||||
[*.json]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
53
.github/actions/fsat-setup/action.yaml
vendored
Normal file
53
.github/actions/fsat-setup/action.yaml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Set up the Full Stack Asset Transfer Guide Dependencies
|
||||
description: Set up the Full Stack Asset Transfer Guide Dependencies
|
||||
inputs:
|
||||
node-version:
|
||||
description: Version of node
|
||||
default: "lts/*"
|
||||
just-version:
|
||||
description: Just Version
|
||||
default: "1.43.0"
|
||||
k9s-version:
|
||||
description: k9s Version
|
||||
default: v0.50.15
|
||||
fabric-version:
|
||||
description: Version of Hyperledger Fabric
|
||||
default: "2.5.15"
|
||||
ca-version:
|
||||
description: Version of Hyperledger Fabric CA
|
||||
default: "1.5.15"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
|
||||
- name: Install k9s
|
||||
shell: bash
|
||||
run: |
|
||||
curl --fail --silent --show-error -L https://github.com/derailed/k9s/releases/download/${{ inputs.k9s-version }}/k9s_Linux_amd64.tar.gz -o /tmp/k9s_Linux_amd64.tar.gz
|
||||
tar -zxf /tmp/k9s_Linux_amd64.tar.gz -C /usr/local/bin k9s
|
||||
sudo chown root /usr/local/bin/k9s
|
||||
sudo chmod 755 /usr/local/bin/k9s
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: just@${{ inputs.just-version }}
|
||||
|
||||
- name: Install weft
|
||||
shell: bash
|
||||
run: |
|
||||
npm install -g @hyperledger-labs/weft
|
||||
|
||||
- name: Install fabric CLI
|
||||
shell: bash
|
||||
working-directory: full-stack-asset-transfer-guide
|
||||
run: |
|
||||
curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh \
|
||||
| bash -s -- binary --fabric-version ${{ inputs.fabric-version }} --ca-version ${{ inputs.ca-version }}
|
||||
echo ${PWD}/bin >> $GITHUB_PATH
|
||||
63
.github/actions/test-network-setup/action.yaml
vendored
Normal file
63
.github/actions/test-network-setup/action.yaml
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
name: Set up Test Network Runner
|
||||
description: Set up the Test Network Runtime
|
||||
inputs:
|
||||
go-version:
|
||||
description: Version of go
|
||||
default: stable
|
||||
node-version:
|
||||
description: Version of node
|
||||
default: "lts/*"
|
||||
java-version:
|
||||
description: Version of JDK
|
||||
default: 25.x
|
||||
fabric-version:
|
||||
description: Version of Hyperledger Fabric
|
||||
default: 2.5.15
|
||||
ca-version:
|
||||
description: Version of Hyperledger Fabric CA
|
||||
default: 1.5.15
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
cache-dependency-path: "**/go.sum"
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: ${{ inputs.java-version }}
|
||||
cache: gradle
|
||||
|
||||
- name: Install fabric CLI
|
||||
shell: bash
|
||||
run: |
|
||||
curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh \
|
||||
| bash -s -- binary --fabric-version ${{ inputs.fabric-version }} --ca-version ${{ inputs.ca-version }}
|
||||
echo ${PWD}/bin >> $GITHUB_PATH
|
||||
|
||||
- name: Pull Fabric Docker Images
|
||||
shell: bash
|
||||
run: |
|
||||
curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh \
|
||||
| bash -s -- docker --fabric-version ${{ inputs.fabric-version }} --ca-version ${{ inputs.ca-version }}
|
||||
|
||||
- name: Pull chaincode container images
|
||||
shell: bash
|
||||
run: |
|
||||
docker pull ghcr.io/hyperledger/fabric-nodeenv:2.5
|
||||
docker tag ghcr.io/hyperledger/fabric-nodeenv:2.5 hyperledger/fabric-nodeenv:2.5
|
||||
docker pull ghcr.io/hyperledger/fabric-javaenv:2.5
|
||||
docker tag ghcr.io/hyperledger/fabric-javaenv:2.5 hyperledger/fabric-javaenv:2.5
|
||||
|
||||
- name: Install retry CLI
|
||||
shell: bash
|
||||
run: curl -sSL https://raw.githubusercontent.com/kadwanev/retry/master/retry -o ./bin/retry && chmod +x ./bin/retry
|
||||
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
14
.github/settings.yml
vendored
Normal file
14
.github/settings.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
repository:
|
||||
name: fabric-samples
|
||||
description: Samples for Hyperledger Fabric
|
||||
homepage: https://lf-hyperledger.atlassian.net/wiki/spaces/fabric
|
||||
default_branch: main
|
||||
has_downloads: true
|
||||
has_issues: true
|
||||
has_projects: false
|
||||
has_wiki: false
|
||||
archived: false
|
||||
private: false
|
||||
allow_squash_merge: true
|
||||
allow_merge_commit: false
|
||||
allow_rebase_merge: true
|
||||
65
.github/workflows/lint.yaml
vendored
Normal file
65
.github/workflows/lint.yaml
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Lint 🎉
|
||||
run-name: ${{ github.actor }} is linting fabric-samples 🎉
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ["main", "release-2.5"]
|
||||
pull_request:
|
||||
branches: ["main", "release-2.5"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VER: "lts/*"
|
||||
JAVA_VER: 25.x
|
||||
|
||||
jobs:
|
||||
go:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
- uses: actions/checkout@v6
|
||||
- run: go install golang.org/x/tools/cmd/goimports@latest
|
||||
- run: ci/scripts/lint-go.sh
|
||||
|
||||
typescript:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
- run: ci/scripts/lint-typescript.sh
|
||||
|
||||
javascript:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
- run: ci/scripts/lint-javascript.sh
|
||||
|
||||
java:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: ${{ env.JAVA_VER }}
|
||||
- run: ci/scripts/lint-java.sh
|
||||
|
||||
shell:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: ci/scripts/lint-shell.sh
|
||||
58
.github/workflows/rest-sample.yaml
vendored
Normal file
58
.github/workflows/rest-sample.yaml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: REST Sample 🐧
|
||||
run-name: ${{ github.actor }} is testing the REST Sample 🐧
|
||||
|
||||
env:
|
||||
NODE_VER: "lts/*"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ["main", "release-2.5"]
|
||||
paths: ["asset-transfer-basic/rest-api-typescript/**"]
|
||||
pull_request:
|
||||
branches: ["main", "release-2.5"]
|
||||
paths: ["asset-transfer-basic/rest-api-typescript/**"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-sample:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
|
||||
- name: Install REST Sample Dependencies
|
||||
working-directory: asset-transfer-basic/rest-api-typescript
|
||||
run: npm install
|
||||
|
||||
- name: Build REST Sample Application
|
||||
run: npm run build
|
||||
working-directory: asset-transfer-basic/rest-api-typescript
|
||||
|
||||
- name: Test REST Sample Application
|
||||
run: npm test
|
||||
working-directory: asset-transfer-basic/rest-api-typescript
|
||||
|
||||
- name: Build REST Sample Docker Image
|
||||
run: docker build -t ghcr.io/hyperledger/fabric-rest-sample .
|
||||
working-directory: asset-transfer-basic/rest-api-typescript
|
||||
|
||||
- name: Publish REST Sample Docker Image
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main')
|
||||
run: |
|
||||
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
docker push ghcr.io/hyperledger/fabric-rest-sample:latest
|
||||
working-directory: asset-transfer-basic/rest-api-typescript
|
||||
59
.github/workflows/test-fsat.yaml
vendored
Normal file
59
.github/workflows/test-fsat.yaml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
name: Full Stack Asset Transfer Guide 🚀
|
||||
run-name: ${{ github.actor }} is testing the Full Stack Asset Transfer Guide 🚀
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release-2.5"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ansible:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Full Stack Runtime
|
||||
uses: ./.github/actions/fsat-setup
|
||||
- run: just test-ansible
|
||||
working-directory: full-stack-asset-transfer-guide
|
||||
|
||||
appdev:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Full Stack Runtime
|
||||
uses: ./.github/actions/fsat-setup
|
||||
- run: just test-appdev
|
||||
working-directory: full-stack-asset-transfer-guide
|
||||
|
||||
chaincode:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Full Stack Runtime
|
||||
uses: ./.github/actions/fsat-setup
|
||||
- run: just test-chaincode
|
||||
working-directory: full-stack-asset-transfer-guide
|
||||
|
||||
cloud:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Full Stack Runtime
|
||||
uses: ./.github/actions/fsat-setup
|
||||
- run: just test-cloud
|
||||
working-directory: full-stack-asset-transfer-guide
|
||||
|
||||
console:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Full Stack Runtime
|
||||
uses: ./.github/actions/fsat-setup
|
||||
- run: just test-console
|
||||
working-directory: full-stack-asset-transfer-guide
|
||||
49
.github/workflows/test-high-throughput.yaml
vendored
Normal file
49
.github/workflows/test-high-throughput.yaml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test High Throughput
|
||||
run-name: ${{ github.actor }} is running the High Throughput tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ["main", "release-2.5"]
|
||||
pull_request:
|
||||
branches: ["main", "release-2.5"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
basic:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Start Fabric
|
||||
working-directory: high-throughput
|
||||
run: ./startFabric.sh
|
||||
|
||||
- name: Test High Throughput
|
||||
working-directory: high-throughput/application-go
|
||||
run: |
|
||||
go run . manyUpdates testvar1 100 +
|
||||
go run . prune testvar1
|
||||
go run . get testvar1
|
||||
go run . update testvar1 100 +
|
||||
go run . get testvar1
|
||||
go run . delete testvar1
|
||||
go run . manyUpdatesTraditional testvar2 100 +
|
||||
go run . getstandard testvar2
|
||||
go run . updatestandard testvar2 100 +
|
||||
go run . getstandard testvar2
|
||||
go run . delstandard testvar2
|
||||
|
||||
- name: Stop Fabric
|
||||
working-directory: high-throughput
|
||||
run: ./networkDown.sh
|
||||
39
.github/workflows/test-network-basic.yaml
vendored
Normal file
39
.github/workflows/test-network-basic.yaml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network Basic 🔎
|
||||
run-name: ${{ github.actor }} is running the Test Network Basic tests 🔎
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
basic:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
- javascript
|
||||
- typescript
|
||||
- java
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Run Test Network Basic
|
||||
working-directory: test-network
|
||||
run: ../ci/scripts/run-test-network-basic.sh
|
||||
env:
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
49
.github/workflows/test-network-bft-orderer.yaml
vendored
Normal file
49
.github/workflows/test-network-bft-orderer.yaml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network BFT Orderer 🍟
|
||||
run-name: ${{ github.actor }} is running the Test Network with BFT Orderer tests 🍟
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
basic:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
- javascript
|
||||
- typescript
|
||||
- java
|
||||
crypto:
|
||||
- cryptogen
|
||||
- ca
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
# Note: The default Fabric version for CI is currently the latest LTS (v2.5.x).
|
||||
# To test BFT Orderers, Fabric v3.x is explicitly specified here.
|
||||
with:
|
||||
fabric-version: 3.1.4
|
||||
|
||||
- name: Run Test Network with BFT Orderers
|
||||
working-directory: test-network
|
||||
run: ../ci/scripts/run-test-network-basic.sh
|
||||
env:
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
ORDERER_TYPE: bft
|
||||
CRYPTO: ${{ matrix.crypto }}
|
||||
41
.github/workflows/test-network-events.yaml
vendored
Normal file
41
.github/workflows/test-network-events.yaml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network Events 💡
|
||||
run-name: ${{ github.actor }} is running the Test Network Events tests 💡
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
events:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
- javascript
|
||||
- java
|
||||
chaincode-name:
|
||||
- events
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Run Test
|
||||
working-directory: test-network
|
||||
run: ../ci/scripts/run-test-network-events.sh
|
||||
env:
|
||||
CHAINCODE_NAME: ${{ matrix.chaincode-name }}
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
51
.github/workflows/test-network-hsm.yaml
vendored
Normal file
51
.github/workflows/test-network-hsm.yaml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network HSM 🍏
|
||||
run-name: ${{ github.actor }} is running the Test Network HSM tests 🍏
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
hsm:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
- javascript
|
||||
- typescript
|
||||
- java
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Install SoftHSM
|
||||
run: sudo apt install -y softhsm2
|
||||
|
||||
- name: Set up SoftHSM
|
||||
env:
|
||||
TMPDIR: ${{ runner.temp }}
|
||||
SOFTHSM2_CONF: ${{ github.workspace }}/softhsm2.conf
|
||||
run: |
|
||||
echo "directories.tokendir = ${TMPDIR}" > "${SOFTHSM2_CONF}"
|
||||
softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234
|
||||
|
||||
- name: Run Test Network HSM
|
||||
working-directory: test-network
|
||||
env:
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
SOFTHSM2_CONF: ${{ github.workspace }}/softhsm2.conf
|
||||
run: ../ci/scripts/run-test-network-hsm.sh
|
||||
124
.github/workflows/test-network-k8s.yaml
vendored
Normal file
124
.github/workflows/test-network-k8s.yaml
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Kubernetes Test Network 🍒
|
||||
run-name: ${{ github.actor }} is testing the Kubernetes Test Network 🍒
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ccaas-java:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Test the network
|
||||
id: run-test
|
||||
working-directory: test-network-k8s
|
||||
run: ../ci/scripts/run-k8s-test-network-basic.sh
|
||||
env:
|
||||
CLIENT_LANGUAGE: typescript
|
||||
CHAINCODE_LANGUAGE: java
|
||||
- name: Upload failure logs
|
||||
if: ${{ failure() && steps.run-test.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ github.job }}-logs
|
||||
path: test-network-k8s/network-debug.log
|
||||
|
||||
ccaas-external:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Test the network
|
||||
id: run-test
|
||||
working-directory: test-network-k8s
|
||||
run: ../ci/scripts/run-k8s-test-network-basic.sh
|
||||
env:
|
||||
CLIENT_LANGUAGE: typescript
|
||||
CHAINCODE_LANGUAGE: external
|
||||
- name: Upload failure logs
|
||||
if: ${{ failure() && steps.run-test.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ github.job }}-logs
|
||||
path: test-network-k8s/network-debug.log
|
||||
|
||||
k8s-builder:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Test the network
|
||||
id: run-test
|
||||
working-directory: test-network-k8s
|
||||
run: ../ci/scripts/run-k8s-test-network-basic.sh
|
||||
env:
|
||||
CHAINCODE_NAME: basic
|
||||
CHAINCODE_LANGUAGE: java
|
||||
CHAINCODE_BUILDER: k8s
|
||||
- name: Upload failure logs
|
||||
if: ${{ failure() && steps.run-test.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ github.job }}-logs
|
||||
path: test-network-k8s/network-debug.log
|
||||
|
||||
multi-namespace:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Test the network
|
||||
id: run-test
|
||||
working-directory: test-network-k8s
|
||||
run: ../ci/scripts/run-k8s-test-network-basic.sh
|
||||
env:
|
||||
ORG0_NS: org0-namespace
|
||||
ORG1_NS: org1-namespace
|
||||
ORG2_NS: org2-namespace
|
||||
CHAINCODE_NAME: basic
|
||||
CHAINCODE_LANGUAGE: java
|
||||
CHAINCODE_BUILDER: k8s
|
||||
- name: Upload failure logs
|
||||
if: ${{ failure() && steps.run-test.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ github.job }}-logs
|
||||
path: test-network-k8s/network-debug.log
|
||||
|
||||
bft-orderer:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
# This job requires Fabric v3.0 or later, which is only supported on 'main'.
|
||||
# Ensure it does not run on 'release-2.5' or earlier versions.
|
||||
if: ${{ github.ref == 'refs/heads/main' || github.event.pull_request.base.ref == 'main' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Test the network
|
||||
id: run-test
|
||||
working-directory: test-network-k8s
|
||||
run: ../ci/scripts/run-k8s-test-network-basic.sh
|
||||
env:
|
||||
CLIENT_LANGUAGE: typescript
|
||||
CHAINCODE_LANGUAGE: java
|
||||
# Note: The default Fabric version for CI is currently the latest LTS (v2.5.x).
|
||||
# To test BFT Orderers, Fabric v3.x is explicitly specified here.
|
||||
FABRIC_VERSION: '3.1'
|
||||
ORDERER_TYPE: bft
|
||||
- name: Upload failure logs
|
||||
if: ${{ failure() && steps.run-test.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ github.job }}-logs
|
||||
path: test-network-k8s/network-debug.log
|
||||
41
.github/workflows/test-network-ledger.yaml
vendored
Normal file
41
.github/workflows/test-network-ledger.yaml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network Ledger 🥑
|
||||
run-name: ${{ github.actor }} is running the Test Network Ledger tests 🥑
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
basic:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
- javascript
|
||||
- typescript
|
||||
chaincode-name:
|
||||
- ledger
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Run Test
|
||||
working-directory: test-network
|
||||
run: ../ci/scripts/run-test-network-ledger.sh
|
||||
env:
|
||||
CHAINCODE_NAME: ${{ matrix.chaincode-name }}
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
39
.github/workflows/test-network-off-chain.yaml
vendored
Normal file
39
.github/workflows/test-network-off-chain.yaml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network Off Chain 🍔
|
||||
run-name: ${{ github.actor }} is running the Test Network Off Chain tests 🍔
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
off-chain:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
- javascript
|
||||
- typescript
|
||||
- java
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Run Test Network Off Chain
|
||||
working-directory: test-network
|
||||
env:
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
run: ../ci/scripts/run-test-network-off-chain.sh
|
||||
41
.github/workflows/test-network-private.yaml
vendored
Normal file
41
.github/workflows/test-network-private.yaml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network Private 🔒
|
||||
run-name: ${{ github.actor }} is running the Test Network Private tests 🔒
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ["main", "release-2.5"]
|
||||
pull_request:
|
||||
branches: ["main", "release-2.5"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
private:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
- java
|
||||
- typescript
|
||||
chaincode-name:
|
||||
- private
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Run Test
|
||||
working-directory: test-network
|
||||
run: ../ci/scripts/run-test-network-private.sh
|
||||
env:
|
||||
CHAINCODE_NAME: ${{ matrix.chaincode-name }}
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
40
.github/workflows/test-network-sbe.yaml
vendored
Normal file
40
.github/workflows/test-network-sbe.yaml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network SBE 🎵
|
||||
run-name: ${{ github.actor }} is running the Test Network SBE tests 🎵
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
SBE:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- typescript
|
||||
- java
|
||||
chaincode-name:
|
||||
- sbe
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Run Test
|
||||
working-directory: test-network
|
||||
run: ../ci/scripts/run-test-network-sbe.sh
|
||||
env:
|
||||
CHAINCODE_NAME: ${{ matrix.chaincode-name }}
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
39
.github/workflows/test-network-secured.yaml
vendored
Normal file
39
.github/workflows/test-network-secured.yaml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
name: Test Network Secured 🔔
|
||||
run-name: ${{ github.actor }} is running the Test Network Secured tests 🔔
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
pull_request:
|
||||
branches: [ "main", "release-2.5" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
secured:
|
||||
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
chaincode-language:
|
||||
- go
|
||||
chaincode-name:
|
||||
- secured
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up the test network runtime
|
||||
uses: ./.github/actions/test-network-setup
|
||||
|
||||
- name: Run Test
|
||||
working-directory: test-network
|
||||
run: ../ci/scripts/run-test-network-secured.sh
|
||||
env:
|
||||
CHAINCODE_NAME: ${{ matrix.chaincode-name }}
|
||||
CHAINCODE_LANGUAGE: ${{ matrix.chaincode-language }}
|
||||
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Emacs backup files
|
||||
*~
|
||||
*#
|
||||
.#*
|
||||
# Vim file artifacts
|
||||
.*.sw*
|
||||
.DS_Store
|
||||
.project
|
||||
# omit Go vendor directories
|
||||
vendor/
|
||||
.vscode
|
||||
.gradle
|
||||
.idea
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
# Ignore Gradle build output directory
|
||||
build
|
||||
# Ignore Maven build output directory
|
||||
target
|
||||
|
||||
# Eclipse
|
||||
.classpath
|
||||
.settings
|
||||
|
||||
# installed Fabric binaries etc.
|
||||
/bin/
|
||||
builders/
|
||||
config/
|
||||
external-chaincode/
|
||||
install-fabric.sh
|
||||
|
||||
# override the ignore of all config/ folders
|
||||
!full-stack-asset-transfer-guide/infrastructure/sample-network/config
|
||||
2660
CHANGELOG.md
Normal file
2660
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load diff
4
CODEOWNERS
Normal file
4
CODEOWNERS
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Fabric Samples Maintainers
|
||||
* @hyperledger/fabric-samples-maintainers
|
||||
8
CODE_OF_CONDUCT.md
Normal file
8
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
Code of Conduct Guidelines
|
||||
==========================
|
||||
|
||||
Please review the Hyperledger [Code of
|
||||
Conduct](https://lf-hyperledger.atlassian.net/wiki/spaces/HYP/pages/19595281/Hyperledger+Code+of+Conduct)
|
||||
before participating. It is important that we keep things civil.
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.
|
||||
18
CONTRIBUTING.md
Normal file
18
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
## Contributing
|
||||
|
||||
We welcome contributions to the Hyperledger Fabric Project in many forms, and
|
||||
there's always plenty to do!
|
||||
|
||||
Please visit the
|
||||
[contributors guide](http://hyperledger-fabric.readthedocs.io/en/latest/CONTRIBUTING.html) in the
|
||||
docs to learn how to make contributions to this exciting project.
|
||||
|
||||
## Code of Conduct Guidelines <a name="conduct"></a>
|
||||
|
||||
See our [Code of Conduct Guidelines](./CODE_OF_CONDUCT.md).
|
||||
|
||||
## Maintainers <a name="maintainers"></a>
|
||||
|
||||
Should you have any questions or concerns, please reach out to one of the project's [Maintainers](./MAINTAINERS.md).
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
14
MAINTAINERS.md
Normal file
14
MAINTAINERS.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
Maintainers
|
||||
===========
|
||||
|
||||
fabric-samples uses a non-author code review policy, requiring a single approval from a non-author maintainer.
|
||||
|
||||
| Name | GitHub | Discord ID | email |
|
||||
|---------------------------|------------------|------------------|-------------------------------------|
|
||||
| Dave Enyeart | denyeart | Dave Enyeart | enyeart@us.ibm.com |
|
||||
| Mark Lewis | bestbeforetoday | bestbeforetoday | Mark.S.Lewis@outlook.com |
|
||||
| Tatsuya Sato | satota2 | satota2 | tatsuya.sato.so@hitachi.com |
|
||||
|
||||
Also: Please see the [Release Manager section](https://github.com/hyperledger/fabric/blob/main/MAINTAINERS.md)
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.
|
||||
73
README.md
Normal file
73
README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
[//]: # (SPDX-License-Identifier: CC-BY-4.0)
|
||||
|
||||
# Hyperledger Fabric Samples
|
||||
|
||||
You can use Fabric samples to get started working with Hyperledger Fabric, explore important Fabric features, and learn how to build applications that can interact with blockchain networks using the Fabric SDKs. To learn more about Hyperledger Fabric, visit the [Fabric documentation](https://hyperledger-fabric.readthedocs.io/en/latest).
|
||||
|
||||
Note that this branch contains samples for the latest Fabric release. For older Fabric versions, refer to the corresponding branches:
|
||||
|
||||
- [release-2.2](https://github.com/hyperledger/fabric-samples/tree/release-2.2)
|
||||
- [release-1.4](https://github.com/hyperledger/fabric-samples/tree/release-1.4)
|
||||
|
||||
## Getting started with the Fabric samples
|
||||
|
||||
To use the Fabric samples, you need to download the Fabric Docker images and the Fabric CLI tools. First, make sure that you have installed all of the [Fabric prerequisites](https://hyperledger-fabric.readthedocs.io/en/latest/prereqs.html). You can then follow the instructions to [Install the Fabric Samples, Binaries, and Docker Images](https://hyperledger-fabric.readthedocs.io/en/latest/install.html) in the Fabric documentation. In addition to downloading the Fabric images and tool binaries, the Fabric samples will also be cloned to your local machine.
|
||||
|
||||
## Test network
|
||||
|
||||
The [Fabric test network](test-network) in the samples repository provides a Docker Compose based test network with two
|
||||
Organization peers and an ordering service node. You can use it on your local machine to run the samples listed below.
|
||||
You can also use it to deploy and test your own Fabric chaincodes and applications. To get started, see
|
||||
the [test network tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html).
|
||||
|
||||
The [Kubernetes Test Network](test-network-k8s) sample builds upon the Compose network, constructing a Fabric
|
||||
network with peer, orderer, and CA infrastructure nodes running on Kubernetes. In addition to providing a sample
|
||||
Kubernetes guide, the Kube test network can be used as a platform to author and debug _cloud ready_ Fabric Client
|
||||
applications on a development or CI workstation.
|
||||
|
||||
|
||||
## Asset transfer samples and tutorials
|
||||
|
||||
The asset transfer series provides a series of sample smart contracts and applications to demonstrate how to store and transfer assets using Hyperledger Fabric.
|
||||
Each sample and associated tutorial in the series demonstrates a different core capability in Hyperledger Fabric. The **Basic** sample provides an introduction on how
|
||||
to write smart contracts and how to interact with a Fabric network using the Fabric SDKs. The **Ledger queries**, **Private data**, and **State-based endorsement**
|
||||
samples demonstrate these additional capabilities. Finally, the **Secured agreement** sample demonstrates how to bring all the capabilities together to securely
|
||||
transfer an asset in a more realistic transfer scenario.
|
||||
|
||||
| **Smart Contract** | **Description** | **Tutorial** | **Smart contract languages** | **Application languages** |
|
||||
| -----------|------------------------------|----------|---------|---------|
|
||||
| [Basic](asset-transfer-basic) | The Basic sample smart contract that allows you to create and transfer an asset by putting data on the ledger and retrieving it. This sample is recommended for new Fabric users. | [Writing your first application](https://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html) | Go, JavaScript, TypeScript, Java | Go, TypeScript, Java |
|
||||
| [Ledger queries](asset-transfer-ledger-queries) | The ledger queries sample demonstrates range queries and transaction updates using range queries (applicable for both LevelDB and CouchDB state databases), and how to deploy an index with your chaincode to support JSON queries (applicable for CouchDB state database only). | [Using CouchDB](https://hyperledger-fabric.readthedocs.io/en/latest/couchdb_tutorial.html) | Go, JavaScript | Java, JavaScript |
|
||||
| [Private data](asset-transfer-private-data) | This sample demonstrates the use of private data collections, how to manage private data collections with the chaincode lifecycle, and how the private data hash can be used to verify private data on the ledger. It also demonstrates how to control asset updates and transfers using client-based ownership and access control. | [Using Private Data](https://hyperledger-fabric.readthedocs.io/en/latest/private_data_tutorial.html) | Go, TypeScript, Java | TypeScript |
|
||||
| [State-Based Endorsement](asset-transfer-sbe) | This sample demonstrates how to override the chaincode-level endorsement policy to set endorsement policies at the key-level (data/asset level). | [Using State-based endorsement](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-sbe) | Java, TypeScript | JavaScript |
|
||||
| [Secured agreement](asset-transfer-secured-agreement) | Smart contract that uses implicit private data collections, state-based endorsement, and organization-based ownership and access control to keep data private and securely transfer an asset with the consent of both the current owner and buyer. | [Secured asset transfer](https://hyperledger-fabric.readthedocs.io/en/latest/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) | Go | TypeScript |
|
||||
| [Events](asset-transfer-events) | The events sample demonstrates how smart contracts can emit events that are read by the applications interacting with the network. | [README](asset-transfer-events/README.md) | Go, JavaScript, Java | Go, TypeScript, Java |
|
||||
| [Attribute-based access control](asset-transfer-abac) | Demonstrates the use of attribute and identity based access control using a simple asset transfer scenario | [README](asset-transfer-abac/README.md) | Go | _None_ |
|
||||
|
||||
## Full stack asset transfer guide
|
||||
|
||||
The [full stack asset transfer guide](full-stack-asset-transfer-guide#readme) workshop demonstrates how a generic asset transfer solution for Hyperledger Fabric can be developed and deployed. This covers chaincode development, client application development, and deployment to a production-like environment.
|
||||
|
||||
## Additional samples
|
||||
|
||||
Additional samples demonstrate various Fabric use cases and application patterns.
|
||||
|
||||
| **Sample** | **Description** | **Documentation** |
|
||||
| -------------|------------------------------|------------------|
|
||||
| [Off chain data](off_chain_data) | Learn how to use block events to build an off-chain database for reporting and analytics. | [Peer channel-based event services](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html) |
|
||||
| [Token SDK](token-sdk) | Sample REST API around the Hyperledger Labs [Token SDK](https://github.com/hyperledger-labs/fabric-token-sdk) for privacy friendly (zero knowledge proof) UTXO transactions. | [README](token-sdk/README.md) |
|
||||
| [Token ERC-20](token-erc-20) | Smart contract demonstrating how to create and transfer fungible tokens using an account-based model. | [README](token-erc-20/README.md) |
|
||||
| [Token UTXO](token-utxo) | Smart contract demonstrating how to create and transfer fungible tokens using a UTXO (unspent transaction output) model. | [README](token-utxo/README.md) |
|
||||
| [Token ERC-1155](token-erc-1155) | Smart contract demonstrating how to create and transfer multiple tokens (both fungible and non-fungible) using an account based model. | [README](token-erc-1155/README.md) |
|
||||
| [Token ERC-721](token-erc-721) | Smart contract demonstrating how to create and transfer non-fungible tokens using an account-based model. | [README](token-erc-721/README.md) |
|
||||
| [High throughput](high-throughput) | Learn how you can design your smart contract to avoid transaction collisions in high volume environments. | [README](high-throughput/README.md) |
|
||||
| [Simple Auction](auction-simple) | Run an auction where bids are kept private until the auction is closed, after which users can reveal their bid. | [README](auction-simple/README.md) |
|
||||
| [Dutch Auction](auction-dutch) | Run an auction in which multiple items of the same type can be sold to more than one buyer. This example also includes the ability to add an auditor organization. | [README](auction-dutch/README.md) |
|
||||
|
||||
|
||||
## License <a name="license"></a>
|
||||
|
||||
Hyperledger Project source code files are made available under the Apache
|
||||
License, Version 2.0 (Apache-2.0), located in the [LICENSE](LICENSE) file.
|
||||
Hyperledger Project documentation files are made available under the Creative
|
||||
Commons Attribution 4.0 International License (CC-BY-4.0), available at http://creativecommons.org/licenses/by/4.0/.
|
||||
11
SECURITY.md
Normal file
11
SECURITY.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Hyperledger Security Policy
|
||||
|
||||
## Reporting a Security Bug
|
||||
|
||||
If you think you have discovered a security issue in any of the Hyperledger projects, we'd love to hear from you. We will take all security bugs seriously and if confirmed upon investigation we will patch it within a reasonable amount of time and release a public security bulletin discussing the impact and credit the discoverer.
|
||||
|
||||
There are two ways to report a security bug. The easiest is to email a description of the flaw and any related information (e.g. reproduction steps, version) to [security at hyperledger dot org](mailto:security@hyperledger.org).
|
||||
|
||||
The other way is to file a confidential security bug in the repository's [security advisories page](https://github.com/hyperledger/fabric/security/advisories). Guidance can be found in the GitHub documentation on [privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability).
|
||||
|
||||
The process by which the Hyperledger Security Team handles security bugs is documented further in our [Defect Response page](https://lf-hyperledger.atlassian.net/wiki/spaces/SEC/pages/20283618/Defect+Response) on our [wiki](https://lf-hyperledger.atlassian.net/wiki).
|
||||
173
asset-transfer-abac/README.md
Normal file
173
asset-transfer-abac/README.md
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
# Attribute based access control
|
||||
|
||||
The `asset-transfer-abac` sample demonstrates the use of Attribute-based access control within the context of a simple asset transfer scenario. The sample also uses authorization based on individual client identities to allow the users that interact with the network to own assets on the blockchain ledger.
|
||||
|
||||
Attribute-Based Access Control (ABAC) refers to the ability to restrict access to certain functionality within a smart contract based on the attributes within a users certificate. For example, you may want certain functions within a smart contract to be used only by application administrators. ABAC allows organizations to provide elevated access to certain users without requiring those users be administrators of the network. For more information, see [Attribute-based access control](https://hyperledger-fabric-ca.readthedocs.io/en/latest/users-guide.html#attribute-based-access-control) in the Fabric CA users guide.
|
||||
|
||||
The `asset-transfer-abac` smart contract allows you to create assets that can be updated or transferred by the asset owner. However, the ability to create or remove assets from the ledger is restricted to identities with the `abac.creator=true` attribute. The identity that creates the asset is assigned as the asset owner. Only the owner can transfer the asset to a new owner or update the asset properties. In the course of the tutorial, we will use the Fabric CA client to create identities with the attribute required to create a new asset. We will then transfer the asset to another identity and demonstrate how the `GetID()` function can be used to enforce asset ownership. We will then use the owner identity to delete the asset. Both Attribute based access control and the `GetID()` function are provided by the [Client Identity Chaincode Library](https://github.com/hyperledger/fabric-chaincode-go/blob/master/pkg/cid/README.md).
|
||||
|
||||
## Start the network and deploy the smart contract
|
||||
|
||||
We can use the Fabric test network to deploy and interact with the `asset-transfer-abac` smart contract. Run the following command to change into the test network directory and bring down any running nodes:
|
||||
```
|
||||
cd fabric-samples/test-network
|
||||
./network.sh down
|
||||
```
|
||||
|
||||
Run the following command to deploy the test network using Certificate Authorities:
|
||||
```
|
||||
./network.sh up createChannel -ca
|
||||
```
|
||||
|
||||
You can then use the test network script to deploy the `asset-transfer-abac` smart contract to a channel on the network:
|
||||
```
|
||||
./network.sh deployCC -ccn abac -ccp ../asset-transfer-abac/chaincode-go/ -ccl go
|
||||
```
|
||||
|
||||
## Register identities with attributes
|
||||
|
||||
We can use the one of the test network Certificate Authorities to register and enroll identities with the attribute of `abac.creator=true`. First, we need to set the following environment variables in order to use the Fabric CA client.
|
||||
```
|
||||
export PATH=${PWD}/../bin:${PWD}:$PATH
|
||||
export FABRIC_CFG_PATH=$PWD/../config/
|
||||
```
|
||||
|
||||
We will create the identities using the Org1 CA. Set the Fabric CA client home to the MSP of the Org1 CA admin:
|
||||
```
|
||||
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
|
||||
```
|
||||
|
||||
There are two ways to generate certificates with attributes added. We will use both methods and create two identities in the process. The first method is to specify that the attribute be added to the certificate by default when the identity is registered. The following command will register an identity named creator1 with the attribute of `abac.creator=true`.
|
||||
|
||||
```
|
||||
fabric-ca-client register --id.name creator1 --id.secret creator1pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:ecert' --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
|
||||
```
|
||||
|
||||
The `ecert` suffix adds the attribute to the certificate automatically when the identity is enrolled. As a result, the following enroll command will contain the attribute that was provided in the registration command.
|
||||
|
||||
```
|
||||
fabric-ca-client enroll -u https://creator1:creator1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
|
||||
```
|
||||
|
||||
Now that we have enrolled the identity, run the command below to copy the Node OU configuration file into the creator1 MSP folder.
|
||||
```
|
||||
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp/config.yaml"
|
||||
```
|
||||
|
||||
The second method is to request that the attribute be added upon enrollment. The following command will register an identity named creator2 with the same `abac.creator` attribute.
|
||||
```
|
||||
fabric-ca-client register --id.name creator2 --id.secret creator2pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:' --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
|
||||
```
|
||||
|
||||
The following enroll command will add the attribute to the certificate:
|
||||
|
||||
```
|
||||
fabric-ca-client enroll -u https://creator2:creator2pw@localhost:7054 --caname ca-org1 --enrollment.attrs "abac.creator" -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
|
||||
```
|
||||
|
||||
Run the command below to copy the Node OU configuration file into the creator2 MSP folder.
|
||||
```
|
||||
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp/config.yaml"
|
||||
```
|
||||
|
||||
## Create an asset
|
||||
|
||||
You can use either identity with the `abac.creator=true` attribute to create an asset using the `asset-transfer-abac` smart contract. We will set the following environment variables to use the first identity that was generated, creator1:
|
||||
|
||||
```
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_LOCALMSPID=Org1MSP
|
||||
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
|
||||
export CORE_PEER_ADDRESS=localhost:7051
|
||||
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
|
||||
```
|
||||
|
||||
Run the following command to create Asset1:
|
||||
```
|
||||
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset1","blue","20","100"]}'
|
||||
```
|
||||
|
||||
You can use the command below to query the asset on the ledger:
|
||||
```
|
||||
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
|
||||
```
|
||||
The result will list the creator1 identity as the asset owner. The `GetID()` API reads the name and issuer from the certificate of the identity that submitted the transaction and assigns that identity as the asset owner:
|
||||
```
|
||||
{"ID":"Asset1","color":"blue","size":20,"owner":"x509::CN=creator1,OU=client+OU=org1,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100}
|
||||
```
|
||||
|
||||
## Transfer the asset
|
||||
|
||||
As the owner of Asset1, the creator1 identity has the ability to transfer the asset to another owner. In order to transfer the asset, the owner needs to provide the name and issuer of the new owner to the `TransferAsset` function. The `asset-transfer-abac` smart contract has a `GetSubmittingClientIdentity` function that allows users to retrieve their certificate information and provide it to the asset owner out of band (we omit this step). Issue the command below to transfer Asset1 to the user1 identity from Org1 that was created when the test network was deployed:
|
||||
```
|
||||
export RECIPIENT="x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
|
||||
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"TransferAsset","Args":["Asset1","'"$RECIPIENT"'"]}'
|
||||
```
|
||||
Query the ledger to verify that the asset has a new owner:
|
||||
```
|
||||
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
|
||||
```
|
||||
We can see that Asset1 with is now owned by User1:
|
||||
```
|
||||
{"ID":"Asset1","color":"blue","size":20,"owner":"x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100}
|
||||
```
|
||||
|
||||
## Update the asset
|
||||
|
||||
Now that the asset has been transferred, the new owner can update the asset properties. The smart contract uses the `GetID()` API to ensure that the update is being submitted by the asset owner. To demonstrate the difference between identity and attribute based access control, lets try to update the asset using the creator1 identity first:
|
||||
```
|
||||
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}'
|
||||
```
|
||||
|
||||
Even though creator1 can create new assets, the smart contract detects that the transaction was not submitted by the identity that owns the asset, user1. The command returns the following error:
|
||||
```
|
||||
Error: endorsement failure during invoke. response: status:500 message:"submitting client not authorized to update asset, does not own asset"
|
||||
```
|
||||
|
||||
Run the following command to operate as the asset owner by setting the MSP path to User1:
|
||||
```
|
||||
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
|
||||
```
|
||||
|
||||
We can now update the asset. Run the following command to change the asset color from blue to green. All other aspects of the asset will remain unchanged.
|
||||
```
|
||||
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}'
|
||||
```
|
||||
Run the query command again to verify that the asset has changed color:
|
||||
```
|
||||
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
|
||||
```
|
||||
The result will display that Asset1 is now green:
|
||||
```
|
||||
{"ID":"Asset1","color":"green","size":20,"owner":"x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100}
|
||||
```
|
||||
|
||||
## Delete the asset
|
||||
|
||||
The owner also has the ability to delete the asset. Run the following command to remove Asset1 from the ledger:
|
||||
```
|
||||
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"DeleteAsset","Args":["Asset1"]}'
|
||||
```
|
||||
|
||||
If you query the ledger once more, you will see that Asset1 no longer exists:
|
||||
```
|
||||
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
|
||||
```
|
||||
|
||||
While we are operating as User1, we can demonstrate attribute based access control by trying to create an asset using an identity without the `abac.creator=true` attribute. Run the following command to try to create Asset1 as User1:
|
||||
```
|
||||
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset2","red","20","100"]}'
|
||||
```
|
||||
|
||||
The smart contract will return the following error:
|
||||
```
|
||||
Error: endorsement failure during invoke. response: status:500 message:"submitting client not authorized to create asset, does not have abac.creator role"
|
||||
```
|
||||
|
||||
## Clean up
|
||||
|
||||
When you are finished, you can run the following command to bring down the test network:
|
||||
```
|
||||
./network.sh down
|
||||
```
|
||||
26
asset-transfer-abac/chaincode-go/go.mod
Normal file
26
asset-transfer-abac/chaincode-go/go.mod
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
module github.com/hyperledger/fabric-samples/asset-transfer-abac/chaincode-go
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0
|
||||
|
||||
require (
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 // indirect
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
81
asset-transfer-abac/chaincode-go/go.sum
Normal file
81
asset-transfer-abac/chaincode-go/go.sum
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 h1:IhkHfrl5X/fVnmB6pWeCYCdIJRi9bxj+WTnVN8DtW3c=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0/go.mod h1:PHHaFffjw7p7n9bmCfcm7RqDqYdivNEsJdiNIKZo5Lk=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 h1:rmUoBmciB0GL/miqcbJmJbgp5QTWoJUrZo+CNxrNLF4=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0/go.mod h1:FeWeO/jwGjiME7ak3GufqKIcwkejtzrDG4QxbfKydWs=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
214
asset-transfer-abac/chaincode-go/smart-contract/abac.go
Normal file
214
asset-transfer-abac/chaincode-go/smart-contract/abac.go
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
package abac
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
)
|
||||
|
||||
// SmartContract provides functions for managing an Asset
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// Asset describes basic details of what makes up a simple asset
|
||||
type Asset struct {
|
||||
ID string `json:"ID"`
|
||||
Color string `json:"color"`
|
||||
Size int `json:"size"`
|
||||
Owner string `json:"owner"`
|
||||
AppraisedValue int `json:"appraisedValue"`
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, appraisedValue int) error {
|
||||
|
||||
// Demonstrate the use of Attribute-Based Access Control (ABAC) by checking
|
||||
// to see if the caller has the "abac.creator" attribute with a value of true;
|
||||
// if not, return an error.
|
||||
|
||||
err := ctx.GetClientIdentity().AssertAttributeValue("abac.creator", "true")
|
||||
if err != nil {
|
||||
return fmt.Errorf("submitting client not authorized to create asset, does not have abac.creator role")
|
||||
}
|
||||
|
||||
exists, err := s.AssetExists(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("the asset %s already exists", id)
|
||||
}
|
||||
|
||||
// Get ID of submitting client identity
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
asset := Asset{
|
||||
ID: id,
|
||||
Color: color,
|
||||
Size: size,
|
||||
Owner: clientID,
|
||||
AppraisedValue: appraisedValue,
|
||||
}
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.GetStub().PutState(id, assetJSON)
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, newColor string, newSize int, newValue int) error {
|
||||
|
||||
asset, err := s.ReadAsset(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if clientID != asset.Owner {
|
||||
return fmt.Errorf("submitting client not authorized to update asset, does not own asset")
|
||||
}
|
||||
|
||||
asset.Color = newColor
|
||||
asset.Size = newSize
|
||||
asset.AppraisedValue = newValue
|
||||
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.GetStub().PutState(id, assetJSON)
|
||||
}
|
||||
|
||||
// DeleteAsset deletes a given asset from the world state.
|
||||
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
|
||||
|
||||
asset, err := s.ReadAsset(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if clientID != asset.Owner {
|
||||
return fmt.Errorf("submitting client not authorized to update asset, does not own asset")
|
||||
}
|
||||
|
||||
return ctx.GetStub().DelState(id)
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in world state.
|
||||
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
|
||||
|
||||
asset, err := s.ReadAsset(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if clientID != asset.Owner {
|
||||
return fmt.Errorf("submitting client not authorized to update asset, does not own asset")
|
||||
}
|
||||
|
||||
asset.Owner = newOwner
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.GetStub().PutState(id, assetJSON)
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
|
||||
|
||||
assetJSON, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
if assetJSON == nil {
|
||||
return nil, fmt.Errorf("the asset %s does not exist", id)
|
||||
}
|
||||
|
||||
var asset Asset
|
||||
err = json.Unmarshal(assetJSON, &asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &asset, nil
|
||||
}
|
||||
|
||||
// GetAllAssets returns all assets found in world state
|
||||
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
|
||||
// range query with empty string for startKey and endKey does an
|
||||
// open-ended query of all assets in the chaincode namespace.
|
||||
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resultsIterator.Close()
|
||||
|
||||
var assets []*Asset
|
||||
for resultsIterator.HasNext() {
|
||||
queryResponse, err := resultsIterator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var asset Asset
|
||||
err = json.Unmarshal(queryResponse.Value, &asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assets = append(assets, &asset)
|
||||
}
|
||||
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state
|
||||
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
|
||||
|
||||
assetJSON, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
|
||||
return assetJSON != nil, nil
|
||||
}
|
||||
|
||||
// GetSubmittingClientIdentity returns the name and issuer of the identity that
|
||||
// invokes the smart contract. This function base64 decodes the identity string
|
||||
// before returning the value to the client or smart contract.
|
||||
func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
|
||||
b64ID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read clientID: %v", err)
|
||||
}
|
||||
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
|
||||
}
|
||||
return string(decodeID), nil
|
||||
}
|
||||
23
asset-transfer-abac/chaincode-go/smartContract.go
Normal file
23
asset-transfer-abac/chaincode-go/smartContract.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
abac "github.com/hyperledger/fabric-samples/asset-transfer-abac/chaincode-go/smart-contract"
|
||||
)
|
||||
|
||||
func main() {
|
||||
abacSmartContract, err := contractapi.NewChaincode(&abac.SmartContract{})
|
||||
if err != nil {
|
||||
log.Panicf("Error creating abac chaincode: %v", err)
|
||||
}
|
||||
|
||||
if err := abacSmartContract.Start(); err != nil {
|
||||
log.Panicf("Error starting abac chaincode: %v", err)
|
||||
}
|
||||
}
|
||||
2
asset-transfer-basic/.gitignore
vendored
Executable file
2
asset-transfer-basic/.gitignore
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
wallet
|
||||
!wallet/.gitkeep
|
||||
107
asset-transfer-basic/README.md
Normal file
107
asset-transfer-basic/README.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Asset transfer basic sample
|
||||
|
||||
The asset transfer basic sample demonstrates:
|
||||
|
||||
- Connecting a client application to a Fabric blockchain network.
|
||||
- Submitting smart contract transactions to update ledger state.
|
||||
- Evaluating smart contract transactions to query ledger state.
|
||||
- Handling errors in transaction invocation.
|
||||
|
||||
## About the sample
|
||||
|
||||
This sample includes smart contract and application code in multiple languages. This sample shows create, read, update, transfer and delete of an asset.
|
||||
|
||||
For a more detailed walk-through of the application code and client API usage, refer to the [Running a Fabric Application tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html) in the main Hyperledger Fabric documentation.
|
||||
|
||||
### Application
|
||||
|
||||
Follow the execution flow in the client application code, and corresponding output on running the application. Pay attention to the sequence of:
|
||||
|
||||
- Transaction invocations (console output like "**--> Submit Transaction**" and "**--> Evaluate Transaction**").
|
||||
- Results returned by transactions (console output like "**\*\*\* Result**").
|
||||
|
||||
### Smart Contract
|
||||
|
||||
The smart contract (in folder `chaincode-xyz`) implements the following functions to support the application:
|
||||
|
||||
- CreateAsset
|
||||
- ReadAsset
|
||||
- UpdateAsset
|
||||
- DeleteAsset
|
||||
- TransferAsset
|
||||
|
||||
Note that the asset transfer implemented by the smart contract is a simplified scenario, without ownership validation, meant only to demonstrate how to invoke transactions.
|
||||
|
||||
## Running the sample
|
||||
|
||||
The Fabric test network is used to deploy and run this sample. Follow these steps in order:
|
||||
|
||||
1. Create the test network and a channel (from the `test-network` folder).
|
||||
|
||||
```
|
||||
./network.sh up createChannel -c mychannel -ca
|
||||
```
|
||||
|
||||
1. Deploy one of the smart contract implementations (from the `test-network` folder).
|
||||
|
||||
- To deploy the **TypeScript** chaincode implementation:
|
||||
|
||||
```shell
|
||||
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript
|
||||
```
|
||||
|
||||
- To deploy the **JavaScript** chaincode implementation:
|
||||
|
||||
```shell
|
||||
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript
|
||||
```
|
||||
|
||||
- To deploy the **Go** chaincode implementation:
|
||||
|
||||
```shell
|
||||
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go
|
||||
```
|
||||
|
||||
- To deploy the **Java** chaincode implementation:
|
||||
```shell
|
||||
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java/ -ccl java
|
||||
```
|
||||
|
||||
1. Run the application (from the `asset-transfer-basic` folder).
|
||||
|
||||
- To run the **TypeScript** sample application:
|
||||
|
||||
```shell
|
||||
cd application-gateway-typescript
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
- To run the **JavaScript** sample application:
|
||||
|
||||
```shell
|
||||
cd application-gateway-javascript
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
- To run the **Go** sample application:
|
||||
|
||||
```shell
|
||||
cd application-gateway-go
|
||||
go run .
|
||||
```
|
||||
|
||||
- To run the **Java** sample application:
|
||||
```shell
|
||||
cd application-gateway-java
|
||||
./gradlew run
|
||||
```
|
||||
|
||||
## Clean up
|
||||
|
||||
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
|
||||
|
||||
```shell
|
||||
./network.sh down
|
||||
```
|
||||
275
asset-transfer-basic/application-gateway-go/assetTransfer.go
Executable file
275
asset-transfer-basic/application-gateway-go/assetTransfer.go
Executable file
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
Copyright 2021 IBM All Rights Reserved.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/hyperledger/fabric-gateway/pkg/client"
|
||||
"github.com/hyperledger/fabric-gateway/pkg/hash"
|
||||
"github.com/hyperledger/fabric-gateway/pkg/identity"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
mspID = "Org1MSP"
|
||||
cryptoPath = "../../test-network/organizations/peerOrganizations/org1.example.com"
|
||||
certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts"
|
||||
keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore"
|
||||
tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
|
||||
peerEndpoint = "dns:///localhost:7051"
|
||||
gatewayPeer = "peer0.org1.example.com"
|
||||
)
|
||||
|
||||
var now = time.Now()
|
||||
var assetId = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6)
|
||||
|
||||
func main() {
|
||||
// The gRPC client connection should be shared by all Gateway connections to this endpoint
|
||||
clientConnection := newGrpcConnection()
|
||||
defer clientConnection.Close()
|
||||
|
||||
id := newIdentity()
|
||||
sign := newSign()
|
||||
|
||||
// Create a Gateway connection for a specific client identity
|
||||
gw, err := client.Connect(
|
||||
id,
|
||||
client.WithSign(sign),
|
||||
client.WithHash(hash.SHA256),
|
||||
client.WithClientConnection(clientConnection),
|
||||
// Default timeouts for different gRPC calls
|
||||
client.WithEvaluateTimeout(5*time.Second),
|
||||
client.WithEndorseTimeout(15*time.Second),
|
||||
client.WithSubmitTimeout(5*time.Second),
|
||||
client.WithCommitStatusTimeout(1*time.Minute),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer gw.Close()
|
||||
|
||||
// Override default values for chaincode and channel name as they may differ in testing contexts.
|
||||
chaincodeName := "basic"
|
||||
if ccname := os.Getenv("CHAINCODE_NAME"); ccname != "" {
|
||||
chaincodeName = ccname
|
||||
}
|
||||
|
||||
channelName := "mychannel"
|
||||
if cname := os.Getenv("CHANNEL_NAME"); cname != "" {
|
||||
channelName = cname
|
||||
}
|
||||
|
||||
network := gw.GetNetwork(channelName)
|
||||
contract := network.GetContract(chaincodeName)
|
||||
|
||||
initLedger(contract)
|
||||
getAllAssets(contract)
|
||||
createAsset(contract)
|
||||
readAssetByID(contract)
|
||||
transferAssetAsync(contract)
|
||||
exampleErrorHandling(contract)
|
||||
}
|
||||
|
||||
// newGrpcConnection creates a gRPC connection to the Gateway server.
|
||||
func newGrpcConnection() *grpc.ClientConn {
|
||||
certificatePEM, err := os.ReadFile(tlsCertPath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to read TLS certifcate file: %w", err))
|
||||
}
|
||||
|
||||
certificate, err := identity.CertificateFromPEM(certificatePEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AddCert(certificate)
|
||||
transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
|
||||
|
||||
connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
|
||||
}
|
||||
|
||||
return connection
|
||||
}
|
||||
|
||||
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
|
||||
func newIdentity() *identity.X509Identity {
|
||||
certificatePEM, err := readFirstFile(certPath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to read certificate file: %w", err))
|
||||
}
|
||||
|
||||
certificate, err := identity.CertificateFromPEM(certificatePEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
id, err := identity.NewX509Identity(mspID, certificate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// newSign creates a function that generates a digital signature from a message digest using a private key.
|
||||
func newSign() identity.Sign {
|
||||
privateKeyPEM, err := readFirstFile(keyPath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to read private key file: %w", err))
|
||||
}
|
||||
|
||||
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sign, err := identity.NewPrivateKeySign(privateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sign
|
||||
}
|
||||
|
||||
func readFirstFile(dirPath string) ([]byte, error) {
|
||||
dir, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileNames, err := dir.Readdirnames(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.ReadFile(path.Join(dirPath, fileNames[0]))
|
||||
}
|
||||
|
||||
// This type of transaction would typically only be run once by an application the first time it was started after its
|
||||
// initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function.
|
||||
func initLedger(contract *client.Contract) {
|
||||
fmt.Printf("\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger \n")
|
||||
|
||||
_, err := contract.SubmitTransaction("InitLedger")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to submit transaction: %w", err))
|
||||
}
|
||||
|
||||
fmt.Printf("*** Transaction committed successfully\n")
|
||||
}
|
||||
|
||||
// Evaluate a transaction to query ledger state.
|
||||
func getAllAssets(contract *client.Contract) {
|
||||
fmt.Println("\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
|
||||
|
||||
evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to evaluate transaction: %w", err))
|
||||
}
|
||||
result := formatJSON(evaluateResult)
|
||||
|
||||
fmt.Printf("*** Result:%s\n", result)
|
||||
}
|
||||
|
||||
// Submit a transaction synchronously, blocking until it has been committed to the ledger.
|
||||
func createAsset(contract *client.Contract) {
|
||||
fmt.Printf("\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments \n")
|
||||
|
||||
_, err := contract.SubmitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to submit transaction: %w", err))
|
||||
}
|
||||
|
||||
fmt.Printf("*** Transaction committed successfully\n")
|
||||
}
|
||||
|
||||
// Evaluate a transaction by assetID to query ledger state.
|
||||
func readAssetByID(contract *client.Contract) {
|
||||
fmt.Printf("\n--> Evaluate Transaction: ReadAsset, function returns asset attributes\n")
|
||||
|
||||
evaluateResult, err := contract.EvaluateTransaction("ReadAsset", assetId)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to evaluate transaction: %w", err))
|
||||
}
|
||||
result := formatJSON(evaluateResult)
|
||||
|
||||
fmt.Printf("*** Result:%s\n", result)
|
||||
}
|
||||
|
||||
// Submit transaction asynchronously, blocking until the transaction has been sent to the orderer, and allowing
|
||||
// this thread to process the chaincode response (e.g. update a UI) without waiting for the commit notification
|
||||
func transferAssetAsync(contract *client.Contract) {
|
||||
fmt.Printf("\n--> Async Submit Transaction: TransferAsset, updates existing asset owner")
|
||||
|
||||
submitResult, commit, err := contract.SubmitAsync("TransferAsset", client.WithArguments(assetId, "Mark"))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to submit transaction asynchronously: %w", err))
|
||||
}
|
||||
|
||||
fmt.Printf("\n*** Successfully submitted transaction to transfer ownership from %s to Mark. \n", string(submitResult))
|
||||
fmt.Println("*** Waiting for transaction commit.")
|
||||
|
||||
if commitStatus, err := commit.Status(); err != nil {
|
||||
panic(fmt.Errorf("failed to get commit status: %w", err))
|
||||
} else if !commitStatus.Successful {
|
||||
panic(fmt.Errorf("transaction %s failed to commit with status: %d", commitStatus.TransactionID, int32(commitStatus.Code)))
|
||||
}
|
||||
|
||||
fmt.Printf("*** Transaction committed successfully\n")
|
||||
}
|
||||
|
||||
// Submit transaction, passing in the wrong number of arguments ,expected to throw an error containing details of any error responses from the smart contract.
|
||||
func exampleErrorHandling(contract *client.Contract) {
|
||||
fmt.Println("\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error")
|
||||
|
||||
_, err := contract.SubmitTransaction("UpdateAsset", "asset70", "blue", "5", "Tomoko", "300")
|
||||
if err == nil {
|
||||
panic("******** FAILED to return an error")
|
||||
}
|
||||
|
||||
fmt.Println("*** Successfully caught the error:")
|
||||
|
||||
var commitStatusErr *client.CommitStatusError
|
||||
var transactionErr *client.TransactionError
|
||||
|
||||
if errors.As(err, &commitStatusErr) {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
fmt.Printf("Timeout waiting for transaction %s commit status: %s\n", commitStatusErr.TransactionID, commitStatusErr)
|
||||
} else {
|
||||
fmt.Printf("Error obtaining commit status for transaction %s with gRPC status %v: %s\n", commitStatusErr.TransactionID, status.Code(commitStatusErr), commitStatusErr)
|
||||
}
|
||||
} else if errors.As(err, &transactionErr) {
|
||||
// The error could be an EndorseError, SubmitError or CommitError.
|
||||
fmt.Println(err)
|
||||
fmt.Printf("TransactionID: %s\n", transactionErr.TransactionID)
|
||||
} else {
|
||||
panic(fmt.Errorf("unexpected error type %T: %w", err, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Format JSON data
|
||||
func formatJSON(data []byte) string {
|
||||
var prettyJSON bytes.Buffer
|
||||
if err := json.Indent(&prettyJSON, data, "", " "); err != nil {
|
||||
panic(fmt.Errorf("failed to parse JSON: %w", err))
|
||||
}
|
||||
return prettyJSON.String()
|
||||
}
|
||||
18
asset-transfer-basic/application-gateway-go/go.mod
Normal file
18
asset-transfer-basic/application-gateway-go/go.mod
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module assetTransfer
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/hyperledger/fabric-gateway v1.10.0
|
||||
google.golang.org/grpc v1.76.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
52
asset-transfer-basic/application-gateway-go/go.sum
Normal file
52
asset-transfer-basic/application-gateway-go/go.sum
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hyperledger/fabric-gateway v1.10.0 h1:x5z/pofdVYIqgMo9QWejubfAZYCSt94WdUPj4Wipdeg=
|
||||
github.com/hyperledger/fabric-gateway v1.10.0/go.mod h1:fSFS1vQkPZq6inNvzsnI/7PCaKSU+UZOZ6uAuau0Yq0=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7 h1:sQ5qv8vQQfwewa1JlCiSCC8dLElmaU2/frLolpgibEY=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7/go.mod h1:bJnwzfv03oZQeCc863pdGTDgf5nmCy6Za3RAE7d2XsQ=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
37
asset-transfer-basic/application-gateway-java/build.gradle
Normal file
37
asset-transfer-basic/application-gateway-java/build.gradle
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*
|
||||
* This generated file contains a sample Java project to get you started.
|
||||
* For more details take a look at the Java Quickstart chapter in the Gradle
|
||||
* User Manual available at https://docs.gradle.org/6.5/userguide/tutorial_java_projects.html
|
||||
*/
|
||||
plugins {
|
||||
// Apply the application plugin to add support for building a CLI application.
|
||||
id 'application'
|
||||
}
|
||||
|
||||
ext {
|
||||
javaMainClass = "application.java.App"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.hyperledger.fabric:fabric-gateway:1.10.0'
|
||||
implementation platform('com.google.protobuf:protobuf-bom:4.33.0')
|
||||
implementation platform('io.grpc:grpc-bom:1.76.0')
|
||||
compileOnly 'io.grpc:grpc-api'
|
||||
runtimeOnly 'io.grpc:grpc-netty-shaded'
|
||||
implementation 'com.google.code.gson:gson:2.13.2'
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.release = 11
|
||||
}
|
||||
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'App'
|
||||
}
|
||||
BIN
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
asset-transfer-basic/application-gateway-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
252
asset-transfer-basic/application-gateway-java/gradlew
vendored
Executable file
252
asset-transfer-basic/application-gateway-java/gradlew
vendored
Executable file
|
|
@ -0,0 +1,252 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
asset-transfer-basic/application-gateway-java/gradlew.bat
vendored
Normal file
94
asset-transfer-basic/application-gateway-java/gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
101
asset-transfer-basic/application-gateway-java/pom.xml
Normal file
101
asset-transfer-basic/application-gateway-java/pom.xml
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.hyperledger.fabric.example</groupId>
|
||||
<artifactId>asset-transfer-basic</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-bom</artifactId>
|
||||
<version>4.33.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-bom</artifactId>
|
||||
<version>1.76.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hyperledger.fabric</groupId>
|
||||
<artifactId>fabric-gateway</artifactId>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty-shaded</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be
|
||||
moved to parent pom) -->
|
||||
<plugins>
|
||||
<!-- clean lifecycle, see
|
||||
https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
</plugin>
|
||||
<!-- default lifecycle, jar packaging: see
|
||||
https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<!-- site lifecycle, see
|
||||
https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
|
||||
<plugin>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.12.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*
|
||||
* The settings file is used to specify which projects to include in your build.
|
||||
*
|
||||
* Detailed information about configuring a multi-project build in Gradle can be found
|
||||
* in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html
|
||||
*/
|
||||
|
||||
rootProject.name = 'asset-transfer-basic'
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParser;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.TlsChannelCredentials;
|
||||
import org.hyperledger.fabric.client.CommitException;
|
||||
import org.hyperledger.fabric.client.CommitStatusException;
|
||||
import org.hyperledger.fabric.client.Contract;
|
||||
import org.hyperledger.fabric.client.EndorseException;
|
||||
import org.hyperledger.fabric.client.Gateway;
|
||||
import org.hyperledger.fabric.client.GatewayException;
|
||||
import org.hyperledger.fabric.client.Hash;
|
||||
import org.hyperledger.fabric.client.SubmitException;
|
||||
import org.hyperledger.fabric.client.identity.Identities;
|
||||
import org.hyperledger.fabric.client.identity.Identity;
|
||||
import org.hyperledger.fabric.client.identity.Signer;
|
||||
import org.hyperledger.fabric.client.identity.Signers;
|
||||
import org.hyperledger.fabric.client.identity.X509Identity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class App {
|
||||
private static final String MSP_ID = System.getenv().getOrDefault("MSP_ID", "Org1MSP");
|
||||
private static final String CHANNEL_NAME = System.getenv().getOrDefault("CHANNEL_NAME", "mychannel");
|
||||
private static final String CHAINCODE_NAME = System.getenv().getOrDefault("CHAINCODE_NAME", "basic");
|
||||
|
||||
// Path to crypto materials.
|
||||
private static final Path CRYPTO_PATH = Paths.get("../../test-network/organizations/peerOrganizations/org1.example.com");
|
||||
// Path to user certificate.
|
||||
private static final Path CERT_DIR_PATH = CRYPTO_PATH.resolve(Paths.get("users/User1@org1.example.com/msp/signcerts"));
|
||||
// Path to user private key directory.
|
||||
private static final Path KEY_DIR_PATH = CRYPTO_PATH.resolve(Paths.get("users/User1@org1.example.com/msp/keystore"));
|
||||
// Path to peer tls certificate.
|
||||
private static final Path TLS_CERT_PATH = CRYPTO_PATH.resolve(Paths.get("peers/peer0.org1.example.com/tls/ca.crt"));
|
||||
|
||||
// Gateway peer end point.
|
||||
private static final String PEER_ENDPOINT = "localhost:7051";
|
||||
private static final String OVERRIDE_AUTH = "peer0.org1.example.com";
|
||||
|
||||
private final Contract contract;
|
||||
private final String assetId = "asset" + Instant.now().toEpochMilli();
|
||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
// The gRPC client connection should be shared by all Gateway connections to
|
||||
// this endpoint.
|
||||
var channel = newGrpcConnection();
|
||||
|
||||
var builder = Gateway.newInstance()
|
||||
.identity(newIdentity())
|
||||
.signer(newSigner())
|
||||
.hash(Hash.SHA256)
|
||||
.connection(channel)
|
||||
// Default timeouts for different gRPC calls
|
||||
.evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
|
||||
.endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))
|
||||
.submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
|
||||
.commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES));
|
||||
|
||||
try (var gateway = builder.connect()) {
|
||||
new App(gateway).run();
|
||||
} finally {
|
||||
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private static ManagedChannel newGrpcConnection() throws IOException {
|
||||
var credentials = TlsChannelCredentials.newBuilder()
|
||||
.trustManager(TLS_CERT_PATH.toFile())
|
||||
.build();
|
||||
return Grpc.newChannelBuilder(PEER_ENDPOINT, credentials)
|
||||
.overrideAuthority(OVERRIDE_AUTH)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Identity newIdentity() throws IOException, CertificateException {
|
||||
try (var certReader = Files.newBufferedReader(getFirstFilePath(CERT_DIR_PATH))) {
|
||||
var certificate = Identities.readX509Certificate(certReader);
|
||||
return new X509Identity(MSP_ID, certificate);
|
||||
}
|
||||
}
|
||||
|
||||
private static Signer newSigner() throws IOException, InvalidKeyException {
|
||||
try (var keyReader = Files.newBufferedReader(getFirstFilePath(KEY_DIR_PATH))) {
|
||||
var privateKey = Identities.readPrivateKey(keyReader);
|
||||
return Signers.newPrivateKeySigner(privateKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getFirstFilePath(Path dirPath) throws IOException {
|
||||
try (var keyFiles = Files.list(dirPath)) {
|
||||
return keyFiles.findFirst().orElseThrow();
|
||||
}
|
||||
}
|
||||
|
||||
public App(final Gateway gateway) {
|
||||
// Get a network instance representing the channel where the smart contract is
|
||||
// deployed.
|
||||
var network = gateway.getNetwork(CHANNEL_NAME);
|
||||
|
||||
// Get the smart contract from the network.
|
||||
contract = network.getContract(CHAINCODE_NAME);
|
||||
}
|
||||
|
||||
public void run() throws GatewayException, CommitException {
|
||||
// Initialize a set of asset data on the ledger using the chaincode 'InitLedger' function.
|
||||
initLedger();
|
||||
|
||||
// Return all the current assets on the ledger.
|
||||
getAllAssets();
|
||||
|
||||
// Create a new asset on the ledger.
|
||||
createAsset();
|
||||
|
||||
// Update an existing asset asynchronously.
|
||||
transferAssetAsync();
|
||||
|
||||
// Get the asset details by assetID.
|
||||
readAssetById();
|
||||
|
||||
// Update an asset which does not exist.
|
||||
updateNonExistentAsset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This type of transaction would typically only be run once by an application
|
||||
* the first time it was started after its initial deployment. A new version of
|
||||
* the chaincode deployed later would likely not need to run an "init" function.
|
||||
*/
|
||||
private void initLedger() throws EndorseException, SubmitException, CommitStatusException, CommitException {
|
||||
System.out.println("\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger");
|
||||
|
||||
contract.submitTransaction("InitLedger");
|
||||
|
||||
System.out.println("*** Transaction committed successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a transaction to query ledger state.
|
||||
*/
|
||||
private void getAllAssets() throws GatewayException {
|
||||
System.out.println("\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger");
|
||||
|
||||
var result = contract.evaluateTransaction("GetAllAssets");
|
||||
|
||||
System.out.println("*** Result: " + prettyJson(result));
|
||||
}
|
||||
|
||||
private String prettyJson(final byte[] json) {
|
||||
return prettyJson(new String(json, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private String prettyJson(final String json) {
|
||||
var parsedJson = JsonParser.parseString(json);
|
||||
return gson.toJson(parsedJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a transaction synchronously, blocking until it has been committed to
|
||||
* the ledger.
|
||||
*/
|
||||
private void createAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
|
||||
System.out.println("\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments");
|
||||
|
||||
contract.submitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300");
|
||||
|
||||
System.out.println("*** Transaction committed successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit transaction asynchronously, allowing the application to process the
|
||||
* smart contract response (e.g. update a UI) while waiting for the commit
|
||||
* notification.
|
||||
*/
|
||||
private void transferAssetAsync() throws EndorseException, SubmitException, CommitStatusException {
|
||||
System.out.println("\n--> Async Submit Transaction: TransferAsset, updates existing asset owner");
|
||||
|
||||
var commit = contract.newProposal("TransferAsset")
|
||||
.addArguments(assetId, "Saptha")
|
||||
.build()
|
||||
.endorse()
|
||||
.submitAsync();
|
||||
|
||||
var result = commit.getResult();
|
||||
var oldOwner = new String(result, StandardCharsets.UTF_8);
|
||||
|
||||
System.out.println("*** Successfully submitted transaction to transfer ownership from " + oldOwner + " to Saptha");
|
||||
System.out.println("*** Waiting for transaction commit");
|
||||
|
||||
var status = commit.getStatus();
|
||||
if (!status.isSuccessful()) {
|
||||
throw new RuntimeException("Transaction " + status.getTransactionId() +
|
||||
" failed to commit with status code " + status.getCode());
|
||||
}
|
||||
|
||||
System.out.println("*** Transaction committed successfully");
|
||||
}
|
||||
|
||||
private void readAssetById() throws GatewayException {
|
||||
System.out.println("\n--> Evaluate Transaction: ReadAsset, function returns asset attributes");
|
||||
|
||||
var evaluateResult = contract.evaluateTransaction("ReadAsset", assetId);
|
||||
|
||||
System.out.println("*** Result:" + prettyJson(evaluateResult));
|
||||
}
|
||||
|
||||
/**
|
||||
* submitTransaction() will throw an error containing details of any error
|
||||
* responses from the smart contract.
|
||||
*/
|
||||
private void updateNonExistentAsset() {
|
||||
try {
|
||||
System.out.println("\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error");
|
||||
|
||||
contract.submitTransaction("UpdateAsset", "asset70", "blue", "5", "Tomoko", "300");
|
||||
|
||||
System.out.println("******** FAILED to return an error");
|
||||
} catch (EndorseException | SubmitException | CommitStatusException e) {
|
||||
System.out.println("*** Successfully caught the error:");
|
||||
e.printStackTrace(System.out);
|
||||
System.out.println("Transaction ID: " + e.getTransactionId());
|
||||
} catch (CommitException e) {
|
||||
System.out.println("*** Successfully caught the error:");
|
||||
e.printStackTrace(System.out);
|
||||
System.out.println("Transaction ID: " + e.getTransactionId());
|
||||
System.out.println("Status code: " + e.getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
asset-transfer-basic/application-gateway-javascript/.gitignore
vendored
Normal file
11
asset-transfer-basic/application-gateway-javascript/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
|
@ -0,0 +1 @@
|
|||
engine-strict=true
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import js from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2023,
|
||||
sourceType: 'commonjs',
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic Application implemented in JavaScript using fabric-gateway",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src",
|
||||
"pretest": "npm run lint",
|
||||
"start": "node src/app.js"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.0",
|
||||
"@hyperledger/fabric-gateway": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.5.0",
|
||||
"eslint": "^9.5.0",
|
||||
"globals": "^15.6.0"
|
||||
}
|
||||
}
|
||||
301
asset-transfer-basic/application-gateway-javascript/src/app.js
Normal file
301
asset-transfer-basic/application-gateway-javascript/src/app.js
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const { connect, hash, signers } = require('@hyperledger/fabric-gateway');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
const { TextDecoder } = require('node:util');
|
||||
|
||||
const channelName = envOrDefault('CHANNEL_NAME', 'mychannel');
|
||||
const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic');
|
||||
const mspId = envOrDefault('MSP_ID', 'Org1MSP');
|
||||
|
||||
// Path to crypto materials.
|
||||
const cryptoPath = envOrDefault(
|
||||
'CRYPTO_PATH',
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'test-network',
|
||||
'organizations',
|
||||
'peerOrganizations',
|
||||
'org1.example.com'
|
||||
)
|
||||
);
|
||||
|
||||
// Path to user private key directory.
|
||||
const keyDirectoryPath = envOrDefault(
|
||||
'KEY_DIRECTORY_PATH',
|
||||
path.resolve(
|
||||
cryptoPath,
|
||||
'users',
|
||||
'User1@org1.example.com',
|
||||
'msp',
|
||||
'keystore'
|
||||
)
|
||||
);
|
||||
|
||||
// Path to user certificate directory.
|
||||
const certDirectoryPath = envOrDefault(
|
||||
'CERT_DIRECTORY_PATH',
|
||||
path.resolve(
|
||||
cryptoPath,
|
||||
'users',
|
||||
'User1@org1.example.com',
|
||||
'msp',
|
||||
'signcerts'
|
||||
)
|
||||
);
|
||||
|
||||
// Path to peer tls certificate.
|
||||
const tlsCertPath = envOrDefault(
|
||||
'TLS_CERT_PATH',
|
||||
path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt')
|
||||
);
|
||||
|
||||
// Gateway peer endpoint.
|
||||
const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051');
|
||||
|
||||
// Gateway peer SSL host name override.
|
||||
const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer0.org1.example.com');
|
||||
|
||||
const utf8Decoder = new TextDecoder();
|
||||
const assetId = `asset${String(Date.now())}`;
|
||||
|
||||
async function main() {
|
||||
displayInputParameters();
|
||||
|
||||
// The gRPC client connection should be shared by all Gateway connections to this endpoint.
|
||||
const client = await newGrpcConnection();
|
||||
|
||||
const gateway = connect({
|
||||
client,
|
||||
identity: await newIdentity(),
|
||||
signer: await newSigner(),
|
||||
hash: hash.sha256,
|
||||
// Default timeouts for different gRPC calls
|
||||
evaluateOptions: () => {
|
||||
return { deadline: Date.now() + 5000 }; // 5 seconds
|
||||
},
|
||||
endorseOptions: () => {
|
||||
return { deadline: Date.now() + 15000 }; // 15 seconds
|
||||
},
|
||||
submitOptions: () => {
|
||||
return { deadline: Date.now() + 5000 }; // 5 seconds
|
||||
},
|
||||
commitStatusOptions: () => {
|
||||
return { deadline: Date.now() + 60000 }; // 1 minute
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Get a network instance representing the channel where the smart contract is deployed.
|
||||
const network = gateway.getNetwork(channelName);
|
||||
|
||||
// Get the smart contract from the network.
|
||||
const contract = network.getContract(chaincodeName);
|
||||
|
||||
// Initialize a set of asset data on the ledger using the chaincode 'InitLedger' function.
|
||||
await initLedger(contract);
|
||||
|
||||
// Return all the current assets on the ledger.
|
||||
await getAllAssets(contract);
|
||||
|
||||
// Create a new asset on the ledger.
|
||||
await createAsset(contract);
|
||||
|
||||
// Update an existing asset asynchronously.
|
||||
await transferAssetAsync(contract);
|
||||
|
||||
// Get the asset details by assetID.
|
||||
await readAssetByID(contract);
|
||||
|
||||
// Update an asset which does not exist.
|
||||
await updateNonExistentAsset(contract);
|
||||
} finally {
|
||||
gateway.close();
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('******** FAILED to run the application:', error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
async function newGrpcConnection() {
|
||||
const tlsRootCert = await fs.readFile(tlsCertPath);
|
||||
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
|
||||
return new grpc.Client(peerEndpoint, tlsCredentials, {
|
||||
'grpc.ssl_target_name_override': peerHostAlias,
|
||||
});
|
||||
}
|
||||
|
||||
async function newIdentity() {
|
||||
const certPath = await getFirstDirFileName(certDirectoryPath);
|
||||
const credentials = await fs.readFile(certPath);
|
||||
return { mspId, credentials };
|
||||
}
|
||||
|
||||
async function getFirstDirFileName(dirPath) {
|
||||
const files = await fs.readdir(dirPath);
|
||||
const file = files[0];
|
||||
if (!file) {
|
||||
throw new Error(`No files in directory: ${dirPath}`);
|
||||
}
|
||||
return path.join(dirPath, file);
|
||||
}
|
||||
|
||||
async function newSigner() {
|
||||
const keyPath = await getFirstDirFileName(keyDirectoryPath);
|
||||
const privateKeyPem = await fs.readFile(keyPath);
|
||||
const privateKey = crypto.createPrivateKey(privateKeyPem);
|
||||
return signers.newPrivateKeySigner(privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* This type of transaction would typically only be run once by an application the first time it was started after its
|
||||
* initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function.
|
||||
*/
|
||||
async function initLedger(contract) {
|
||||
console.log(
|
||||
'\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger'
|
||||
);
|
||||
|
||||
await contract.submitTransaction('InitLedger');
|
||||
|
||||
console.log('*** Transaction committed successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a transaction to query ledger state.
|
||||
*/
|
||||
async function getAllAssets(contract) {
|
||||
console.log(
|
||||
'\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger'
|
||||
);
|
||||
|
||||
const resultBytes = await contract.evaluateTransaction('GetAllAssets');
|
||||
|
||||
const resultJson = utf8Decoder.decode(resultBytes);
|
||||
const result = JSON.parse(resultJson);
|
||||
console.log('*** Result:', result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a transaction synchronously, blocking until it has been committed to the ledger.
|
||||
*/
|
||||
async function createAsset(contract) {
|
||||
console.log(
|
||||
'\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments'
|
||||
);
|
||||
|
||||
await contract.submitTransaction(
|
||||
'CreateAsset',
|
||||
assetId,
|
||||
'yellow',
|
||||
'5',
|
||||
'Tom',
|
||||
'1300'
|
||||
);
|
||||
|
||||
console.log('*** Transaction committed successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit transaction asynchronously, allowing the application to process the smart contract response (e.g. update a UI)
|
||||
* while waiting for the commit notification.
|
||||
*/
|
||||
async function transferAssetAsync(contract) {
|
||||
console.log(
|
||||
'\n--> Async Submit Transaction: TransferAsset, updates existing asset owner'
|
||||
);
|
||||
|
||||
const commit = await contract.submitAsync('TransferAsset', {
|
||||
arguments: [assetId, 'Saptha'],
|
||||
});
|
||||
const oldOwner = utf8Decoder.decode(commit.getResult());
|
||||
|
||||
console.log(
|
||||
`*** Successfully submitted transaction to transfer ownership from ${oldOwner} to Saptha`
|
||||
);
|
||||
console.log('*** Waiting for transaction commit');
|
||||
|
||||
const status = await commit.getStatus();
|
||||
if (!status.successful) {
|
||||
throw new Error(
|
||||
`Transaction ${
|
||||
status.transactionId
|
||||
} failed to commit with status code ${String(status.code)}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log('*** Transaction committed successfully');
|
||||
}
|
||||
|
||||
async function readAssetByID(contract) {
|
||||
console.log(
|
||||
'\n--> Evaluate Transaction: ReadAsset, function returns asset attributes'
|
||||
);
|
||||
|
||||
const resultBytes = await contract.evaluateTransaction(
|
||||
'ReadAsset',
|
||||
assetId
|
||||
);
|
||||
|
||||
const resultJson = utf8Decoder.decode(resultBytes);
|
||||
const result = JSON.parse(resultJson);
|
||||
console.log('*** Result:', result);
|
||||
}
|
||||
|
||||
/**
|
||||
* submitTransaction() will throw an error containing details of any error responses from the smart contract.
|
||||
*/
|
||||
async function updateNonExistentAsset(contract) {
|
||||
console.log(
|
||||
'\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error'
|
||||
);
|
||||
|
||||
try {
|
||||
await contract.submitTransaction(
|
||||
'UpdateAsset',
|
||||
'asset70',
|
||||
'blue',
|
||||
'5',
|
||||
'Tomoko',
|
||||
'300'
|
||||
);
|
||||
console.log('******** FAILED to return an error');
|
||||
} catch (error) {
|
||||
console.log('*** Successfully caught the error: \n', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined.
|
||||
*/
|
||||
function envOrDefault(key, defaultValue) {
|
||||
return process.env[key] || defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* displayInputParameters() will print the global scope parameters used by the main driver routine.
|
||||
*/
|
||||
function displayInputParameters() {
|
||||
console.log(`channelName: ${channelName}`);
|
||||
console.log(`chaincodeName: ${chaincodeName}`);
|
||||
console.log(`mspId: ${mspId}`);
|
||||
console.log(`cryptoPath: ${cryptoPath}`);
|
||||
console.log(`keyDirectoryPath: ${keyDirectoryPath}`);
|
||||
console.log(`certDirectoryPath: ${certDirectoryPath}`);
|
||||
console.log(`tlsCertPath: ${tlsCertPath}`);
|
||||
console.log(`peerEndpoint: ${peerEndpoint}`);
|
||||
console.log(`peerHostAlias: ${peerHostAlias}`);
|
||||
}
|
||||
14
asset-transfer-basic/application-gateway-typescript/.gitignore
vendored
Normal file
14
asset-transfer-basic/application-gateway-typescript/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
|
@ -0,0 +1 @@
|
|||
engine-strict=true
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import js from '@eslint/js';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
|
||||
languageOptions: {
|
||||
ecmaVersion: 2023,
|
||||
sourceType: 'module',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
});
|
||||
1659
asset-transfer-basic/application-gateway-typescript/package-lock.json
generated
Normal file
1659
asset-transfer-basic/application-gateway-typescript/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic Application implemented in typeScript using fabric-gateway",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"lint": "eslint src",
|
||||
"prepare": "npm run build",
|
||||
"pretest": "npm run lint",
|
||||
"start": "node dist/app.js"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.0",
|
||||
"@hyperledger/fabric-gateway": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@tsconfig/node20": "^20.1.9",
|
||||
"@types/node": "^20.19.33",
|
||||
"eslint": "^10.0.2",
|
||||
"typescript": "~5.8",
|
||||
"typescript-eslint": "^8.56.1"
|
||||
}
|
||||
}
|
||||
247
asset-transfer-basic/application-gateway-typescript/src/app.ts
Normal file
247
asset-transfer-basic/application-gateway-typescript/src/app.ts
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as grpc from '@grpc/grpc-js';
|
||||
import { connect, Contract, hash, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
|
||||
import * as crypto from 'crypto';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { TextDecoder } from 'util';
|
||||
|
||||
const channelName = envOrDefault('CHANNEL_NAME', 'mychannel');
|
||||
const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic');
|
||||
const mspId = envOrDefault('MSP_ID', 'Org1MSP');
|
||||
|
||||
// Path to crypto materials.
|
||||
const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com'));
|
||||
|
||||
// Path to user private key directory.
|
||||
const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'));
|
||||
|
||||
// Path to user certificate directory.
|
||||
const certDirectoryPath = envOrDefault('CERT_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts'));
|
||||
|
||||
// Path to peer tls certificate.
|
||||
const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'));
|
||||
|
||||
// Gateway peer endpoint.
|
||||
const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051');
|
||||
|
||||
// Gateway peer SSL host name override.
|
||||
const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer0.org1.example.com');
|
||||
|
||||
const utf8Decoder = new TextDecoder();
|
||||
const assetId = `asset${String(Date.now())}`;
|
||||
|
||||
async function main(): Promise<void> {
|
||||
displayInputParameters();
|
||||
|
||||
// The gRPC client connection should be shared by all Gateway connections to this endpoint.
|
||||
const client = await newGrpcConnection();
|
||||
|
||||
const gateway = connect({
|
||||
client,
|
||||
identity: await newIdentity(),
|
||||
signer: await newSigner(),
|
||||
hash: hash.sha256,
|
||||
// Default timeouts for different gRPC calls
|
||||
evaluateOptions: () => {
|
||||
return { deadline: Date.now() + 5000 }; // 5 seconds
|
||||
},
|
||||
endorseOptions: () => {
|
||||
return { deadline: Date.now() + 15000 }; // 15 seconds
|
||||
},
|
||||
submitOptions: () => {
|
||||
return { deadline: Date.now() + 5000 }; // 5 seconds
|
||||
},
|
||||
commitStatusOptions: () => {
|
||||
return { deadline: Date.now() + 60000 }; // 1 minute
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Get a network instance representing the channel where the smart contract is deployed.
|
||||
const network = gateway.getNetwork(channelName);
|
||||
|
||||
// Get the smart contract from the network.
|
||||
const contract = network.getContract(chaincodeName);
|
||||
|
||||
// Initialize a set of asset data on the ledger using the chaincode 'InitLedger' function.
|
||||
await initLedger(contract);
|
||||
|
||||
// Return all the current assets on the ledger.
|
||||
await getAllAssets(contract);
|
||||
|
||||
// Create a new asset on the ledger.
|
||||
await createAsset(contract);
|
||||
|
||||
// Update an existing asset asynchronously.
|
||||
await transferAssetAsync(contract);
|
||||
|
||||
// Get the asset details by assetID.
|
||||
await readAssetByID(contract);
|
||||
|
||||
// Update an asset which does not exist.
|
||||
await updateNonExistentAsset(contract)
|
||||
} finally {
|
||||
gateway.close();
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
console.error('******** FAILED to run the application:', error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
async function newGrpcConnection(): Promise<grpc.Client> {
|
||||
const tlsRootCert = await fs.readFile(tlsCertPath);
|
||||
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
|
||||
return new grpc.Client(peerEndpoint, tlsCredentials, {
|
||||
'grpc.ssl_target_name_override': peerHostAlias,
|
||||
});
|
||||
}
|
||||
|
||||
async function newIdentity(): Promise<Identity> {
|
||||
const certPath = await getFirstDirFileName(certDirectoryPath);
|
||||
const credentials = await fs.readFile(certPath);
|
||||
return { mspId, credentials };
|
||||
}
|
||||
|
||||
async function getFirstDirFileName(dirPath: string): Promise<string> {
|
||||
const files = await fs.readdir(dirPath);
|
||||
const file = files[0];
|
||||
if (!file) {
|
||||
throw new Error(`No files in directory: ${dirPath}`);
|
||||
}
|
||||
return path.join(dirPath, file);
|
||||
}
|
||||
|
||||
async function newSigner(): Promise<Signer> {
|
||||
const keyPath = await getFirstDirFileName(keyDirectoryPath);
|
||||
const privateKeyPem = await fs.readFile(keyPath);
|
||||
const privateKey = crypto.createPrivateKey(privateKeyPem);
|
||||
return signers.newPrivateKeySigner(privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* This type of transaction would typically only be run once by an application the first time it was started after its
|
||||
* initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function.
|
||||
*/
|
||||
async function initLedger(contract: Contract): Promise<void> {
|
||||
console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger');
|
||||
|
||||
await contract.submitTransaction('InitLedger');
|
||||
|
||||
console.log('*** Transaction committed successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a transaction to query ledger state.
|
||||
*/
|
||||
async function getAllAssets(contract: Contract): Promise<void> {
|
||||
console.log('\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger');
|
||||
|
||||
const resultBytes = await contract.evaluateTransaction('GetAllAssets');
|
||||
|
||||
const resultJson = utf8Decoder.decode(resultBytes);
|
||||
const result: unknown = JSON.parse(resultJson);
|
||||
console.log('*** Result:', result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a transaction synchronously, blocking until it has been committed to the ledger.
|
||||
*/
|
||||
async function createAsset(contract: Contract): Promise<void> {
|
||||
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments');
|
||||
|
||||
await contract.submitTransaction(
|
||||
'CreateAsset',
|
||||
assetId,
|
||||
'yellow',
|
||||
'5',
|
||||
'Tom',
|
||||
'1300',
|
||||
);
|
||||
|
||||
console.log('*** Transaction committed successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit transaction asynchronously, allowing the application to process the smart contract response (e.g. update a UI)
|
||||
* while waiting for the commit notification.
|
||||
*/
|
||||
async function transferAssetAsync(contract: Contract): Promise<void> {
|
||||
console.log('\n--> Async Submit Transaction: TransferAsset, updates existing asset owner');
|
||||
|
||||
const commit = await contract.submitAsync('TransferAsset', {
|
||||
arguments: [assetId, 'Saptha'],
|
||||
});
|
||||
const oldOwner = utf8Decoder.decode(commit.getResult());
|
||||
|
||||
console.log(`*** Successfully submitted transaction to transfer ownership from ${oldOwner} to Saptha`);
|
||||
console.log('*** Waiting for transaction commit');
|
||||
|
||||
const status = await commit.getStatus();
|
||||
if (!status.successful) {
|
||||
throw new Error(`Transaction ${status.transactionId} failed to commit with status code ${String(status.code)}`);
|
||||
}
|
||||
|
||||
console.log('*** Transaction committed successfully');
|
||||
}
|
||||
|
||||
async function readAssetByID(contract: Contract): Promise<void> {
|
||||
console.log('\n--> Evaluate Transaction: ReadAsset, function returns asset attributes');
|
||||
|
||||
const resultBytes = await contract.evaluateTransaction('ReadAsset', assetId);
|
||||
|
||||
const resultJson = utf8Decoder.decode(resultBytes);
|
||||
const result: unknown = JSON.parse(resultJson);
|
||||
console.log('*** Result:', result);
|
||||
}
|
||||
|
||||
/**
|
||||
* submitTransaction() will throw an error containing details of any error responses from the smart contract.
|
||||
*/
|
||||
async function updateNonExistentAsset(contract: Contract): Promise<void>{
|
||||
console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error');
|
||||
|
||||
try {
|
||||
await contract.submitTransaction(
|
||||
'UpdateAsset',
|
||||
'asset70',
|
||||
'blue',
|
||||
'5',
|
||||
'Tomoko',
|
||||
'300',
|
||||
);
|
||||
console.log('******** FAILED to return an error');
|
||||
} catch (error) {
|
||||
console.log('*** Successfully caught the error: \n', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined.
|
||||
*/
|
||||
function envOrDefault(key: string, defaultValue: string): string {
|
||||
return process.env[key] || defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* displayInputParameters() will print the global scope parameters used by the main driver routine.
|
||||
*/
|
||||
function displayInputParameters(): void {
|
||||
console.log(`channelName: ${channelName}`);
|
||||
console.log(`chaincodeName: ${chaincodeName}`);
|
||||
console.log(`mspId: ${mspId}`);
|
||||
console.log(`cryptoPath: ${cryptoPath}`);
|
||||
console.log(`keyDirectoryPath: ${keyDirectoryPath}`);
|
||||
console.log(`certDirectoryPath: ${certDirectoryPath}`);
|
||||
console.log(`tlsCertPath: ${tlsCertPath}`);
|
||||
console.log(`peerEndpoint: ${peerEndpoint}`);
|
||||
console.log(`peerHostAlias: ${peerHostAlias}`);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/"],
|
||||
"exclude": ["**/*.spec.*"]
|
||||
}
|
||||
5
asset-transfer-basic/chaincode-external/.dockerignore
Normal file
5
asset-transfer-basic/chaincode-external/.dockerignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
chaincode.env*
|
||||
*.json
|
||||
*.md
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
3
asset-transfer-basic/chaincode-external/.gitignore
vendored
Normal file
3
asset-transfer-basic/chaincode-external/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*.tar.gz
|
||||
*.tgz
|
||||
crypto/*.pem
|
||||
14
asset-transfer-basic/chaincode-external/Dockerfile
Normal file
14
asset-transfer-basic/chaincode-external/Dockerfile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Copyright IBM Corp. All Rights Reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:alpine
|
||||
|
||||
WORKDIR /go/src/github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-external
|
||||
COPY . .
|
||||
|
||||
RUN go get -d -v ./...
|
||||
RUN go install -v ./...
|
||||
|
||||
EXPOSE 9999
|
||||
CMD ["chaincode-external"]
|
||||
252
asset-transfer-basic/chaincode-external/README.md
Executable file
252
asset-transfer-basic/chaincode-external/README.md
Executable file
|
|
@ -0,0 +1,252 @@
|
|||
# Asset-Transfer-Basic as an external service
|
||||
|
||||
This sample provides an introduction to how to use external builder and launcher scripts to run chaincode as an external service to your peer. For more information, see the [Chaincode as an external service](https://hyperledger-fabric.readthedocs.io/en/latest/cc_service.html) topic in the Fabric documentation.
|
||||
|
||||
**Note:** each organization in a real network would need to setup and host their own instance of the external service. In this tutorial, we use the same instance for both organizations for simplicity.
|
||||
|
||||
## Setting up the external builder and launcher
|
||||
|
||||
External Builders and Launchers is an advanced feature that typically requires custom packaging of the peer image so that it contains all the tools your builder and launcher require. This sample uses very simple (and crude) shell scripts that can be run directly within the default Fabric peer images.
|
||||
|
||||
Open the `config/core.yaml` file at the top of the `fabric-samples` directory. If you do not have this file, follow the instructions to [Install the Samples, Binaries and Docker Images](https://hyperledger-fabric.readthedocs.io/en/latest/install.html) to download the Fabric binaries and configuration files alongside the Fabric samples.
|
||||
|
||||
Modify the `externalBuilders` field in the `core.yaml` file to resemble the configuration below:
|
||||
|
||||
```
|
||||
externalBuilders:
|
||||
- path: /opt/gopath/src/github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-external/sampleBuilder
|
||||
name: external-sample-builder
|
||||
```
|
||||
|
||||
This update sets the name of the external builder as `external-sample-builder`, and the path of the builder to the scripts provided in this sample. Note that this is the path within the peer container, not your local machine.
|
||||
|
||||
To set the path within the peer container, you will need to modify the docker compose file to mount a couple of additional volumes. Open the file `test-network/compose/docker/docker-compose-test-net.yaml`, and add to the `volumes` section of both `peer0.org1.example.com` and `peer0.org2.example.com` the following two lines:
|
||||
|
||||
```
|
||||
- ../..:/opt/gopath/src/github.com/hyperledger/fabric-samples
|
||||
- ../../config/core.yaml:/etc/hyperledger/peercfg/core.yaml
|
||||
```
|
||||
|
||||
This update will mount the `core.yaml` that you modified into the peer container and override the configuration file within the peer image. The update also mounts the fabric-sample builder so that it can be found at the location that you specified in `core.yaml`. You also have the option of commenting out the line `- /var/run/docker.sock:/host/var/run/docker.sock`, since we no longer need to access the docker daemon from inside the peer container to launch the chaincode.
|
||||
|
||||
## Packaging and installing Chaincode
|
||||
|
||||
The Asset-Transfer-Basic external chaincode requires two environment variables to run, `CHAINCODE_SERVER_ADDRESS` and `CHAINCODE_ID`, which are described and set in the `chaincode.env` file.
|
||||
|
||||
You need to provide a `connection.json` configuration file to your peer in order to connect to the external Asset-Transfer-Basic service. The address specified in the `connection.json` must correspond to the `CHAINCODE_SERVER_ADDRESS` value in `chaincode.env`, which is `asset-transfer-basic.org1.example.com:9999` in our example.
|
||||
|
||||
Because we will run our chaincode as an external service, the chaincode itself does not need to be included in the chaincode
|
||||
package that gets installed to each peer. Only the configuration and metadata information needs to be included
|
||||
in the package. Since the packaging is trivial, we can manually create the chaincode package.
|
||||
|
||||
Open a new terminal and navigate to the `fabric-samples/asset-transfer-basic/chaincode-external` directory.
|
||||
|
||||
```
|
||||
cd fabric-samples/asset-transfer-basic/chaincode-external
|
||||
```
|
||||
|
||||
First, create a `code.tar.gz` archive containing the `connection.json` file:
|
||||
|
||||
```
|
||||
tar cfz code.tar.gz connection.json
|
||||
```
|
||||
|
||||
Then, create the chaincode package, including the `code.tar.gz` file and the supplied `metadata.json` file:
|
||||
|
||||
```
|
||||
tar cfz asset-transfer-basic-external.tgz metadata.json code.tar.gz
|
||||
```
|
||||
|
||||
You are now ready to deploy the external chaincode sample.
|
||||
|
||||
## Starting the test network
|
||||
|
||||
We will use the Fabric test network to run the external chaincode. Open a new terminal and navigate to the `fabric-samples/test-network` directory.
|
||||
|
||||
```
|
||||
cd fabric-samples/test-network
|
||||
```
|
||||
|
||||
Run the following command to deploy the test network and create a new channel:
|
||||
|
||||
```
|
||||
./network.sh up createChannel -c mychannel -ca
|
||||
```
|
||||
|
||||
We are now ready to deploy the external chaincode.
|
||||
|
||||
## Installing the external chaincode
|
||||
|
||||
We can't use the test network script to install an external chaincode so we will have to do a bit more work. However, we can still leverage part of the test-network scripts to make this easier.
|
||||
|
||||
From the `test-network` directory, set the following environment variables to use the Fabric binaries:
|
||||
|
||||
```
|
||||
export PATH=${PWD}/../bin:$PATH
|
||||
export FABRIC_CFG_PATH=$PWD/../config/
|
||||
```
|
||||
|
||||
Run the following command to import functions from the `envVar.sh` script into your terminal. These functions allow you to act as either test network organization.
|
||||
|
||||
```
|
||||
. ./scripts/envVar.sh
|
||||
```
|
||||
|
||||
Run the following commands to install the `asset-transfer-basic-external.tar.gz` chaincode on org1. The `setGlobals` function simply sets the environment variables that allow you to act as org1 or org2.
|
||||
|
||||
```
|
||||
setGlobals 1
|
||||
peer lifecycle chaincode install ../asset-transfer-basic/chaincode-external/asset-transfer-basic-external.tgz
|
||||
```
|
||||
|
||||
Install the chaincode package on the org2 peer:
|
||||
|
||||
```
|
||||
setGlobals 2
|
||||
peer lifecycle chaincode install ../asset-transfer-basic/chaincode-external/asset-transfer-basic-external.tgz
|
||||
```
|
||||
|
||||
Run the following command to query the package ID of the chaincode that you just installed:
|
||||
|
||||
```
|
||||
setGlobals 1
|
||||
peer lifecycle chaincode queryinstalled --peerAddresses localhost:7051 --tlsRootCertFiles organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
|
||||
```
|
||||
|
||||
The command will return output similar to the following:
|
||||
|
||||
```
|
||||
Installed chaincodes on peer:
|
||||
Package ID: basic_1.0:ecfc83f251b7c2d9ef376bc3fc20fc6b9744c0fc0a8923092af6542af94790c3, Label: basic_1.0
|
||||
```
|
||||
|
||||
Save the package ID that was returned by the command as an environment variable. The ID will not be the same for all users, so you need to set the variable using the ID from your command window:
|
||||
|
||||
```
|
||||
export CHAINCODE_ID=basic_1.0:ecfc83f251b7c2d9ef376bc3fc20fc6b9744c0fc0a8923092af6542af94790c3
|
||||
```
|
||||
|
||||
## Running the Asset-Transfer-Basic external service
|
||||
|
||||
We are going to run the smart contract as an external service by first building and then starting a docker container. Open a new terminal and navigate back to the `chaincode-external` directory:
|
||||
|
||||
```
|
||||
cd fabric-samples/asset-transfer-basic/chaincode-external
|
||||
```
|
||||
|
||||
In this directory, open the `chaincode.env` file to set the `CHAINCODE_ID` variable to the same package ID that was returned by the `peer lifecycle chaincode queryinstalled` command. The value should be the same as the environment variable that you set in the previous terminal.
|
||||
|
||||
After you edit the `chaincode.env` file, you can use the `Dockerfile` to build an image of the external Asset-Transfer-Basic chaincode:
|
||||
|
||||
```
|
||||
docker build -t hyperledger/asset-transfer-basic .
|
||||
```
|
||||
|
||||
You can then run the image to start the Asset-Transfer-Basic service:
|
||||
|
||||
```
|
||||
docker run -it --rm --name asset-transfer-basic.org1.example.com --hostname asset-transfer-basic.org1.example.com --env-file chaincode.env --network=fabric_test hyperledger/asset-transfer-basic
|
||||
```
|
||||
|
||||
This will start and run the external chaincode service within the container.
|
||||
|
||||
## Deploy the Asset-Transfer-Basic external chaincode definition to the channel
|
||||
|
||||
Navigate back to the `test-network` directory to finish deploying the chaincode definition of the external smart contract to the channel. Make sure that your environment variables are still set.
|
||||
|
||||
```
|
||||
setGlobals 2
|
||||
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$PWD/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --channelID mychannel --name basic --version 1.0 --package-id $CHAINCODE_ID --sequence 1
|
||||
|
||||
setGlobals 1
|
||||
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$PWD/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --channelID mychannel --name basic --version 1.0 --package-id $CHAINCODE_ID --sequence 1
|
||||
|
||||
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$PWD/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --channelID mychannel --name basic --peerAddresses localhost:7051 --tlsRootCertFiles "$PWD/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --version 1.0 --sequence 1
|
||||
```
|
||||
|
||||
The commands above approve the chaincode definition for the external chaincode and commits the definition to the channel. The resulting output should be similar to the following:
|
||||
|
||||
```
|
||||
2020-08-05 15:41:44.982 PDT [chaincodeCmd] ClientWait -> INFO 001 txid [6bdbe040b99a45cc90a23ec21f02ea5da7be8b70590eb04ff3323ef77fdedfc7] committed with status (VALID) at localhost:7051
|
||||
2020-08-05 15:41:44.983 PDT [chaincodeCmd] ClientWait -> INFO 002 txid [6bdbe040b99a45cc90a23ec21f02ea5da7be8b70590eb04ff3323ef77fdedfc7] committed with status (VALID) at localhost:9051
|
||||
```
|
||||
|
||||
Now that we have started the chaincode service and deployed it to the channel, we can submit transactions as we would with a normal chaincode.
|
||||
|
||||
## Using the Asset-Transfer-Basic external chaincode
|
||||
|
||||
Open yet another terminal and navigate to the `fabric-samples/asset-transfer-basic/application-gateway-go` directory:
|
||||
|
||||
```
|
||||
cd fabric-samples/asset-transfer-basic/application-gateway-go
|
||||
```
|
||||
|
||||
Run the following commands to use the node application in this directory to test the external smart contract:
|
||||
|
||||
```
|
||||
go run .
|
||||
```
|
||||
|
||||
If all goes well, the program should run exactly the same as described in the "Writing Your First Application" tutorial.
|
||||
|
||||
## Enabling TLS for chaincode and peer communication
|
||||
|
||||
**Note:** This section uses an example of self-signed certificate. You may use your organization hosted CA to issue the certificate and generate a key for production deployment.
|
||||
|
||||
In the sample so far, you connected both peers in `test-network` to the single instance of chaincode server. However, if you would like to enable TLS between the peer nodes and the chaincode server, each peer node needs to have its own CA certificate. Enabling TLS is made possible at runtime in the chaincode.
|
||||
|
||||
- As a first step generate a keypair that can be used. Run these commands from the `fabric-samples/asset-transfer-basic/chaincode-external` directory.
|
||||
|
||||
_Find instructions to install `openssl` in [openssl.org](https://www.openssl.org/)_
|
||||
|
||||
For `org1.example.com`
|
||||
|
||||
```
|
||||
openssl req -nodes -x509 -newkey rsa:4096 -keyout crypto/key1.pem -out crypto/cert1.pem -subj "/C=IN/ST=KA/L=Bangalore/O=example Inc/OU=Developer/CN=asset-transfer-basic.org1.example.com/emailAddress=dev@asset-transfer-basic.org1.example.com"
|
||||
```
|
||||
|
||||
For `org2.example.com`
|
||||
|
||||
```
|
||||
openssl req -nodes -x509 -newkey rsa:4096 -keyout crypto/key2.pem -out crypto/cert2.pem -subj "/C=IN/ST=KA/L=Bangalore/O=example Inc/OU=Developer/CN=asset-transfer-basic.org2.example.com/emailAddress=dev@asset-transfer-basic.org2.example.com"
|
||||
```
|
||||
|
||||
- Copy the CA file contents for both `org1.example.com` & `org2.example.com`
|
||||
|
||||
```
|
||||
cp ../../test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem crypto/rootcert1.pem
|
||||
cp ../../test-network/organizations/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem crypto/rootcert2.pem
|
||||
```
|
||||
|
||||
- Generate a client key and cert for auth purpose. You need a key and cert generated from the CA of each organization. Peer nodes act as clients to chaincode server.
|
||||
|
||||
- Change the `connection.json` with the below contents. The `root_cert` parameter is the root CA certificate which the chaincode server is run with. You may run the below commands to get the certificate file contents as strings and copy them when needed.
|
||||
|
||||
```
|
||||
awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' crypto/cert1.pem
|
||||
awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' crypto/cert2.pem
|
||||
```
|
||||
|
||||
Similarly, replace the `client_key` and the `client_cert` contents with the values from the previous step.
|
||||
|
||||
```
|
||||
{
|
||||
"address": "asset-transfer-basic.org1.example.com:9999",
|
||||
"dial_timeout": "10s",
|
||||
"tls_required": true,
|
||||
"client_auth_required": true,
|
||||
"client_key": "-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----",
|
||||
"client_cert": "-----BEGIN CERTIFICATE---- ... -----END CERTIFICATE-----",
|
||||
"root_cert": "-----BEGIN CERTIFICATE---- ... -----END CERTIFICATE-----"
|
||||
}
|
||||
```
|
||||
|
||||
- Follow the instructions in [Package](#packaging-and-installing-chaincode) and [Install](#installing-the-external-chaincode) steps for each organization. Remember that the chaincode server's address for the second organization is `asset-transfer-basic.org2.example.com:9999`.
|
||||
|
||||
- Copy the appropriate `CHAINCODE_ID` to both [chaincode1.env](./chaincode1.env) and [chaincode2.env](./chaincode2.env) files. Bring up the chaincode containers using the docker-compose command below
|
||||
|
||||
```
|
||||
docker-compose up -f docker-compose-chaincode.yaml up --build -d
|
||||
```
|
||||
|
||||
- Follow the instructions in [Finish Deployment](#finish-deploying-the-asset-transfer-basic-external-chaincode-) for each organization seperately.
|
||||
297
asset-transfer-basic/chaincode-external/assetTransfer.go
Normal file
297
asset-transfer-basic/chaincode-external/assetTransfer.go
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
)
|
||||
|
||||
type serverConfig struct {
|
||||
CCID string
|
||||
Address string
|
||||
}
|
||||
|
||||
// SmartContract provides functions for managing an asset
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// Asset describes basic details of what makes up a simple asset
|
||||
type Asset struct {
|
||||
ID string `json:"ID"`
|
||||
Color string `json:"color"`
|
||||
Size int `json:"size"`
|
||||
Owner string `json:"owner"`
|
||||
AppraisedValue int `json:"appraisedValue"`
|
||||
}
|
||||
|
||||
// QueryResult structure used for handling result of query
|
||||
type QueryResult struct {
|
||||
Key string `json:"Key"`
|
||||
Record *Asset
|
||||
}
|
||||
|
||||
// InitLedger adds a base set of cars to the ledger
|
||||
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
|
||||
assets := []Asset{
|
||||
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
|
||||
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
|
||||
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
|
||||
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
|
||||
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
|
||||
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
|
||||
}
|
||||
|
||||
for _, asset := range assets {
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ctx.GetStub().PutState(asset.ID, assetJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put to world state: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id, color string, size int, owner string, appraisedValue int) error {
|
||||
exists, err := s.AssetExists(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("the asset %s already exists", id)
|
||||
}
|
||||
asset := Asset{
|
||||
ID: id,
|
||||
Color: color,
|
||||
Size: size,
|
||||
Owner: owner,
|
||||
AppraisedValue: appraisedValue,
|
||||
}
|
||||
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.GetStub().PutState(id, assetJSON)
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
|
||||
assetJSON, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read from world state. %s", err.Error())
|
||||
}
|
||||
if assetJSON == nil {
|
||||
return nil, fmt.Errorf("the asset %s does not exist", id)
|
||||
}
|
||||
|
||||
var asset Asset
|
||||
err = json.Unmarshal(assetJSON, &asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &asset, nil
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id, color string, size int, owner string, appraisedValue int) error {
|
||||
exists, err := s.AssetExists(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("the asset %s does not exist", id)
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
asset := Asset{
|
||||
ID: id,
|
||||
Color: color,
|
||||
Size: size,
|
||||
Owner: owner,
|
||||
AppraisedValue: appraisedValue,
|
||||
}
|
||||
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.GetStub().PutState(id, assetJSON)
|
||||
}
|
||||
|
||||
// DeleteAsset deletes a given asset from the world state.
|
||||
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
|
||||
exists, err := s.AssetExists(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("the asset %s does not exist", id)
|
||||
}
|
||||
|
||||
return ctx.GetStub().DelState(id)
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state
|
||||
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
|
||||
assetJSON, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to read from world state. %s", err.Error())
|
||||
}
|
||||
|
||||
return assetJSON != nil, nil
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in world state, and returns the old owner.
|
||||
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
|
||||
asset, err := s.ReadAsset(ctx, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
oldOwner := asset.Owner
|
||||
asset.Owner = newOwner
|
||||
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = ctx.GetStub().PutState(id, assetJSON)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return oldOwner, nil
|
||||
}
|
||||
|
||||
// GetAllAssets returns all assets found in world state
|
||||
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resultsIterator.Close()
|
||||
|
||||
var results []QueryResult
|
||||
|
||||
for resultsIterator.HasNext() {
|
||||
queryResponse, err := resultsIterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var asset Asset
|
||||
err = json.Unmarshal(queryResponse.Value, &asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryResult := QueryResult{Key: queryResponse.Key, Record: &asset}
|
||||
results = append(results, queryResult)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// See chaincode.env.example
|
||||
config := serverConfig{
|
||||
CCID: os.Getenv("CHAINCODE_ID"),
|
||||
Address: os.Getenv("CHAINCODE_SERVER_ADDRESS"),
|
||||
}
|
||||
|
||||
chaincode, err := contractapi.NewChaincode(&SmartContract{})
|
||||
|
||||
if err != nil {
|
||||
log.Panicf("error create asset-transfer-basic chaincode: %s", err)
|
||||
}
|
||||
|
||||
server := &shim.ChaincodeServer{
|
||||
CCID: config.CCID,
|
||||
Address: config.Address,
|
||||
CC: chaincode,
|
||||
TLSProps: getTLSProperties(),
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
log.Panicf("error starting asset-transfer-basic chaincode: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getTLSProperties() shim.TLSProperties {
|
||||
// Check if chaincode is TLS enabled
|
||||
tlsDisabledStr := getEnvOrDefault("CHAINCODE_TLS_DISABLED", "true")
|
||||
key := getEnvOrDefault("CHAINCODE_TLS_KEY", "")
|
||||
cert := getEnvOrDefault("CHAINCODE_TLS_CERT", "")
|
||||
clientCACert := getEnvOrDefault("CHAINCODE_CLIENT_CA_CERT", "")
|
||||
|
||||
// convert tlsDisabledStr to boolean
|
||||
tlsDisabled := getBoolOrDefault(tlsDisabledStr, false)
|
||||
var keyBytes, certBytes, clientCACertBytes []byte
|
||||
var err error
|
||||
|
||||
if !tlsDisabled {
|
||||
keyBytes, err = os.ReadFile(key)
|
||||
if err != nil {
|
||||
log.Panicf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
certBytes, err = os.ReadFile(cert)
|
||||
if err != nil {
|
||||
log.Panicf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
}
|
||||
// Did not request for the peer cert verification
|
||||
if clientCACert != "" {
|
||||
clientCACertBytes, err = os.ReadFile(clientCACert)
|
||||
if err != nil {
|
||||
log.Panicf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return shim.TLSProperties{
|
||||
Disabled: tlsDisabled,
|
||||
Key: keyBytes,
|
||||
Cert: certBytes,
|
||||
ClientCACerts: clientCACertBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func getEnvOrDefault(env, defaultVal string) string {
|
||||
value, ok := os.LookupEnv(env)
|
||||
if !ok {
|
||||
value = defaultVal
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Note that the method returns default value if the string
|
||||
// cannot be parsed!
|
||||
func getBoolOrDefault(value string, defaultVal bool) bool {
|
||||
parsed, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return defaultVal
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
24
asset-transfer-basic/chaincode-external/chaincode.env
Normal file
24
asset-transfer-basic/chaincode-external/chaincode.env
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# CHAINCODE_SERVER_ADDRESS must be set to the host and port where the peer can
|
||||
# connect to the chaincode server
|
||||
CHAINCODE_SERVER_ADDRESS=asset-transfer-basic.org1.example.com:9999
|
||||
|
||||
# CHAINCODE_ID must be set to the Package ID that is assigned to the chaincode
|
||||
# on install. The `peer lifecycle chaincode queryinstalled` command can be
|
||||
# used to get the ID after install if required
|
||||
CHAINCODE_ID=basic_1.0:0262396ccaffaa2174bc09f750f742319c4f14d60b16334d2c8921b6842c090c
|
||||
|
||||
# Optional parameters that will be used for TLS connection between peer node
|
||||
# and the chaincode.
|
||||
# TLS is disabled by default, uncomment the following line to enable TLS connection
|
||||
# CHAINCODE_TLS_DISABLED=false
|
||||
|
||||
# Following variables will be ignored if TLS is not enabled.
|
||||
# They need to be in PEM format
|
||||
# CHAINCODE_TLS_KEY=/path/to/private/key/file
|
||||
# CHAINCODE_TLS_CERT=/path/to/public/cert/file
|
||||
|
||||
# The following variable will be used by the chaincode server to verify the
|
||||
# connection from the peer node.
|
||||
# Note that when this is set a single chaincode server cannot be shared
|
||||
# across organizations unless their root CA is same.
|
||||
# CHAINCODE_CLIENT_CA_CERT=/path/to/peer/organization/root/ca/cert/file
|
||||
24
asset-transfer-basic/chaincode-external/chaincode1.env
Normal file
24
asset-transfer-basic/chaincode-external/chaincode1.env
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# CHAINCODE_SERVER_ADDRESS must be set to the host and port where the peer can
|
||||
# connect to the chaincode server
|
||||
CHAINCODE_SERVER_ADDRESS=asset-transfer-basic.org1.example.com:9999
|
||||
|
||||
# CHAINCODE_ID must be set to the Package ID that is assigned to the chaincode
|
||||
# on install. The `peer lifecycle chaincode queryinstalled` command can be
|
||||
# used to get the ID after install if required
|
||||
CHAINCODE_ID=basic_1.0:6726c6b6d8ff66fcf5710b72c6ce512d24f118c51c3de510b3d43e51fa592a7d
|
||||
|
||||
# Optional parameters that will be used for TLS connection between peer node
|
||||
# and the chaincode.
|
||||
# TLS is disabled by default, uncomment the following line to enable TLS connection
|
||||
CHAINCODE_TLS_DISABLED=false
|
||||
|
||||
# Following variables will be ignored if TLS is not enabled.
|
||||
# They need to be in PEM format
|
||||
CHAINCODE_TLS_KEY=/crypto/key1.pem
|
||||
CHAINCODE_TLS_CERT=/crypto/cert1.pem
|
||||
|
||||
# The following variable will be used by the chaincode server to verify the
|
||||
# connection from the peer node.
|
||||
# Note that when this is set a single chaincode server cannot be shared
|
||||
# across organizations unless their root CA is same.
|
||||
CHAINCODE_CLIENT_CA_CERT=/crypto/rootcert1.pem
|
||||
24
asset-transfer-basic/chaincode-external/chaincode2.env
Normal file
24
asset-transfer-basic/chaincode-external/chaincode2.env
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# CHAINCODE_SERVER_ADDRESS must be set to the host and port where the peer can
|
||||
# connect to the chaincode server
|
||||
CHAINCODE_SERVER_ADDRESS=asset-transfer-basic.org2.example.com:9999
|
||||
|
||||
# CHAINCODE_ID must be set to the Package ID that is assigned to the chaincode
|
||||
# on install. The `peer lifecycle chaincode queryinstalled` command can be
|
||||
# used to get the ID after install if required
|
||||
CHAINCODE_ID=basic_1.0:e8f9052385e3763ecf5635591155da05d8efbb6905ccbfc1c7229eb6bd28df1b
|
||||
|
||||
# Optional parameters that will be used for TLS connection between peer node
|
||||
# and the chaincode.
|
||||
# TLS is disabled by default, uncomment the following line to enable TLS connection
|
||||
CHAINCODE_TLS_DISABLED=false
|
||||
|
||||
# Following variables will be ignored if TLS is not enabled.
|
||||
# They need to be in PEM format
|
||||
CHAINCODE_TLS_KEY=/crypto/key2.pem
|
||||
CHAINCODE_TLS_CERT=/crypto/cert2.pem
|
||||
|
||||
# The following variable will be used by the chaincode server to verify the
|
||||
# connection from the peer node.
|
||||
# Note that when this is set a single chaincode server cannot be shared
|
||||
# across organizations unless their root CA is same.
|
||||
CHAINCODE_CLIENT_CA_CERT=/crypto/rootcert2.pem
|
||||
5
asset-transfer-basic/chaincode-external/connection.json
Normal file
5
asset-transfer-basic/chaincode-external/connection.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"address": "asset-transfer-basic.org1.example.com:9999",
|
||||
"dial_timeout": "10s",
|
||||
"tls_required": false
|
||||
}
|
||||
0
asset-transfer-basic/chaincode-external/crypto/.gitkeep
Normal file
0
asset-transfer-basic/chaincode-external/crypto/.gitkeep
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
networks:
|
||||
docker_test:
|
||||
external: true
|
||||
|
||||
services:
|
||||
asset-transfer-basic.org1.example.com:
|
||||
build: .
|
||||
container_name: asset-transfer-basic.org1.example.com
|
||||
hostname: asset-transfer-basic.org1.example.com
|
||||
volumes:
|
||||
- ./crypto:/crypto
|
||||
env_file:
|
||||
- chaincode1.env
|
||||
networks:
|
||||
docker_test:
|
||||
expose:
|
||||
- 9999
|
||||
|
||||
asset-transfer-basic.org2.example.com:
|
||||
build: .
|
||||
container_name: asset-transfer-basic.org2.example.com
|
||||
hostname: asset-transfer-basic.org2.example.com
|
||||
volumes:
|
||||
- ./crypto:/crypto
|
||||
env_file:
|
||||
- chaincode2.env
|
||||
networks:
|
||||
docker_test:
|
||||
expose:
|
||||
- 9999
|
||||
28
asset-transfer-basic/chaincode-external/go.mod
Normal file
28
asset-transfer-basic/chaincode-external/go.mod
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
module github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-external
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
81
asset-transfer-basic/chaincode-external/go.sum
Normal file
81
asset-transfer-basic/chaincode-external/go.sum
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 h1:IhkHfrl5X/fVnmB6pWeCYCdIJRi9bxj+WTnVN8DtW3c=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0/go.mod h1:PHHaFffjw7p7n9bmCfcm7RqDqYdivNEsJdiNIKZo5Lk=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 h1:rmUoBmciB0GL/miqcbJmJbgp5QTWoJUrZo+CNxrNLF4=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0/go.mod h1:FeWeO/jwGjiME7ak3GufqKIcwkejtzrDG4QxbfKydWs=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
4
asset-transfer-basic/chaincode-external/metadata.json
Normal file
4
asset-transfer-basic/chaincode-external/metadata.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"type": "external",
|
||||
"label": "basic_1.0"
|
||||
}
|
||||
21
asset-transfer-basic/chaincode-external/sampleBuilder/bin/build
Executable file
21
asset-transfer-basic/chaincode-external/sampleBuilder/bin/build
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SOURCE=$1
|
||||
OUTPUT=$3
|
||||
|
||||
#external chaincodes expect connection.json file in the chaincode package
|
||||
if [ ! -f "$SOURCE/connection.json" ]; then
|
||||
>&2 echo "$SOURCE/connection.json not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#simply copy the endpoint information to specified output location
|
||||
cp $SOURCE/connection.json $OUTPUT/connection.json
|
||||
|
||||
if [ -d "$SOURCE/metadata" ]; then
|
||||
cp -a $SOURCE/metadata $OUTPUT/metadata
|
||||
fi
|
||||
|
||||
exit 0
|
||||
14
asset-transfer-basic/chaincode-external/sampleBuilder/bin/detect
Executable file
14
asset-transfer-basic/chaincode-external/sampleBuilder/bin/detect
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
METADIR=$2
|
||||
# check if the "type" field is set to "external"
|
||||
# crude way without jq which is not in the default fabric peer image
|
||||
TYPE=$(tr -d '\n' < "$METADIR/metadata.json" | awk -F':' '{ for (i = 1; i < NF; i++){ if ($i~/type/) { print $(i+1); break }}}'| cut -d\" -f2)
|
||||
|
||||
if [ "$TYPE" = "external" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 1
|
||||
22
asset-transfer-basic/chaincode-external/sampleBuilder/bin/release
Executable file
22
asset-transfer-basic/chaincode-external/sampleBuilder/bin/release
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BLD="$1"
|
||||
RELEASE="$2"
|
||||
|
||||
if [ -d "$BLD/metadata" ]; then
|
||||
cp -a "$BLD/metadata/"* "$RELEASE/"
|
||||
fi
|
||||
|
||||
#external chaincodes expect artifacts to be placed under "$RELEASE"/chaincode/server
|
||||
if [ -f $BLD/connection.json ]; then
|
||||
mkdir -p "$RELEASE"/chaincode/server
|
||||
cp $BLD/connection.json "$RELEASE"/chaincode/server
|
||||
|
||||
#if tls_required is true, copy TLS files (using above example, the fully qualified path for these fils would be "$RELEASE"/chaincode/server/tls)
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 1
|
||||
23
asset-transfer-basic/chaincode-go/assetTransfer.go
Normal file
23
asset-transfer-basic/chaincode-go/assetTransfer.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"
|
||||
)
|
||||
|
||||
func main() {
|
||||
assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
|
||||
if err != nil {
|
||||
log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
|
||||
}
|
||||
|
||||
if err := assetChaincode.Start(); err != nil {
|
||||
log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
|
||||
}
|
||||
}
|
||||
2991
asset-transfer-basic/chaincode-go/chaincode/mocks/chaincodestub.go
Normal file
2991
asset-transfer-basic/chaincode-go/chaincode/mocks/chaincodestub.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,235 @@
|
|||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/ledger/queryresult"
|
||||
)
|
||||
|
||||
type StateQueryIterator struct {
|
||||
CloseStub func() error
|
||||
closeMutex sync.RWMutex
|
||||
closeArgsForCall []struct {
|
||||
}
|
||||
closeReturns struct {
|
||||
result1 error
|
||||
}
|
||||
closeReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
HasNextStub func() bool
|
||||
hasNextMutex sync.RWMutex
|
||||
hasNextArgsForCall []struct {
|
||||
}
|
||||
hasNextReturns struct {
|
||||
result1 bool
|
||||
}
|
||||
hasNextReturnsOnCall map[int]struct {
|
||||
result1 bool
|
||||
}
|
||||
NextStub func() (*queryresult.KV, error)
|
||||
nextMutex sync.RWMutex
|
||||
nextArgsForCall []struct {
|
||||
}
|
||||
nextReturns struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}
|
||||
nextReturnsOnCall map[int]struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) Close() error {
|
||||
fake.closeMutex.Lock()
|
||||
ret, specificReturn := fake.closeReturnsOnCall[len(fake.closeArgsForCall)]
|
||||
fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.CloseStub
|
||||
fakeReturns := fake.closeReturns
|
||||
fake.recordInvocation("Close", []interface{}{})
|
||||
fake.closeMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseCallCount() int {
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
return len(fake.closeArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseCalls(stub func() error) {
|
||||
fake.closeMutex.Lock()
|
||||
defer fake.closeMutex.Unlock()
|
||||
fake.CloseStub = stub
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseReturns(result1 error) {
|
||||
fake.closeMutex.Lock()
|
||||
defer fake.closeMutex.Unlock()
|
||||
fake.CloseStub = nil
|
||||
fake.closeReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseReturnsOnCall(i int, result1 error) {
|
||||
fake.closeMutex.Lock()
|
||||
defer fake.closeMutex.Unlock()
|
||||
fake.CloseStub = nil
|
||||
if fake.closeReturnsOnCall == nil {
|
||||
fake.closeReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.closeReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNext() bool {
|
||||
fake.hasNextMutex.Lock()
|
||||
ret, specificReturn := fake.hasNextReturnsOnCall[len(fake.hasNextArgsForCall)]
|
||||
fake.hasNextArgsForCall = append(fake.hasNextArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.HasNextStub
|
||||
fakeReturns := fake.hasNextReturns
|
||||
fake.recordInvocation("HasNext", []interface{}{})
|
||||
fake.hasNextMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextCallCount() int {
|
||||
fake.hasNextMutex.RLock()
|
||||
defer fake.hasNextMutex.RUnlock()
|
||||
return len(fake.hasNextArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextCalls(stub func() bool) {
|
||||
fake.hasNextMutex.Lock()
|
||||
defer fake.hasNextMutex.Unlock()
|
||||
fake.HasNextStub = stub
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextReturns(result1 bool) {
|
||||
fake.hasNextMutex.Lock()
|
||||
defer fake.hasNextMutex.Unlock()
|
||||
fake.HasNextStub = nil
|
||||
fake.hasNextReturns = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextReturnsOnCall(i int, result1 bool) {
|
||||
fake.hasNextMutex.Lock()
|
||||
defer fake.hasNextMutex.Unlock()
|
||||
fake.HasNextStub = nil
|
||||
if fake.hasNextReturnsOnCall == nil {
|
||||
fake.hasNextReturnsOnCall = make(map[int]struct {
|
||||
result1 bool
|
||||
})
|
||||
}
|
||||
fake.hasNextReturnsOnCall[i] = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) Next() (*queryresult.KV, error) {
|
||||
fake.nextMutex.Lock()
|
||||
ret, specificReturn := fake.nextReturnsOnCall[len(fake.nextArgsForCall)]
|
||||
fake.nextArgsForCall = append(fake.nextArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.NextStub
|
||||
fakeReturns := fake.nextReturns
|
||||
fake.recordInvocation("Next", []interface{}{})
|
||||
fake.nextMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextCallCount() int {
|
||||
fake.nextMutex.RLock()
|
||||
defer fake.nextMutex.RUnlock()
|
||||
return len(fake.nextArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextCalls(stub func() (*queryresult.KV, error)) {
|
||||
fake.nextMutex.Lock()
|
||||
defer fake.nextMutex.Unlock()
|
||||
fake.NextStub = stub
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextReturns(result1 *queryresult.KV, result2 error) {
|
||||
fake.nextMutex.Lock()
|
||||
defer fake.nextMutex.Unlock()
|
||||
fake.NextStub = nil
|
||||
fake.nextReturns = struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextReturnsOnCall(i int, result1 *queryresult.KV, result2 error) {
|
||||
fake.nextMutex.Lock()
|
||||
defer fake.nextMutex.Unlock()
|
||||
fake.NextStub = nil
|
||||
if fake.nextReturnsOnCall == nil {
|
||||
fake.nextReturnsOnCall = make(map[int]struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.nextReturnsOnCall[i] = struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
fake.hasNextMutex.RLock()
|
||||
defer fake.hasNextMutex.RUnlock()
|
||||
fake.nextMutex.RLock()
|
||||
defer fake.nextMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
166
asset-transfer-basic/chaincode-go/chaincode/mocks/transaction.go
Normal file
166
asset-transfer-basic/chaincode-go/chaincode/mocks/transaction.go
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid"
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
|
||||
)
|
||||
|
||||
type TransactionContext struct {
|
||||
GetClientIdentityStub func() cid.ClientIdentity
|
||||
getClientIdentityMutex sync.RWMutex
|
||||
getClientIdentityArgsForCall []struct {
|
||||
}
|
||||
getClientIdentityReturns struct {
|
||||
result1 cid.ClientIdentity
|
||||
}
|
||||
getClientIdentityReturnsOnCall map[int]struct {
|
||||
result1 cid.ClientIdentity
|
||||
}
|
||||
GetStubStub func() shim.ChaincodeStubInterface
|
||||
getStubMutex sync.RWMutex
|
||||
getStubArgsForCall []struct {
|
||||
}
|
||||
getStubReturns struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}
|
||||
getStubReturnsOnCall map[int]struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentity() cid.ClientIdentity {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
ret, specificReturn := fake.getClientIdentityReturnsOnCall[len(fake.getClientIdentityArgsForCall)]
|
||||
fake.getClientIdentityArgsForCall = append(fake.getClientIdentityArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.GetClientIdentityStub
|
||||
fakeReturns := fake.getClientIdentityReturns
|
||||
fake.recordInvocation("GetClientIdentity", []interface{}{})
|
||||
fake.getClientIdentityMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityCallCount() int {
|
||||
fake.getClientIdentityMutex.RLock()
|
||||
defer fake.getClientIdentityMutex.RUnlock()
|
||||
return len(fake.getClientIdentityArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityCalls(stub func() cid.ClientIdentity) {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
defer fake.getClientIdentityMutex.Unlock()
|
||||
fake.GetClientIdentityStub = stub
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityReturns(result1 cid.ClientIdentity) {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
defer fake.getClientIdentityMutex.Unlock()
|
||||
fake.GetClientIdentityStub = nil
|
||||
fake.getClientIdentityReturns = struct {
|
||||
result1 cid.ClientIdentity
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityReturnsOnCall(i int, result1 cid.ClientIdentity) {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
defer fake.getClientIdentityMutex.Unlock()
|
||||
fake.GetClientIdentityStub = nil
|
||||
if fake.getClientIdentityReturnsOnCall == nil {
|
||||
fake.getClientIdentityReturnsOnCall = make(map[int]struct {
|
||||
result1 cid.ClientIdentity
|
||||
})
|
||||
}
|
||||
fake.getClientIdentityReturnsOnCall[i] = struct {
|
||||
result1 cid.ClientIdentity
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStub() shim.ChaincodeStubInterface {
|
||||
fake.getStubMutex.Lock()
|
||||
ret, specificReturn := fake.getStubReturnsOnCall[len(fake.getStubArgsForCall)]
|
||||
fake.getStubArgsForCall = append(fake.getStubArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.GetStubStub
|
||||
fakeReturns := fake.getStubReturns
|
||||
fake.recordInvocation("GetStub", []interface{}{})
|
||||
fake.getStubMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubCallCount() int {
|
||||
fake.getStubMutex.RLock()
|
||||
defer fake.getStubMutex.RUnlock()
|
||||
return len(fake.getStubArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubCalls(stub func() shim.ChaincodeStubInterface) {
|
||||
fake.getStubMutex.Lock()
|
||||
defer fake.getStubMutex.Unlock()
|
||||
fake.GetStubStub = stub
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubReturns(result1 shim.ChaincodeStubInterface) {
|
||||
fake.getStubMutex.Lock()
|
||||
defer fake.getStubMutex.Unlock()
|
||||
fake.GetStubStub = nil
|
||||
fake.getStubReturns = struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubReturnsOnCall(i int, result1 shim.ChaincodeStubInterface) {
|
||||
fake.getStubMutex.Lock()
|
||||
defer fake.getStubMutex.Unlock()
|
||||
fake.GetStubStub = nil
|
||||
if fake.getStubReturnsOnCall == nil {
|
||||
fake.getStubReturnsOnCall = make(map[int]struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
})
|
||||
}
|
||||
fake.getStubReturnsOnCall[i] = struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.getClientIdentityMutex.RLock()
|
||||
defer fake.getClientIdentityMutex.RUnlock()
|
||||
fake.getStubMutex.RLock()
|
||||
defer fake.getStubMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
194
asset-transfer-basic/chaincode-go/chaincode/smartcontract.go
Normal file
194
asset-transfer-basic/chaincode-go/chaincode/smartcontract.go
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
package chaincode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
)
|
||||
|
||||
// SmartContract provides functions for managing an Asset
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// Asset describes basic details of what makes up a simple asset
|
||||
// Insert struct field in alphabetic order => to achieve determinism across languages
|
||||
// golang keeps the order when marshal to json but doesn't order automatically
|
||||
type Asset struct {
|
||||
AppraisedValue int `json:"AppraisedValue"`
|
||||
Color string `json:"Color"`
|
||||
ID string `json:"ID"`
|
||||
Owner string `json:"Owner"`
|
||||
Size int `json:"Size"`
|
||||
}
|
||||
|
||||
// InitLedger adds a base set of assets to the ledger
|
||||
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
|
||||
assets := []Asset{
|
||||
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
|
||||
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
|
||||
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
|
||||
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
|
||||
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
|
||||
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
|
||||
}
|
||||
|
||||
for _, asset := range assets {
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ctx.GetStub().PutState(asset.ID, assetJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put to world state. %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
|
||||
exists, err := s.AssetExists(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("the asset %s already exists", id)
|
||||
}
|
||||
|
||||
asset := Asset{
|
||||
ID: id,
|
||||
Color: color,
|
||||
Size: size,
|
||||
Owner: owner,
|
||||
AppraisedValue: appraisedValue,
|
||||
}
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.GetStub().PutState(id, assetJSON)
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
|
||||
assetJSON, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
if assetJSON == nil {
|
||||
return nil, fmt.Errorf("the asset %s does not exist", id)
|
||||
}
|
||||
|
||||
var asset Asset
|
||||
err = json.Unmarshal(assetJSON, &asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &asset, nil
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
|
||||
exists, err := s.AssetExists(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("the asset %s does not exist", id)
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
asset := Asset{
|
||||
ID: id,
|
||||
Color: color,
|
||||
Size: size,
|
||||
Owner: owner,
|
||||
AppraisedValue: appraisedValue,
|
||||
}
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.GetStub().PutState(id, assetJSON)
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
|
||||
exists, err := s.AssetExists(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("the asset %s does not exist", id)
|
||||
}
|
||||
|
||||
return ctx.GetStub().DelState(id)
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state
|
||||
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
|
||||
assetJSON, err := ctx.GetStub().GetState(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
|
||||
return assetJSON != nil, nil
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in world state, and returns the old owner.
|
||||
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
|
||||
asset, err := s.ReadAsset(ctx, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
oldOwner := asset.Owner
|
||||
asset.Owner = newOwner
|
||||
|
||||
assetJSON, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = ctx.GetStub().PutState(id, assetJSON)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return oldOwner, nil
|
||||
}
|
||||
|
||||
// GetAllAssets returns all assets found in world state
|
||||
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
|
||||
// range query with empty string for startKey and endKey does an
|
||||
// open-ended query of all assets in the chaincode namespace.
|
||||
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resultsIterator.Close()
|
||||
|
||||
var assets []*Asset
|
||||
for resultsIterator.HasNext() {
|
||||
queryResponse, err := resultsIterator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var asset Asset
|
||||
err = json.Unmarshal(queryResponse.Value, &asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assets = append(assets, &asset)
|
||||
}
|
||||
|
||||
return assets, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package chaincode_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/ledger/queryresult"
|
||||
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"
|
||||
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
//go:generate counterfeiter -o mocks/transaction.go -fake-name TransactionContext . transactionContext
|
||||
type transactionContext interface {
|
||||
contractapi.TransactionContextInterface
|
||||
}
|
||||
|
||||
//go:generate counterfeiter -o mocks/chaincodestub.go -fake-name ChaincodeStub . chaincodeStub
|
||||
type chaincodeStub interface {
|
||||
shim.ChaincodeStubInterface
|
||||
}
|
||||
|
||||
//go:generate counterfeiter -o mocks/statequeryiterator.go -fake-name StateQueryIterator . stateQueryIterator
|
||||
type stateQueryIterator interface {
|
||||
shim.StateQueryIteratorInterface
|
||||
}
|
||||
|
||||
func TestInitLedger(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
assetTransfer := chaincode.SmartContract{}
|
||||
err := assetTransfer.InitLedger(transactionContext)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.PutStateReturns(fmt.Errorf("failed inserting key"))
|
||||
err = assetTransfer.InitLedger(transactionContext)
|
||||
require.EqualError(t, err, "failed to put to world state. failed inserting key")
|
||||
}
|
||||
|
||||
func TestCreateAsset(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
assetTransfer := chaincode.SmartContract{}
|
||||
err := assetTransfer.CreateAsset(transactionContext, "", "", 0, "", 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns([]byte{}, nil)
|
||||
err = assetTransfer.CreateAsset(transactionContext, "asset1", "", 0, "", 0)
|
||||
require.EqualError(t, err, "the asset asset1 already exists")
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
|
||||
err = assetTransfer.CreateAsset(transactionContext, "asset1", "", 0, "", 0)
|
||||
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
|
||||
}
|
||||
|
||||
func TestReadAsset(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
expectedAsset := &chaincode.Asset{ID: "asset1"}
|
||||
bytes, err := json.Marshal(expectedAsset)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(bytes, nil)
|
||||
assetTransfer := chaincode.SmartContract{}
|
||||
asset, err := assetTransfer.ReadAsset(transactionContext, "")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedAsset, asset)
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
|
||||
_, err = assetTransfer.ReadAsset(transactionContext, "")
|
||||
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, nil)
|
||||
asset, err = assetTransfer.ReadAsset(transactionContext, "asset1")
|
||||
require.EqualError(t, err, "the asset asset1 does not exist")
|
||||
require.Nil(t, asset)
|
||||
}
|
||||
|
||||
func TestUpdateAsset(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
expectedAsset := &chaincode.Asset{ID: "asset1"}
|
||||
bytes, err := json.Marshal(expectedAsset)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(bytes, nil)
|
||||
assetTransfer := chaincode.SmartContract{}
|
||||
err = assetTransfer.UpdateAsset(transactionContext, "", "", 0, "", 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, nil)
|
||||
err = assetTransfer.UpdateAsset(transactionContext, "asset1", "", 0, "", 0)
|
||||
require.EqualError(t, err, "the asset asset1 does not exist")
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
|
||||
err = assetTransfer.UpdateAsset(transactionContext, "asset1", "", 0, "", 0)
|
||||
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
|
||||
}
|
||||
|
||||
func TestDeleteAsset(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
asset := &chaincode.Asset{ID: "asset1"}
|
||||
bytes, err := json.Marshal(asset)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(bytes, nil)
|
||||
chaincodeStub.DelStateReturns(nil)
|
||||
assetTransfer := chaincode.SmartContract{}
|
||||
err = assetTransfer.DeleteAsset(transactionContext, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, nil)
|
||||
err = assetTransfer.DeleteAsset(transactionContext, "asset1")
|
||||
require.EqualError(t, err, "the asset asset1 does not exist")
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
|
||||
err = assetTransfer.DeleteAsset(transactionContext, "")
|
||||
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
|
||||
}
|
||||
|
||||
func TestTransferAsset(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
asset := &chaincode.Asset{ID: "asset1"}
|
||||
bytes, err := json.Marshal(asset)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(bytes, nil)
|
||||
assetTransfer := chaincode.SmartContract{}
|
||||
_, err = assetTransfer.TransferAsset(transactionContext, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
|
||||
_, err = assetTransfer.TransferAsset(transactionContext, "", "")
|
||||
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
|
||||
}
|
||||
|
||||
func TestGetAllAssets(t *testing.T) {
|
||||
asset := &chaincode.Asset{ID: "asset1"}
|
||||
bytes, err := json.Marshal(asset)
|
||||
require.NoError(t, err)
|
||||
|
||||
iterator := &mocks.StateQueryIterator{}
|
||||
iterator.HasNextReturnsOnCall(0, true)
|
||||
iterator.HasNextReturnsOnCall(1, false)
|
||||
iterator.NextReturns(&queryresult.KV{Value: bytes}, nil)
|
||||
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
chaincodeStub.GetStateByRangeReturns(iterator, nil)
|
||||
assetTransfer := &chaincode.SmartContract{}
|
||||
assets, err := assetTransfer.GetAllAssets(transactionContext)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []*chaincode.Asset{asset}, assets)
|
||||
|
||||
iterator.HasNextReturns(true)
|
||||
iterator.NextReturns(nil, fmt.Errorf("failed retrieving next item"))
|
||||
assets, err = assetTransfer.GetAllAssets(transactionContext)
|
||||
require.EqualError(t, err, "failed retrieving next item")
|
||||
require.Nil(t, assets)
|
||||
|
||||
chaincodeStub.GetStateByRangeReturns(nil, fmt.Errorf("failed retrieving all assets"))
|
||||
assets, err = assetTransfer.GetAllAssets(transactionContext)
|
||||
require.EqualError(t, err, "failed retrieving all assets")
|
||||
require.Nil(t, assets)
|
||||
}
|
||||
31
asset-transfer-basic/chaincode-go/go.mod
Normal file
31
asset-transfer-basic/chaincode-go/go.mod
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
module github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4
|
||||
github.com/stretchr/testify v1.10.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
81
asset-transfer-basic/chaincode-go/go.sum
Normal file
81
asset-transfer-basic/chaincode-go/go.sum
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 h1:IhkHfrl5X/fVnmB6pWeCYCdIJRi9bxj+WTnVN8DtW3c=
|
||||
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0/go.mod h1:PHHaFffjw7p7n9bmCfcm7RqDqYdivNEsJdiNIKZo5Lk=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 h1:rmUoBmciB0GL/miqcbJmJbgp5QTWoJUrZo+CNxrNLF4=
|
||||
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0/go.mod h1:FeWeO/jwGjiME7ak3GufqKIcwkejtzrDG4QxbfKydWs=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
6
asset-transfer-basic/chaincode-java/.gitattributes
vendored
Normal file
6
asset-transfer-basic/chaincode-java/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
2
asset-transfer-basic/chaincode-java/.gitignore
vendored
Normal file
2
asset-transfer-basic/chaincode-java/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.idea/
|
||||
.gradle/
|
||||
31
asset-transfer-basic/chaincode-java/Dockerfile
Executable file
31
asset-transfer-basic/chaincode-java/Dockerfile
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
# the first stage
|
||||
FROM gradle:9-jdk25 AS gradle_build
|
||||
|
||||
# copy the build.gradle and src code to the container
|
||||
COPY src/ src/
|
||||
COPY build.gradle ./
|
||||
|
||||
# Build and package our code
|
||||
RUN gradle --no-daemon build shadowJar -x checkstyleMain -x checkstyleTest
|
||||
|
||||
|
||||
# the second stage of our build just needs the compiled files
|
||||
FROM eclipse-temurin:25-jre
|
||||
ARG CC_SERVER_PORT=9999
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION=v0.19.0
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
RUN addgroup --system javauser && useradd -g javauser javauser
|
||||
|
||||
# copy only the artifacts we need from the first stage and discard the rest
|
||||
COPY --chown=javauser:javauser --from=gradle_build /home/gradle/build/libs/chaincode.jar /chaincode.jar
|
||||
COPY --chown=javauser:javauser docker/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
ENV PORT=$CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
|
||||
USER javauser
|
||||
ENTRYPOINT [ "/tini", "--", "/docker-entrypoint.sh" ]
|
||||
10
asset-transfer-basic/chaincode-java/README.md
Normal file
10
asset-transfer-basic/chaincode-java/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
## Basic Asset Transfer
|
||||
|
||||
This sample implements the basic asset transfer scenario, illustrating the use of the Java Contract SDKs to provide a
|
||||
smart contract as a service.
|
||||
|
||||
To run this chaincode contract locally on a development network, see:
|
||||
|
||||
- [Debugging chaincode as a service](../../test-network-k8s/docs/CHAINCODE_AS_A_SERVICE.md) (Kube test network)
|
||||
- [End-to-end with the test-network](../../test-network/CHAINCODE_AS_A_SERVICE_TUTORIAL.md#end-to-end-with-the-the-test-network) (Docker compose)
|
||||
91
asset-transfer-basic/chaincode-java/build.gradle
Normal file
91
asset-transfer-basic/chaincode-java/build.gradle
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'com.gradleup.shadow' version '9.2.2'
|
||||
id 'application'
|
||||
id 'checkstyle'
|
||||
id 'jacoco'
|
||||
}
|
||||
|
||||
group 'org.hyperledger.fabric.samples'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
dependencies {
|
||||
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.5.+'
|
||||
implementation 'org.json:json:+'
|
||||
implementation 'com.owlike:genson:1.6'
|
||||
testImplementation platform('org.junit:junit-bom:5.14.0')
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
testImplementation 'org.assertj:assertj-core:3.27.6'
|
||||
testImplementation 'org.mockito:mockito-core:5.20.0'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.release = 11
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'org.hyperledger.fabric.contract.ContractRouter'
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion '8.21'
|
||||
configFile file("config/checkstyle/checkstyle.xml")
|
||||
}
|
||||
|
||||
checkstyleMain {
|
||||
source ='src/main/java'
|
||||
}
|
||||
|
||||
checkstyleTest {
|
||||
source ='src/test/java'
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
dependsOn test
|
||||
}
|
||||
|
||||
jacocoTestCoverageVerification {
|
||||
violationRules {
|
||||
rule {
|
||||
limit {
|
||||
minimum = 0.9
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finalizedBy jacocoTestReport
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName = 'chaincode'
|
||||
archiveVersion = ''
|
||||
archiveClassifier = ''
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
mergeServiceFiles()
|
||||
|
||||
manifest {
|
||||
attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter'
|
||||
}
|
||||
}
|
||||
|
||||
check.dependsOn jacocoTestCoverageVerification
|
||||
installDist.dependsOn check
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!--
|
||||
|
||||
Checkstyle configuration that matches the Eclipse formatter
|
||||
|
||||
Checkstyle is very configurable. Be sure to read the documentation at
|
||||
http://checkstyle.sourceforge.net (or in your downloaded distribution).
|
||||
|
||||
Most Checks are configurable, be sure to consult the documentation.
|
||||
|
||||
To completely disable a check, just comment it out or delete it from the file.
|
||||
|
||||
Finally, it is worth reading the documentation.
|
||||
|
||||
-->
|
||||
|
||||
<module name="Checker">
|
||||
<!--
|
||||
If you set the basedir property below, then all reported file
|
||||
names will be relative to the specified directory. See
|
||||
https://checkstyle.org/5.x/config.html#Checker
|
||||
|
||||
<property name="basedir" value="${basedir}"/>
|
||||
-->
|
||||
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${config_loc}/suppressions.xml"/>
|
||||
<property name="optional" value="false"/>
|
||||
</module>
|
||||
|
||||
<!-- Excludes all 'module-info.java' files -->
|
||||
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks that a package-info.java file exists for each package. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html#JavadocPackage -->
|
||||
<!-- <module name="JavadocPackage"/> -->
|
||||
|
||||
<!-- Checks whether files end with a new line. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
|
||||
<!-- Checks that property files contain the same keys. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html#Translation -->
|
||||
<module name="Translation"/>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
|
||||
<module name="FileLength"/>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\s+$"/>
|
||||
<property name="minimum" value="0"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for Headers -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_header.html -->
|
||||
<!-- <module name="Header"> -->
|
||||
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
|
||||
<!-- <property name="fileExtensions" value="java"/> -->
|
||||
<!-- </module> -->
|
||||
|
||||
<module name="TreeWalker">
|
||||
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html -->
|
||||
<!-- <module name="JavadocMethod"/> -->
|
||||
<!-- <module name="JavadocType"/> -->
|
||||
<!-- <module name="JavadocVariable"/> -->
|
||||
<!-- <module name="JavadocStyle"/> -->
|
||||
<!-- <module name="MissingJavadocMethod"/> -->
|
||||
|
||||
<!-- Checks for Naming Conventions. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_naming.html -->
|
||||
<module name="ConstantName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="PackageName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="TypeName"/>
|
||||
|
||||
<!-- Checks for imports -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_import.html -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports">
|
||||
<property name="processJavadoc" value="false"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
|
||||
<module name="MethodLength"/>
|
||||
<module name="ParameterNumber"/>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
|
||||
<module name="EmptyForIteratorPad"/>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceAfter"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
|
||||
<!-- Modifier Checks -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<!-- Checks for blocks. You know, those {}'s -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_blocks.html -->
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
<module name="EmptyBlock"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="NeedBraces"/>
|
||||
<module name="RightCurly"/>
|
||||
|
||||
<!-- Checks for common coding problems -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_coding.html -->
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="HiddenField">
|
||||
<property name="ignoreConstructorParameter" value="true"/>
|
||||
</module>
|
||||
<module name="IllegalInstantiation"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<module name="MissingSwitchDefault"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
|
||||
<!-- Checks for class design -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
|
||||
<module name="DesignForExtension"/>
|
||||
<module name="FinalClass"/>
|
||||
<module name="HideUtilityClassConstructor"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
<module name="VisibilityModifier">
|
||||
<property name="allowPublicFinalFields" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="FinalParameters"/>
|
||||
<module name="TodoComment"/>
|
||||
<module name="UpperEll"/>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
|
||||
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||
|
||||
<suppressions>
|
||||
<suppress files="ChaincodeTest.java" checks="ParameterNumber" />
|
||||
</suppressions>
|
||||
16
asset-transfer-basic/chaincode-java/docker/docker-entrypoint.sh
Executable file
16
asset-transfer-basic/chaincode-java/docker/docker-entrypoint.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
set -euo pipefail
|
||||
: ${CORE_PEER_TLS_ENABLED:="false"}
|
||||
: ${DEBUG:="false"}
|
||||
|
||||
if [ "${DEBUG,,}" = "true" ]; then
|
||||
exec java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
exec java -jar /chaincode.jar # todo
|
||||
else
|
||||
exec java -jar /chaincode.jar
|
||||
fi
|
||||
|
||||
BIN
asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
asset-transfer-basic/chaincode-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
248
asset-transfer-basic/chaincode-java/gradlew
vendored
Executable file
248
asset-transfer-basic/chaincode-java/gradlew
vendored
Executable file
|
|
@ -0,0 +1,248 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
93
asset-transfer-basic/chaincode-java/gradlew.bat
vendored
Normal file
93
asset-transfer-basic/chaincode-java/gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
4
asset-transfer-basic/chaincode-java/settings.gradle
Normal file
4
asset-transfer-basic/chaincode-java/settings.gradle
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
rootProject.name = System.getenv('CHAINCODE_NAME') ?: 'basic'
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.fabric.samples.assettransfer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.hyperledger.fabric.contract.annotation.DataType;
|
||||
import org.hyperledger.fabric.contract.annotation.Property;
|
||||
|
||||
import com.owlike.genson.annotation.JsonProperty;
|
||||
|
||||
@DataType()
|
||||
public final class Asset {
|
||||
|
||||
@Property()
|
||||
private final String assetID;
|
||||
|
||||
@Property()
|
||||
private final String color;
|
||||
|
||||
@Property()
|
||||
private final int size;
|
||||
|
||||
@Property()
|
||||
private final String owner;
|
||||
|
||||
@Property()
|
||||
private final int appraisedValue;
|
||||
|
||||
public String getAssetID() {
|
||||
return assetID;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public int getAppraisedValue() {
|
||||
return appraisedValue;
|
||||
}
|
||||
|
||||
public Asset(@JsonProperty("assetID") final String assetID, @JsonProperty("color") final String color,
|
||||
@JsonProperty("size") final int size, @JsonProperty("owner") final String owner,
|
||||
@JsonProperty("appraisedValue") final int appraisedValue) {
|
||||
this.assetID = assetID;
|
||||
this.color = color;
|
||||
this.size = size;
|
||||
this.owner = owner;
|
||||
this.appraisedValue = appraisedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((obj == null) || (getClass() != obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Asset other = (Asset) obj;
|
||||
|
||||
return Objects.deepEquals(
|
||||
new String[] {getAssetID(), getColor(), getOwner()},
|
||||
new String[] {other.getAssetID(), other.getColor(), other.getOwner()})
|
||||
&&
|
||||
Objects.deepEquals(
|
||||
new int[] {getSize(), getAppraisedValue()},
|
||||
new int[] {other.getSize(), other.getAppraisedValue()});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getAssetID(), getColor(), getSize(), getOwner(), getAppraisedValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + " [assetID=" + assetID + ", color="
|
||||
+ color + ", size=" + size + ", owner=" + owner + ", appraisedValue=" + appraisedValue + "]";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.fabric.samples.assettransfer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import org.hyperledger.fabric.contract.Context;
|
||||
import org.hyperledger.fabric.contract.ContractInterface;
|
||||
import org.hyperledger.fabric.contract.annotation.Contact;
|
||||
import org.hyperledger.fabric.contract.annotation.Contract;
|
||||
import org.hyperledger.fabric.contract.annotation.Default;
|
||||
import org.hyperledger.fabric.contract.annotation.Info;
|
||||
import org.hyperledger.fabric.contract.annotation.License;
|
||||
import org.hyperledger.fabric.contract.annotation.Transaction;
|
||||
import org.hyperledger.fabric.shim.ChaincodeException;
|
||||
import org.hyperledger.fabric.shim.ChaincodeStub;
|
||||
import org.hyperledger.fabric.shim.ledger.KeyValue;
|
||||
import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
|
||||
|
||||
import com.owlike.genson.Genson;
|
||||
|
||||
@Contract(
|
||||
name = "basic",
|
||||
info = @Info(
|
||||
title = "Asset Transfer",
|
||||
description = "The hyperlegendary asset transfer",
|
||||
version = "0.0.1-SNAPSHOT",
|
||||
license = @License(
|
||||
name = "Apache 2.0 License",
|
||||
url = "http://www.apache.org/licenses/LICENSE-2.0.html"),
|
||||
contact = @Contact(
|
||||
email = "a.transfer@example.com",
|
||||
name = "Adrian Transfer",
|
||||
url = "https://hyperledger.example.com")))
|
||||
@Default
|
||||
public final class AssetTransfer implements ContractInterface {
|
||||
|
||||
private final Genson genson = new Genson();
|
||||
|
||||
private enum AssetTransferErrors {
|
||||
ASSET_NOT_FOUND,
|
||||
ASSET_ALREADY_EXISTS
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates some initial assets on the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public void InitLedger(final Context ctx) {
|
||||
putAsset(ctx, new Asset("asset1", "blue", 5, "Tomoko", 300));
|
||||
putAsset(ctx, new Asset("asset2", "red", 5, "Brad", 400));
|
||||
putAsset(ctx, new Asset("asset3", "green", 10, "Jin Soo", 500));
|
||||
putAsset(ctx, new Asset("asset4", "yellow", 10, "Max", 600));
|
||||
putAsset(ctx, new Asset("asset5", "black", 15, "Adrian", 700));
|
||||
putAsset(ctx, new Asset("asset6", "white", 15, "Michel", 700));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new asset on the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID the ID of the new asset
|
||||
* @param color the color of the new asset
|
||||
* @param size the size for the new asset
|
||||
* @param owner the owner of the new asset
|
||||
* @param appraisedValue the appraisedValue of the new asset
|
||||
* @return the created asset
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public Asset CreateAsset(final Context ctx, final String assetID, final String color, final int size,
|
||||
final String owner, final int appraisedValue) {
|
||||
|
||||
if (AssetExists(ctx, assetID)) {
|
||||
String errorMessage = String.format("Asset %s already exists", assetID);
|
||||
System.out.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_ALREADY_EXISTS.toString());
|
||||
}
|
||||
|
||||
return putAsset(ctx, new Asset(assetID, color, size, owner, appraisedValue));
|
||||
}
|
||||
|
||||
private Asset putAsset(final Context ctx, final Asset asset) {
|
||||
// Use Genson to convert the Asset into string, sort it alphabetically and serialize it into a json string
|
||||
String sortedJson = genson.serialize(asset);
|
||||
ctx.getStub().putStringState(asset.getAssetID(), sortedJson);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an asset with the specified ID from the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID the ID of the asset
|
||||
* @return the asset found on the ledger if there was one
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.EVALUATE)
|
||||
public Asset ReadAsset(final Context ctx, final String assetID) {
|
||||
String assetJSON = ctx.getStub().getStringState(assetID);
|
||||
|
||||
if (assetJSON == null || assetJSON.isEmpty()) {
|
||||
String errorMessage = String.format("Asset %s does not exist", assetID);
|
||||
System.out.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
|
||||
}
|
||||
|
||||
return genson.deserialize(assetJSON, Asset.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the properties of an asset on the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID the ID of the asset being updated
|
||||
* @param color the color of the asset being updated
|
||||
* @param size the size of the asset being updated
|
||||
* @param owner the owner of the asset being updated
|
||||
* @param appraisedValue the appraisedValue of the asset being updated
|
||||
* @return the transferred asset
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public Asset UpdateAsset(final Context ctx, final String assetID, final String color, final int size,
|
||||
final String owner, final int appraisedValue) {
|
||||
|
||||
if (!AssetExists(ctx, assetID)) {
|
||||
String errorMessage = String.format("Asset %s does not exist", assetID);
|
||||
System.out.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
|
||||
}
|
||||
|
||||
return putAsset(ctx, new Asset(assetID, color, size, owner, appraisedValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes asset on the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID the ID of the asset being deleted
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public void DeleteAsset(final Context ctx, final String assetID) {
|
||||
if (!AssetExists(ctx, assetID)) {
|
||||
String errorMessage = String.format("Asset %s does not exist", assetID);
|
||||
System.out.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
|
||||
}
|
||||
|
||||
ctx.getStub().delState(assetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the existence of the asset on the ledger
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID the ID of the asset
|
||||
* @return boolean indicating the existence of the asset
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.EVALUATE)
|
||||
public boolean AssetExists(final Context ctx, final String assetID) {
|
||||
String assetJSON = ctx.getStub().getStringState(assetID);
|
||||
|
||||
return (assetJSON != null && !assetJSON.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the owner of a asset on the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID the ID of the asset being transferred
|
||||
* @param newOwner the new owner
|
||||
* @return the old owner
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public String TransferAsset(final Context ctx, final String assetID, final String newOwner) {
|
||||
String assetJSON = ctx.getStub().getStringState(assetID);
|
||||
|
||||
if (assetJSON == null || assetJSON.isEmpty()) {
|
||||
String errorMessage = String.format("Asset %s does not exist", assetID);
|
||||
System.out.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
|
||||
}
|
||||
|
||||
Asset asset = genson.deserialize(assetJSON, Asset.class);
|
||||
|
||||
putAsset(ctx, new Asset(asset.getAssetID(), asset.getColor(), asset.getSize(), newOwner, asset.getAppraisedValue()));
|
||||
|
||||
return asset.getOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all assets from the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @return array of assets found on the ledger
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.EVALUATE)
|
||||
public String GetAllAssets(final Context ctx) {
|
||||
ChaincodeStub stub = ctx.getStub();
|
||||
|
||||
List<Asset> queryResults = new ArrayList<>();
|
||||
|
||||
// To retrieve all assets from the ledger use getStateByRange with empty startKey & endKey.
|
||||
// Giving empty startKey & endKey is interpreted as all the keys from beginning to end.
|
||||
// As another example, if you use startKey = 'asset0', endKey = 'asset9' ,
|
||||
// then getStateByRange will retrieve asset with keys between asset0 (inclusive) and asset9 (exclusive) in lexical order.
|
||||
QueryResultsIterator<KeyValue> results = stub.getStateByRange("", "");
|
||||
|
||||
for (KeyValue result: results) {
|
||||
Asset asset = genson.deserialize(result.getStringValue(), Asset.class);
|
||||
System.out.println(asset);
|
||||
queryResults.add(asset);
|
||||
}
|
||||
|
||||
return genson.serialize(queryResults);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.fabric.samples.assettransfer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public final class AssetTest {
|
||||
|
||||
@Nested
|
||||
class Equality {
|
||||
|
||||
@Test
|
||||
public void isReflexive() {
|
||||
Asset asset = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
|
||||
assertThat(asset).isEqualTo(asset);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSymmetric() {
|
||||
Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
Asset assetB = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
|
||||
assertThat(assetA).isEqualTo(assetB);
|
||||
assertThat(assetB).isEqualTo(assetA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTransitive() {
|
||||
Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
Asset assetB = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
Asset assetC = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
|
||||
assertThat(assetA).isEqualTo(assetB);
|
||||
assertThat(assetB).isEqualTo(assetC);
|
||||
assertThat(assetA).isEqualTo(assetC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesInequality() {
|
||||
Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
Asset assetB = new Asset("asset2", "Red", 40, "Lady", 200);
|
||||
|
||||
assertThat(assetA).isNotEqualTo(assetB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesOtherObjects() {
|
||||
Asset assetA = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
String assetB = "not a asset";
|
||||
|
||||
assertThat(assetA).isNotEqualTo(assetB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesNull() {
|
||||
Asset asset = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
|
||||
assertThat(asset).isNotEqualTo(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringIdentifiesAsset() {
|
||||
Asset asset = new Asset("asset1", "Blue", 20, "Guy", 100);
|
||||
|
||||
assertThat(asset.toString()).isEqualTo("Asset@e04f6c53 [assetID=asset1, color=Blue, size=20, owner=Guy, appraisedValue=100]");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.fabric.samples.assettransfer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.hyperledger.fabric.contract.Context;
|
||||
import org.hyperledger.fabric.shim.ChaincodeException;
|
||||
import org.hyperledger.fabric.shim.ChaincodeStub;
|
||||
import org.hyperledger.fabric.shim.ledger.KeyValue;
|
||||
import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
public final class AssetTransferTest {
|
||||
|
||||
private static final class MockKeyValue implements KeyValue {
|
||||
|
||||
private final String key;
|
||||
private final String value;
|
||||
|
||||
MockKeyValue(final String key, final String value) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStringValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getValue() {
|
||||
return this.value.getBytes();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class MockAssetResultsIterator implements QueryResultsIterator<KeyValue> {
|
||||
|
||||
private final List<KeyValue> assetList;
|
||||
|
||||
MockAssetResultsIterator() {
|
||||
super();
|
||||
|
||||
assetList = new ArrayList<KeyValue>();
|
||||
|
||||
assetList.add(new MockKeyValue("asset1",
|
||||
"{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }"));
|
||||
assetList.add(new MockKeyValue("asset2",
|
||||
"{ \"assetID\": \"asset2\", \"color\": \"red\", \"size\": 5,\"owner\": \"Brad\", \"appraisedValue\": 400 }"));
|
||||
assetList.add(new MockKeyValue("asset3",
|
||||
"{ \"assetID\": \"asset3\", \"color\": \"green\", \"size\": 10,\"owner\": \"Jin Soo\", \"appraisedValue\": 500 }"));
|
||||
assetList.add(new MockKeyValue("asset4",
|
||||
"{ \"assetID\": \"asset4\", \"color\": \"yellow\", \"size\": 10,\"owner\": \"Max\", \"appraisedValue\": 600 }"));
|
||||
assetList.add(new MockKeyValue("asset5",
|
||||
"{ \"assetID\": \"asset5\", \"color\": \"black\", \"size\": 15,\"owner\": \"Adrian\", \"appraisedValue\": 700 }"));
|
||||
assetList.add(new MockKeyValue("asset6",
|
||||
"{ \"assetID\": \"asset6\", \"color\": \"white\", \"size\": 15,\"owner\": \"Michel\", \"appraisedValue\": 800 }"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<KeyValue> iterator() {
|
||||
return assetList.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeUnknownTransaction() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
|
||||
Throwable thrown = catchThrowable(() -> {
|
||||
contract.unknownTransaction(ctx);
|
||||
});
|
||||
|
||||
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
|
||||
.hasMessage("Undefined contract method called");
|
||||
assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo(null);
|
||||
|
||||
verifyNoInteractions(ctx);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InvokeReadAssetTransaction {
|
||||
|
||||
@Test
|
||||
public void whenAssetExists() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1"))
|
||||
.thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }");
|
||||
|
||||
Asset asset = contract.ReadAsset(ctx, "asset1");
|
||||
|
||||
assertThat(asset).isEqualTo(new Asset("asset1", "blue", 5, "Tomoko", 300));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenAssetDoesNotExist() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1")).thenReturn("");
|
||||
|
||||
Throwable thrown = catchThrowable(() -> {
|
||||
contract.ReadAsset(ctx, "asset1");
|
||||
});
|
||||
|
||||
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
|
||||
.hasMessage("Asset asset1 does not exist");
|
||||
assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invokeInitLedgerTransaction() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
|
||||
contract.InitLedger(ctx);
|
||||
|
||||
InOrder inOrder = inOrder(stub);
|
||||
inOrder.verify(stub).putStringState("asset1", "{\"appraisedValue\":300,\"assetID\":\"asset1\",\"color\":\"blue\",\"owner\":\"Tomoko\",\"size\":5}");
|
||||
inOrder.verify(stub).putStringState("asset2", "{\"appraisedValue\":400,\"assetID\":\"asset2\",\"color\":\"red\",\"owner\":\"Brad\",\"size\":5}");
|
||||
inOrder.verify(stub).putStringState("asset3", "{\"appraisedValue\":500,\"assetID\":\"asset3\",\"color\":\"green\",\"owner\":\"Jin Soo\",\"size\":10}");
|
||||
inOrder.verify(stub).putStringState("asset4", "{\"appraisedValue\":600,\"assetID\":\"asset4\",\"color\":\"yellow\",\"owner\":\"Max\",\"size\":10}");
|
||||
inOrder.verify(stub).putStringState("asset5", "{\"appraisedValue\":700,\"assetID\":\"asset5\",\"color\":\"black\",\"owner\":\"Adrian\",\"size\":15}");
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InvokeCreateAssetTransaction {
|
||||
|
||||
@Test
|
||||
public void whenAssetExists() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1"))
|
||||
.thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }");
|
||||
|
||||
Throwable thrown = catchThrowable(() -> {
|
||||
contract.CreateAsset(ctx, "asset1", "blue", 45, "Siobhán", 60);
|
||||
});
|
||||
|
||||
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
|
||||
.hasMessage("Asset asset1 already exists");
|
||||
assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_ALREADY_EXISTS".getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenAssetDoesNotExist() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1")).thenReturn("");
|
||||
|
||||
Asset asset = contract.CreateAsset(ctx, "asset1", "blue", 45, "Siobhán", 60);
|
||||
|
||||
assertThat(asset).isEqualTo(new Asset("asset1", "blue", 45, "Siobhán", 60));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invokeGetAllAssetsTransaction() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStateByRange("", "")).thenReturn(new MockAssetResultsIterator());
|
||||
|
||||
String assets = contract.GetAllAssets(ctx);
|
||||
|
||||
assertThat(assets).isEqualTo("[{\"appraisedValue\":300,\"assetID\":\"asset1\",\"color\":\"blue\",\"owner\":\"Tomoko\",\"size\":5},"
|
||||
+ "{\"appraisedValue\":400,\"assetID\":\"asset2\",\"color\":\"red\",\"owner\":\"Brad\",\"size\":5},"
|
||||
+ "{\"appraisedValue\":500,\"assetID\":\"asset3\",\"color\":\"green\",\"owner\":\"Jin Soo\",\"size\":10},"
|
||||
+ "{\"appraisedValue\":600,\"assetID\":\"asset4\",\"color\":\"yellow\",\"owner\":\"Max\",\"size\":10},"
|
||||
+ "{\"appraisedValue\":700,\"assetID\":\"asset5\",\"color\":\"black\",\"owner\":\"Adrian\",\"size\":15},"
|
||||
+ "{\"appraisedValue\":800,\"assetID\":\"asset6\",\"color\":\"white\",\"owner\":\"Michel\",\"size\":15}]");
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class TransferAssetTransaction {
|
||||
|
||||
@Test
|
||||
public void whenAssetExists() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1"))
|
||||
.thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 5, \"owner\": \"Tomoko\", \"appraisedValue\": 300 }");
|
||||
|
||||
String oldOwner = contract.TransferAsset(ctx, "asset1", "Dr Evil");
|
||||
|
||||
assertThat(oldOwner).isEqualTo("Tomoko");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenAssetDoesNotExist() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1")).thenReturn("");
|
||||
|
||||
Throwable thrown = catchThrowable(() -> {
|
||||
contract.TransferAsset(ctx, "asset1", "Dr Evil");
|
||||
});
|
||||
|
||||
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
|
||||
.hasMessage("Asset asset1 does not exist");
|
||||
assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class UpdateAssetTransaction {
|
||||
|
||||
@Test
|
||||
public void whenAssetExists() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1"))
|
||||
.thenReturn("{ \"assetID\": \"asset1\", \"color\": \"blue\", \"size\": 45, \"owner\": \"Arturo\", \"appraisedValue\": 60 }");
|
||||
|
||||
Asset asset = contract.UpdateAsset(ctx, "asset1", "pink", 45, "Arturo", 600);
|
||||
|
||||
assertThat(asset).isEqualTo(new Asset("asset1", "pink", 45, "Arturo", 600));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenAssetDoesNotExist() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1")).thenReturn("");
|
||||
|
||||
Throwable thrown = catchThrowable(() -> {
|
||||
contract.TransferAsset(ctx, "asset1", "Alex");
|
||||
});
|
||||
|
||||
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
|
||||
.hasMessage("Asset asset1 does not exist");
|
||||
assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class DeleteAssetTransaction {
|
||||
|
||||
@Test
|
||||
public void whenAssetDoesNotExist() {
|
||||
AssetTransfer contract = new AssetTransfer();
|
||||
Context ctx = mock(Context.class);
|
||||
ChaincodeStub stub = mock(ChaincodeStub.class);
|
||||
when(ctx.getStub()).thenReturn(stub);
|
||||
when(stub.getStringState("asset1")).thenReturn("");
|
||||
|
||||
Throwable thrown = catchThrowable(() -> {
|
||||
contract.DeleteAsset(ctx, "asset1");
|
||||
});
|
||||
|
||||
assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
|
||||
.hasMessage("Asset asset1 does not exist");
|
||||
assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("ASSET_NOT_FOUND".getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue