├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── publish-docker-on-main.yml │ ├── release-on-tag.yml │ └── test-on-push.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── .shellcheckrc ├── .yamllint.yml ├── ARCHITECTURE.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── bump-version.sh ├── check-if-fablo-version-matches.sh ├── docker-entrypoint.sh ├── docs ├── CNAME ├── editor.html ├── index.html ├── lib │ └── jsoneditor.min.js ├── sample.json └── schema.json ├── e2e-network ├── TEST_CASES.md ├── docker │ ├── expect-ca-rest.sh │ ├── expect-command.sh │ ├── expect-invoke-cli.sh │ ├── expect-invoke-rest.sh │ ├── test-01-simple.sh │ ├── test-02-raft-2orgs.sh │ ├── test-03-private-data.sh │ ├── test-04-snapshot.sh │ ├── test-05-version3-BFT.sh │ ├── test-05-version3.sh │ ├── wait-for-chaincode.sh │ └── wait-for-container.sh └── k8s │ ├── expect-invoke-cli.sh │ ├── test-01-simple-k8s.sh │ ├── wait-for-chaincode.sh │ └── wait-for-container.sh ├── e2e ├── TestCommands.ts ├── __snapshots__ │ ├── extendConfig.test.ts.snap │ ├── fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap │ ├── fablo-config-hlf2-1org-1chaincode-raft-explorer.json.test.ts.snap │ ├── fablo-config-hlf2-1org-1chaincode.json.test.ts.snap │ ├── fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml.test.ts.snap │ ├── fablo-config-hlf2-2orgs-2chaincodes-raft.yaml.test.ts.snap │ ├── fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json.test.ts.snap │ ├── fablo-config-hlf3-1orgs-1chaincode.json.test.ts.snap │ ├── fablo-config-hlf3-bft-1orgs-1chaincode.json.test.ts.snap │ ├── fabloCommands.test.ts.snap │ └── schema.test.ts.snap ├── create-test-cases.sh ├── extendConfig.test.ts ├── fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts ├── fablo-config-hlf2-1org-1chaincode-raft-explorer.json.test.ts ├── fablo-config-hlf2-1org-1chaincode.json.test.ts ├── fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml.test.ts ├── fablo-config-hlf2-2orgs-2chaincodes-raft.yaml.test.ts ├── fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json.test.ts ├── fablo-config-hlf3-1orgs-1chaincode.json.test.ts ├── fablo-config-hlf3-bft-1orgs-1chaincode.json.test.ts ├── fabloCommands.test.ts ├── performTests.ts ├── schema.test.ts └── schemaFilesMatch.test.ts ├── fablo-build.sh ├── fablo.sh ├── jest.config.js ├── lint.sh ├── logo-sygnet-192.png ├── logo.svg ├── package-lock.json ├── package.json ├── samples ├── chaincodes │ ├── .gitignore │ ├── chaincode-java-simple │ │ ├── .fabricignore │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ ├── Pokeball.java │ │ │ │ └── PokeballContract.java │ │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── PokeballContractTest.java │ └── chaincode-kv-node │ │ ├── .nvmrc │ │ ├── Dockerfile │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json ├── fablo-config-hlf2-1org-1chaincode-k8s.json ├── fablo-config-hlf2-1org-1chaincode-raft-explorer.json ├── fablo-config-hlf2-1org-1chaincode.json ├── fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml ├── fablo-config-hlf2-2orgs-2chaincodes-raft.yaml ├── fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json ├── fablo-config-hlf3-1orgs-1chaincode.json ├── fablo-config-hlf3-bft-1orgs-1chaincode.json └── gateway │ └── node │ ├── .env.example │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── server.js ├── src ├── app │ └── index.ts ├── config.test.ts ├── config.ts ├── extend-config │ ├── defaults.ts │ ├── extendChaincodesConfig.ts │ ├── extendChannelsConfig.ts │ ├── extendConfig.ts │ ├── extendGlobal.ts │ ├── extendHooksConfig.ts │ ├── extendOrgsConfig.ts │ ├── index.ts │ └── mergeOrdererGroups.ts ├── init │ └── index.ts ├── list-compatible-updates │ └── index.ts ├── list-versions │ └── index.ts ├── repositoryUtils.test.ts ├── repositoryUtils.ts ├── setup-docker │ ├── index.ts │ └── templates │ │ ├── fabric-config │ │ ├── .gitignore │ │ ├── configtx-raft-template.yaml.ejs │ │ ├── configtx.yaml │ │ ├── crypto-config-org.yaml │ │ ├── explorer │ │ │ └── config.json │ │ └── fabric-ca-server-config.yaml │ │ ├── fabric-docker.sh │ │ ├── fabric-docker │ │ ├── .env │ │ ├── chaincode-scripts.sh │ │ ├── channel-query-scripts.sh │ │ ├── commands-generated.sh │ │ ├── commands-generated │ │ │ ├── chaincode-dev-v2.sh │ │ │ └── chaincode-install-v2.sh │ │ ├── docker-compose.yaml │ │ ├── scripts │ │ │ ├── base-functions-v2.sh │ │ │ ├── base-functions-v3.sh │ │ │ ├── base-help.sh │ │ │ ├── chaincode-functions-v2.sh │ │ │ ├── channel-query-functions.sh │ │ │ └── cli │ │ │ │ ├── channel_fns-v2.sh │ │ │ │ └── channel_fns-v3.sh │ │ └── snapshot-scripts.sh │ │ └── hooks │ │ └── post-generate.sh ├── setup-k8s │ ├── index.ts │ └── templates │ │ ├── fabric-config │ │ └── .gitignore │ │ ├── fabric-k8s.sh │ │ ├── fabric-k8s │ │ ├── .env │ │ └── scripts │ │ │ ├── base-functions.sh │ │ │ ├── base-help.sh │ │ │ ├── chaincode-functions-v2.sh │ │ │ ├── chaincode-functions.sh │ │ │ └── util.sh │ │ └── hooks │ │ └── post-generate.sh ├── setup-network │ └── index.ts ├── types │ ├── ConnectionProfile.ts │ ├── ExplorerConfig.ts │ ├── FabloConfigExtended.ts │ └── FabloConfigJson.ts ├── utils │ ├── parseFabloConfig.test.ts │ └── parseFabloConfig.ts ├── validate │ └── index.ts └── version │ ├── buildUtil.ts │ └── index.ts ├── tsconfig-dist.json └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea 2 | e2e 3 | fablo-target 4 | node_modules 5 | Jenkinsfile 6 | samples/chaincodes/chaincode-java-simple 7 | samples/chaincodes/chaincode-kv-node/node_modules 8 | samples/chaincodes/chaincode-kv-node/**/*.spec.js 9 | samples/chaincodes/chaincode-kv-node-1.4/node_modules 10 | samples/chaincodes/chaincode-kv-node-1.4/**/*.spec.js 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | generators/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin 4 | "prettier", 5 | ], 6 | parser: "@typescript-eslint/parser", 7 | plugins: ["@typescript-eslint/eslint-plugin", "prettier"], 8 | settings: { 9 | "import/parsers": { 10 | "@typescript-eslint/parser": [".ts", ".tsx"], 11 | }, 12 | "import/resolver": { 13 | typescript: {}, 14 | }, 15 | }, 16 | rules: { 17 | "prettier/prettier": "error", 18 | "@typescript-eslint/no-use-before-define": "error", 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/publish-docker-on-main.yml: -------------------------------------------------------------------------------- 1 | name: Publish on merge to main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish-docker: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v3 18 | with: 19 | driver: docker-container 20 | 21 | - name: Login to GHCR 22 | uses: docker/login-action@v3 23 | with: 24 | registry: ghcr.io 25 | username: ${{ secrets.GHCR_USER }} 26 | password: ${{ secrets.GHCR_TOKEN }} 27 | 28 | - name: Build and push Docker image 29 | run: | 30 | shellcheck --version 31 | yamllint -v 32 | npm install 33 | ./fablo-build.sh --push 34 | 35 | - name: Build and push node chaincode 36 | run: | 37 | docker buildx build \ 38 | --platform linux/amd64,linux/arm64 \ 39 | --tag "ghcr.io/fablo-io/fablo-kv-node-chaincode-sample:$FABLO_VERSION" \ 40 | --push \ 41 | samples/chaincodes/chaincode-kv-node 42 | -------------------------------------------------------------------------------- /.github/workflows/release-on-tag.yml: -------------------------------------------------------------------------------- 1 | name: Release on tag 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Verify version 17 | run: | 18 | TAG=$(git describe --tags --abbrev=0) 19 | VERSION=${TAG:1} 20 | echo "TAG: $TAG" 21 | echo "VERSION: $VERSION" 22 | PKG_JSON_VERSION=$(jq -r '.version' <"$GITHUB_WORKSPACE/package.json") 23 | if [ "$VERSION" != "$PKG_JSON_VERSION" ]; then 24 | echo "Version in package.json ($PKG_JSON_VERSION) does not match tag ($VERSION)" 25 | exit 1 26 | fi 27 | 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v3 30 | with: 31 | driver: docker-container 32 | 33 | - name: Login to GHCR 34 | uses: docker/login-action@v3 35 | with: 36 | registry: ghcr.io 37 | username: ${{ secrets.GHCR_USER }} 38 | password: ${{ secrets.GHCR_TOKEN }} 39 | 40 | - name: Build and push Docker image 41 | run: | 42 | shellcheck --version 43 | yamllint -v 44 | npm install 45 | ./fablo-build.sh --push 46 | 47 | - name: Create release 48 | uses: softprops/action-gh-release@v2 49 | with: 50 | files: | 51 | docs/schema.json 52 | fablo.sh 53 | 54 | - name: Create next development version PR 55 | run: | 56 | sh ./bump_version.sh unstable 57 | NEW_VERSION=$(jq -r '.version' <"$GITHUB_WORKSPACE/package.json") 58 | BRANCH_NAME="bump-version-to-$NEW_VERSION" 59 | git checkout -b $BRANCH_NAME 60 | git commit -a -m "Set new development version: $NEW_VERSION" 61 | git push --set-upstream origin bump-version-to-$NEW_VERSION 62 | git gh pr create --title "Bump Version to $NEW_VERSION" --body "Bump Version to $NEW_VERSION" --label "bump-version-pr" --head "bump-version-to-$NEW_VERSION" --base main 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | e2e/__tmp__ 2 | e2e-network/docker/*.logs 3 | e2e-network/docker/*.tmpdir 4 | fablo-target 5 | generators 6 | node_modules 7 | .idea 8 | .vscode 9 | samples/invalid-fablo-config.json 10 | .DS_Store -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | generators/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: "all", 4 | singleQuote: false, 5 | printWidth: 120, 6 | tabWidth: 2 7 | }; 8 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | # SC1090: Can't follow non-constant source. Use a directive to specify location. 2 | disable=SC1090 3 | 4 | # SC1091: Not following 5 | disable=SC1091 6 | 7 | # SC2034: Unused 8 | disable=SC2034 9 | 10 | # SC2164: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. 11 | disable=SC2164 12 | 13 | # SC2155: Declare and assign separately to avoid masking return values. 14 | disable=SC2155 15 | 16 | # SC2046: Quote this to prevent word splitting. 17 | disable=SC2046 18 | 19 | # SC2062: Quote the grep pattern so the shell won't interpret it. 20 | disable=SC2062 21 | 22 | # SC2001: See if you can use ${variable//search/replace} instead. 23 | disable=SC2001 24 | 25 | # SC2035: Use ./*glob* or -- *glob* so names with dashes won't become options. 26 | disable=SC2035 27 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | rules: 4 | line-length: disable 5 | 6 | document-start: disable 7 | 8 | comments: 9 | require-starting-space: true 10 | ignore-shebangs: true 11 | min-spaces-from-content: 1 12 | 13 | # why added? 14 | # config fablo-config-hlf2-2orgs-2chaincodes-raft.yaml produces duplicated keys in configtx.yaml due to Orderer sharding. 15 | key-duplicates: 16 | level: warning 17 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Fablo architecture 2 | 3 | Fablo is a tool that allows to setup a running Hyperledger Fabric network on the basis of a config file. 4 | 5 | The main flow of the application is presented in the diagram below (for instance for the `up` command): 6 | 7 | ```mermaid 8 | sequenceDiagram 9 | actor User 10 | User ->> fablo.sh: Call `up` command 11 | fablo.sh ->> fablo-target: Verify if network files
are generated 12 | alt no network files 13 | fablo.sh ->> Fablo Docker: Generate network files 14 | Fablo Docker ->> fablo-target: Generate network files
from `fablo-config.json`
and templates (Yeoman) 15 | end 16 | fablo.sh ->> fablo-target: Call `up` command 17 | 18 | ``` 19 | 20 | There are three important layers in this flow: 21 | 22 | 1. `fablo.sh` - this is our CLI. It accepts user commands, does some validation, and forwards them either to Fablo Docker container or generated network scripts in `fablo-target` directory. 23 | 2. Fablo Docker - is a Docker image that contains templates for generating the network files and Yeoman. When Fablo Docker is running, it has mounted `fablo-config.json` file from host and `fablo-target` directory. 24 | 3. `fablo-target` is a directory which contains generated Hyperledger Fabric network files (config files, helper scripts, temporary network files). 25 | 26 | Notable files and directories: 27 | 28 | * `./src` - source code for Yeoman generators, containing subdirectories for given commands and EJS template files. 29 | * `./fablo.sh` - Fablo CLI script. 30 | * `./Dockerfile`, `./docker-entrypoint.sh`, `fablo-build.sh` - files that define and are used to build the Fablo Docker image. 31 | * `e2e-network` - directory that contains integration tests written in Bash scripts. Their goal is to setup sample networks with Fablo and verify them. 32 | * `e2e` - directory that contains integration tests for generating network target files. Tey are mostly Jest snapshot tests. 33 | * `samples` - directory with sample Fablo config JSON and YAML files. 34 | 35 | See also [Contributing Guidelines](CONTRIBUTING.md), where you can find some more instructions how to run Fablo from source code and useful hints what needs to be done while working with the code. 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for considering contributing to our project! We welcome contributions from everyone and appreciate your interest in making our project better. 4 | 5 | ## Sign off Your Work 6 | 7 | The Developer Certificate of Origin (DCO) is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. Here is the full text of the [DCO](http://developercertificate.org/). Contributors must sign-off that they adhere to these requirements by adding a `Signed-off-by` line to commit messages. 8 | 9 | ```text 10 | This is my commit message 11 | 12 | Signed-off-by: John Doe 13 | ``` 14 | 15 | See `git help commit`: 16 | 17 | ```text 18 | -s, --signoff 19 | Add Signed-off-by line by the committer at the end of the commit log 20 | message. The meaning of a signoff depends on the project, but it typically 21 | certifies that committer has the rights to submit this work under the same 22 | license and agrees to a Developer Certificate of Origin (see 23 | http://developercertificate.org/ for more information). 24 | ``` 25 | 26 | ## Getting Started 27 | 28 | 1. **Fork the repository**: Click the "Fork" button at the top right corner of the repository's page on GitHub. 29 | 2. **Clone your fork**: Use `git clone` to clone the repository to your local machine. 30 | 3. **Set up remote upstream**: Add the original repository as a remote named "upstream" using `git remote add upstream [original repository URL]`. 31 | 4. **Create a new branch**: Use `git checkout -b [branch-name]` to create a new branch for your contribution. 32 | 33 | ## Making Changes 34 | 35 | 1. **Make your changes**: Implement the changes or additions you want to make. Please follow any coding standards and guidelines provided in the project. 36 | 2. **Test your changes**: Ensure that your changes work as expected and don't introduce any new issues. Depending on the scope of your changes you may rely on our CI pipelines or run the following tests: 37 | - **Unit tests**: Execute unit tests using the provided scripts. Use: `npm run test:unit`. 38 | - **End-to-End (E2E) tests**: Execute E2E tests using the provided scripts. Use: `npm run test:e2e`. 39 | - **Ent-to-End network tests**: Execute relevant shell scripts from `e2e-network` directory with E2E network tests. 40 | 3. **Update snapshots**: If you've made changes that affect snapshots (esp. any template changes), update them using `npm run test:e2e-update`. 41 | 42 | ## Running Fablo locally 43 | 44 | You may want to verify some changes by running Fablo locally. To do so: 45 | 1. Execute `./fablo-build.sh` script to create a Fablo Docker image locally. 46 | 2. Use `./fablo.sh` from source root directory to call Fablo commands. 47 | 48 | ## Submitting Changes 49 | 50 | 1. **Push your changes**: Once you've made and tested your changes, push them to your forked repository with `git push origin [branch-name]`. 51 | 2. **Create a pull request**: Go to the original repository on GitHub and create a pull request from your forked branch to the main branch. 52 | 3. **Provide details**: Give your pull request a descriptive title and provide details about the changes you've made. 53 | 4. **Review process**: Your pull request will be reviewed by maintainers. Be responsive to any feedback and make necessary changes. 54 | 55 | We appreciate your contributions and look forward to working with you! 56 | 57 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine3.16 2 | 3 | RUN apk add --no-cache sudo shfmt 4 | RUN npm install --global --silent yo 5 | 6 | # copy fablo files 7 | COPY generators /fablo/generators 8 | COPY package.json /fablo/package.json 9 | COPY package-lock.json /fablo/package-lock.json 10 | 11 | # copy files for init network 12 | COPY samples/chaincodes/chaincode-kv-node /fablo/generators/init/templates/chaincodes/chaincode-kv-node 13 | 14 | WORKDIR /fablo 15 | RUN npm install --silent --only=prod 16 | RUN npm link 17 | 18 | # Add a yeoman user because Yeoman freaks out and runs setuid(501). 19 | # This was because less technical people would run Yeoman as root and cause problems. 20 | # Setting uid to 501 here since it's already a random number being thrown around. 21 | # @see https://github.com/yeoman/yeoman.github.io/issues/282 22 | # @see https://github.com/cthulhu666/docker-yeoman/blob/master/Dockerfile 23 | # @see https://github.com/phase2/docker-yeoman/blob/master/Dockerfile 24 | RUN adduser -D -u 501 yeoman && \ 25 | echo "yeoman ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 26 | 27 | # Yeoman needs the use of a home directory for caching and certain config storage. 28 | ENV HOME /network/workspace 29 | 30 | COPY docker-entrypoint.sh /fablo/docker-entrypoint.sh 31 | COPY docs /fablo/docs 32 | COPY README.md /fablo/README.md 33 | COPY samples /fablo/samples/ 34 | 35 | ARG VERSION_DETAILS 36 | RUN echo "{ \"buildInfo\": \"$VERSION_DETAILS\" }" > /fablo/version.json 37 | RUN cat /fablo/version.json 38 | 39 | CMD /fablo/docker-entrypoint.sh 40 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers 2 | 3 | ### Active Maintainers 4 | | name | Github | 5 | |-------------------|-----------| 6 | | Piotr Hejwowski | [@hejwo](https://github.com/hejwo) | 7 | | Jakub Dzikowski | [@dzikowski](https://github.com/dzikowski) | 8 | | Great Umegbewe | [@umegbewe](https://github.com/umegbewe) | 9 | -------------------------------------------------------------------------------- /bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | old_version=$(< package.json jq -r '.version') 6 | include_readme=true 7 | ver_arg="${1:-unstable}" 8 | 9 | if [ "$ver_arg" = "patch" ] || [ "$ver_arg" = "minor" ] || [ "$ver_arg" = "major" ]; then 10 | new_version=$(semver next "$ver_arg" "$old_version") 11 | elif [ "$ver_arg" = "unstable" ]; then 12 | new_version="$(semver next patch "$old_version")-unstable" 13 | include_readme=false 14 | elif [ "$ver_arg" = "set" ]; then 15 | new_version="$2" 16 | else 17 | echo "Invalid version parameter: $ver_arg" 18 | echo "Usage:" 19 | echo " $0 " 20 | echo " $0 set " 21 | exit 1 22 | fi 23 | 24 | echo "Updating version from $old_version to $new_version" 25 | echo -n " - package.json... " 26 | npm version "$new_version" --no-git-tag-version > /dev/null 27 | echo "done" 28 | 29 | echo -n " - FABLO_VERSION... " 30 | perl -i -pe "s/FABLO_VERSION=.*\\n/FABLO_VERSION=${new_version}\\n/g" fablo.sh 31 | perl -i -pe "s/FABLO_VERSION=.*\\n/FABLO_VERSION=${new_version}\\n/g" e2e/__snapshots__/* 32 | echo "done" 33 | 34 | echo -n " - JSON schema URL... " 35 | schema_update_pattern="s/download\/[0-9-.a-zA-Z]*\/schema.json/download\/${new_version}\/schema.json/g" 36 | if [ "$include_readme" = true ]; then 37 | perl -i -pe "$schema_update_pattern" README.md 38 | fi 39 | perl -i -pe "$schema_update_pattern" docs/sample.json 40 | perl -i -pe "$schema_update_pattern" docs/schema.json 41 | perl -i -pe "$schema_update_pattern" samples/*.json 42 | perl -i -pe "$schema_update_pattern" samples/*.yaml 43 | perl -i -pe "$schema_update_pattern" e2e/__snapshots__/* 44 | echo "done" 45 | 46 | if [ "$include_readme" = true ]; then 47 | echo -n " - download URL... " 48 | download_update_pattern="s/download\/[0-9-.a-zA-Z]*\/fablo.sh/download\/${new_version}\/fablo.sh/g" 49 | perl -i -pe "$download_update_pattern" README.md 50 | echo "done" 51 | fi 52 | -------------------------------------------------------------------------------- /check-if-fablo-version-matches.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo 4 | 5 | source ./fablo.sh "help" 6 | 7 | FABLO_SCRIPT_VERSION="$FABLO_VERSION" 8 | FABLO_NODE_VERSION="$(jq -r .version package.json)" 9 | 10 | echo "Checking Fablo version" 11 | echo " FABLO_SCRIPT_VERSION: $FABLO_SCRIPT_VERSION" 12 | echo " FABLO_NODE_VERSION: $FABLO_NODE_VERSION" 13 | 14 | if [ "$FABLO_SCRIPT_VERSION" != "$FABLO_NODE_VERSION" ]; then 15 | echo "Error !" 16 | echo "Fablo version in 'fablo.sh' is not matching version in 'package.json'" 17 | exit 1 18 | fi 19 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | executeYeomanCommand() { 6 | command_with_params=$1 7 | 8 | # cleanup yeoman files after execution 9 | # shellcheck disable=SC2064 10 | trap "rm -rf \"$yeoman_target_dir/.cache\" \"$yeoman_target_dir/.config\" \"$yeoman_target_dir/.npm\"" EXIT 11 | 12 | if [ "$(id -u)" = 0 ]; then 13 | # root user detected, running as yeoman user 14 | sudo chown -R yeoman:yeoman "$yeoman_target_dir" 15 | # shellcheck disable=SC2086 16 | (cd "$yeoman_target_dir" && sudo -E -u yeoman yo --no-insight $command_with_params) 17 | sudo chown -R root:root "$yeoman_target_dir" 18 | else 19 | # shellcheck disable=SC2086 20 | (cd "$yeoman_target_dir" && yo --no-insight $command_with_params) 21 | fi 22 | } 23 | 24 | formatGeneratedFiles() { 25 | # Additional script and yaml formatting 26 | # 27 | # Why? Yeoman output may contain some additional whitespaces or the formatting 28 | # might not be ideal. Keeping those whitespaces, however, might be useful 29 | # in templates to improve the brevity. That's why we need additional formatting. 30 | # Since the templates should obey good practices, we don't use linters here 31 | # (i.e. shellcheck and yamllint). 32 | echo "Formatting generated files" 33 | shfmt -i=2 -l -w "$yeoman_target_dir" >/dev/null 34 | 35 | for yaml in "$yeoman_target_dir"/**/*.yaml; do 36 | 37 | # the expansion failed, no yaml files found 38 | if [ "$yaml" = "$yeoman_target_dir/**/*.yaml" ]; then 39 | break 40 | fi 41 | 42 | # remove trailing spaces 43 | sed --in-place 's/[ \t]*$//' "$yaml" 44 | 45 | # remove duplicated empty/blank lines 46 | content="$(awk -v RS= -v ORS='\n\n' '1' "$yaml")" 47 | echo "$content" >"$yaml" 48 | done 49 | } 50 | 51 | yeoman_target_dir="/network/workspace" 52 | yeoman_command=${1:-Fablo:setup-network} 53 | 54 | # This part of output will be replaces with empty line. It breaks parsing of yeoman generator output. 55 | # See also: https://github.com/yeoman/generator/issues/1294 56 | annoying_yeoman_info="No change to package.json was detected. No package manager install will be executed." 57 | 58 | # Execute the command 59 | executeYeomanCommand "$yeoman_command" 2>&1 | sed "s/$annoying_yeoman_info//g" 60 | 61 | if echo "$yeoman_command" | grep "setup-network"; then 62 | formatGeneratedFiles 63 | fi 64 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | fablo.io -------------------------------------------------------------------------------- /docs/editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fablo config editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 125 | 126 | 127 |
128 |

Fablo config editor

129 |
130 |
131 | 132 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redirecting to main repo page 5 | 6 | 7 |

Redirecting to https://github.com/hyperledger-labs/fablo...

8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 3 | "global": { 4 | "fabricVersion": "2.3.2", 5 | "tls": false, 6 | "peerDevMode": false 7 | }, 8 | "orgs": [ 9 | { 10 | "organization": { 11 | "name": "Orderer", 12 | "mspName": "OrdererMSP", 13 | "domain": "root.com" 14 | }, 15 | "orderers": [ 16 | { 17 | "groupName": "group1", 18 | "prefix": "orderer", 19 | "type": "solo", 20 | "instances": 1 21 | } 22 | ] 23 | }, 24 | { 25 | "organization": { 26 | "name": "Org1", 27 | "mspName": "Org1MSP", 28 | "domain": "org1.example.com" 29 | }, 30 | "ca": { 31 | "prefix": "ca" 32 | }, 33 | "peer": { 34 | "prefix": "peer", 35 | "instances": 2, 36 | "db": "LevelDb" 37 | } 38 | } 39 | ], 40 | "channels": [ 41 | { 42 | "name": "my-channel1", 43 | "orgs": [ 44 | { 45 | "name": "Org1", 46 | "peers": [ 47 | "peer0", 48 | "peer1" 49 | ] 50 | } 51 | ] 52 | } 53 | ], 54 | "chaincodes": [ 55 | { 56 | "name": "chaincode1", 57 | "version": "0.0.1", 58 | "lang": "node", 59 | "channel": "my-channel1", 60 | "init": "{\"Args\":[]}", 61 | "endorsement": "AND ('Org1MSP.member')", 62 | "directory": "./chaincode1", 63 | "privateData": [ 64 | { 65 | "name": "privateCollectionOrg1", 66 | "orgNames": [ 67 | "Org1" 68 | ] 69 | } 70 | ] 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /e2e-network/TEST_CASES.md: -------------------------------------------------------------------------------- 1 | # Test cases 2 | 3 | | Test case | 01-simple | 02-raft | 03-private | 04-snapshot | test-05-version3 | test-05-version3-BFT | 4 | | ------------------------- |:---------------:|:-----------:|:----------:|:------------------------:|:------------------:|:---------------------:| 5 | | Fabric versions | 2.4.7 | 2.3.2 | 2.4.7 | 2.3.3/2.4.2 | 3.0.0-beta | 3.0.0-beta | 6 | | TLS | no | yes | no | yes | yes | yes | 7 | | Channel capabilities | v2 | v2 | v2_5 | v2 | v3_0 | v3_0 | 8 | | Consensus | solo | RAFT | solo | RAFT | RAFT | BFT | 9 | | Orderer nodes | 1 | 3 | 1 | 1 | 3 | 4 | 10 | | Organizations | 1 | 2 | 2 | 1 | 1 | 1 | 11 | | CA database | SQLite | SQLite | SQLite | Postgres | SQLite | SQLite | 12 | | Peer database | LevelDB | LevelDB | LevelDB | CouchDB | LevelDB | LevelDB | 13 | | Peer count | 2 | 2, 2 | 2, 1 | 2 | 2 | 2 | 14 | | Channels | 1 | 2 | 1 | 1 | 1 | 1 | 15 | | Node chaincode | yes | yes | yes | yes | yes | yes | 16 | | Node chaincode upgrade | no | yes | no | no | no | no | 17 | | Node chaincode endorsement| OR | OR | OR, AND | default | OR | OR | 18 | | Private data | no | no | yes | yes | no | no | 19 | | Java chaincode | no | yes | no | no | no | no | 20 | | Go chaincode | no | no | no | no | no | no | 21 | | Tools | channel scripts | Fablo REST | - | Fablo REST, Explorer | - | - | 22 | | Other Fablo commands | init, reset | stop, start | - | snapshot, prune, restore | - | - | 23 | -------------------------------------------------------------------------------- /e2e-network/docker/expect-ca-rest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | rest_api_path="$1" 6 | token="${2:-""}" 7 | data="${3:-""}" 8 | expected="$4" 9 | 10 | if [ -z "$expected" ]; then 11 | echo "Usage: ./expect-ca-rest.sh " 12 | exit 1 13 | fi 14 | 15 | response="$( 16 | curl \ 17 | -s \ 18 | --request POST \ 19 | --url "$rest_api_path" \ 20 | --header 'Content-Type: application/json' \ 21 | --header "Authorization: Bearer $token" \ 22 | --data "$data" 23 | )" 24 | 25 | if echo "$response" | grep -F "$expected"; then 26 | # in case of success this is a single echo, since the response might be 27 | # required by other tests, without additional noise 28 | echo "" 29 | else 30 | label="$rest_api_path / $token / $data" 31 | echo "" 32 | echo "➜ testing: $label" 33 | echo "$response" 34 | echo "❌ failed (rest): $label | expected: $expected" 35 | fi 36 | -------------------------------------------------------------------------------- /e2e-network/docker/expect-command.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | command="$1" 4 | expected="$2" 5 | 6 | if [ -z "$expected" ]; then 7 | echo "Usage: ./expect-command.sh [command] [expected value]" 8 | exit 1 9 | fi 10 | 11 | echo "" 12 | echo "➜ testing: $command" 13 | 14 | response="$(eval "$command" 2>&1)" 15 | 16 | echo "$response" 17 | 18 | if echo "$response" | grep -a -F "$expected"; then 19 | echo "✅ ok (cli): $command" 20 | else 21 | echo "❌ failed (cli): $command | expected: $expected" 22 | exit 1 23 | fi 24 | -------------------------------------------------------------------------------- /e2e-network/docker/expect-invoke-cli.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | peers="$1" 4 | channel="$2" 5 | chaincode="$3" 6 | command="$4" 7 | expected="$5" 8 | transient_default="{}" 9 | transient="${6:-$transient_default}" 10 | 11 | if [ -z "$expected" ]; then 12 | echo "Usage: ./expect-invoke.sh [peer[,peer]] [channel] [chaincode] [command] [expected_substring] [transient_data]" 13 | exit 1 14 | fi 15 | 16 | label="Invoke $channel/$peers $command" 17 | echo "" 18 | echo "➜ testing: $label" 19 | 20 | 21 | response="$( 22 | "$FABLO_HOME/fablo.sh" chaincode invoke "$peers" "$channel" "$chaincode" "$command" "$transient" 23 | )" 24 | 25 | echo "$response" 26 | 27 | if echo "$response" | grep -F "$expected"; then 28 | echo "✅ ok (cli): $label" 29 | else 30 | echo "❌ failed (cli): $label | expected: $expected" 31 | exit 1 32 | fi 33 | -------------------------------------------------------------------------------- /e2e-network/docker/expect-invoke-rest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | rest_api_url="$(echo "$1" | awk '{print $1}')" 6 | access_token="$(echo "$1" | awk '{print $2}')" 7 | channel="$2" 8 | chaincode="$3" 9 | method="$4" 10 | args_json_array="$5" 11 | expected="$6" 12 | transient_default="{}" 13 | transient="${7:-$transient_default}" 14 | 15 | if [ -z "$expected" ]; then 16 | echo "Usage: ./expect-invoke.sh " 17 | exit 1 18 | fi 19 | 20 | label="Invoke $rest_api_url/invoke/$channel/$chaincode $method" 21 | echo "" 22 | echo "➜ testing: $label" 23 | 24 | if [ -z "$access_token" ]; then 25 | the_same_dir="$(cd "$(dirname "$0")" && pwd)" 26 | enroll_admin_response="$("$the_same_dir/expect-ca-rest.sh" "$rest_api_url/user/enroll" '' '{"id": "admin", "secret": "adminpw"}' "token")" 27 | echo "enroll admin response: $enroll_admin_response" 28 | access_token="$(echo "$enroll_admin_response" | jq -r '.token')" 29 | else 30 | echo "using provided token: $access_token" 31 | fi 32 | 33 | response=$( 34 | curl \ 35 | -s \ 36 | --request POST \ 37 | --url "$rest_api_url/invoke/$channel/$chaincode" \ 38 | --header "Authorization: Bearer $access_token" \ 39 | --header 'Content-Type: application/json' \ 40 | --data "{ 41 | \"method\": \"$method\", 42 | \"args\": $args_json_array, 43 | \"transient\": $transient 44 | }" 45 | ) 46 | 47 | echo "$response" 48 | 49 | if echo "$response" | grep -F "$expected"; then 50 | echo "✅ ok (rest): $label" 51 | else 52 | echo "❌ failed (rest): $label | expected: $expected" 53 | exit 1 54 | fi 55 | -------------------------------------------------------------------------------- /e2e-network/docker/test-01-simple.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" 6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" 7 | FABLO_HOME="$TEST_TMP/../../.." 8 | 9 | export FABLO_HOME 10 | 11 | networkUp() { 12 | "$FABLO_HOME/fablo-build.sh" 13 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" init node) 14 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up) 15 | } 16 | 17 | dumpLogs() { 18 | echo "Saving logs of $1 to $TEST_LOGS/$1.log" 19 | mkdir -p "$TEST_LOGS" 20 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 21 | } 22 | 23 | networkDown() { 24 | rm -rf "$TEST_LOGS" 25 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done) 26 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down) 27 | } 28 | 29 | waitForContainer() { 30 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" 31 | } 32 | 33 | waitForChaincode() { 34 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4") 35 | } 36 | 37 | expectInvoke() { 38 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "$6") 39 | } 40 | 41 | expectCommand() { 42 | sh "$TEST_TMP/../expect-command.sh" "$1" "$2" 43 | } 44 | 45 | trap networkDown EXIT 46 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT 47 | 48 | # start the network 49 | networkUp 50 | 51 | waitForContainer "orderer0.group1.orderer.example.com" "Created and started new.*my-channel1" 52 | waitForContainer "ca.org1.example.com" "Listening on http://0.0.0.0:7054" 53 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations" 54 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations" 55 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 56 | waitForContainer "peer0.org1.example.com" "Anchor peer.*with same endpoint, skipping connecting to myself" 57 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7042" 58 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 59 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7041" 60 | 61 | # Test simple chaincode 62 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \ 63 | '{"Args":["KVContract:put", "name", "Willy Wonka"]}' \ 64 | '{\"success\":\"OK\"}' 65 | expectInvoke "peer1.org1.example.com" "my-channel1" "chaincode1" \ 66 | '{"Args":["KVContract:get", "name"]}' \ 67 | '{\"success\":\"Willy Wonka\"}' 68 | 69 | # Verify channel query scripts 70 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch newest my-channel1 org1 peer1) 71 | expectCommand "cat \"$TEST_TMP/newest.block\"" "KVContract:get" 72 | 73 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch 4 my-channel1 org1 peer1 "another.block") 74 | expectCommand "cat \"$TEST_TMP/another.block\"" "KVContract:put" 75 | 76 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch config my-channel1 org1 peer1 "channel-config.json") 77 | expectCommand "cat \"$TEST_TMP/channel-config.json\"" "\"mod_policy\": \"Admins\"," 78 | 79 | expectCommand "(cd \"$TEST_TMP\" && \"$FABLO_HOME/fablo.sh\" channel getinfo my-channel1 org1 peer1)" "\"height\":6" 80 | 81 | # Reset and ensure the state is lost after reset 82 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" reset) 83 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1" 84 | waitForChaincode "peer1.org1.example.com" "my-channel1" "chaincode1" "0.0.1" 85 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \ 86 | '{"Args":["KVContract:get", "name"]}' \ 87 | '{\"error\":\"NOT_FOUND\"}' 88 | 89 | # Put some data again 90 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \ 91 | '{"Args":["KVContract:put", "name", "James Bond"]}' \ 92 | '{\"success\":\"OK\"}' -------------------------------------------------------------------------------- /e2e-network/docker/test-02-raft-2orgs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" 6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" 7 | FABLO_HOME="$TEST_TMP/../../.." 8 | 9 | export FABLO_HOME 10 | 11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml" 12 | 13 | networkUp() { 14 | # separate generate and up is intentional just to check if it works 15 | "$FABLO_HOME/fablo-build.sh" 16 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" generate "$CONFIG") 17 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up) 18 | } 19 | 20 | dumpLogs() { 21 | echo "Saving logs of $1 to $TEST_LOGS/$1.log" 22 | mkdir -p "$TEST_LOGS" 23 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 24 | } 25 | 26 | networkDown() { 27 | sleep 2 28 | rm -rf "$TEST_LOGS" 29 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done) 30 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down) 31 | } 32 | 33 | waitForContainer() { 34 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" 35 | } 36 | 37 | waitForChaincode() { 38 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4") 39 | } 40 | 41 | expectInvokeRest() { 42 | sh "$TEST_TMP/../expect-invoke-rest.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7" 43 | } 44 | 45 | expectInvokeCli() { 46 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "$6") 47 | } 48 | 49 | trap networkDown EXIT 50 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT 51 | 52 | # start the network 53 | networkUp 54 | 55 | # check if orderers are ready 56 | waitForContainer "orderer0.group1.orderer1.com" "Starting Raft node channel=my-channel1" 57 | waitForContainer "orderer0.group1.orderer1.com" "Starting Raft node channel=my-channel2" 58 | waitForContainer "orderer1.group1.orderer1.com" "Starting Raft node channel=my-channel1" 59 | waitForContainer "orderer1.group1.orderer1.com" "Starting Raft node channel=my-channel2" 60 | waitForContainer "orderer2.group1.orderer1.com" "Starting Raft node channel=my-channel1" 61 | waitForContainer "orderer2.group1.orderer1.com" "Starting Raft node channel=my-channel2" 62 | 63 | waitForContainer "orderer0.group2.orderer2.com" "Created and started new channel my-channel3" 64 | 65 | # check if org1 is ready 66 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054" 67 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 2 organizations" 68 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 69 | waitForContainer "peer0.org1.example.com" "Anchor peer for channel my-channel1 with same endpoint, skipping connecting to myself" 70 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer0.org2.example.com:7081" 71 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel2 with 2 organizations" 72 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel2" 73 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer1.org2.example.com:7082" 74 | 75 | # check if org2 is ready 76 | waitForContainer "ca.org2.example.com" "Listening on https://0.0.0.0:7054" 77 | waitForContainer "peer0.org2.example.com" "Joining gossip network of channel my-channel1 with 2 organizations" 78 | waitForContainer "peer0.org2.example.com" "Learning about the configured anchor peers of Org2MSP for channel my-channel1" 79 | waitForContainer "peer0.org2.example.com" "Anchor peer for channel my-channel1 with same endpoint, skipping connecting to myself" 80 | waitForContainer "peer0.org2.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7061" 81 | waitForContainer "peer1.org2.example.com" "Joining gossip network of channel my-channel2 with 2 organizations" 82 | waitForContainer "peer1.org2.example.com" "Learning about the configured anchor peers of Org2MSP for channel my-channel2" 83 | waitForContainer "peer1.org2.example.com" "Anchor peer for channel my-channel2 with same endpoint, skipping connecting to myself" 84 | waitForContainer "peer1.org2.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7062" 85 | 86 | # check if chaincodes are instantiated on peers 87 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1" 88 | waitForChaincode "peer0.org2.example.com" "my-channel1" "chaincode1" "0.0.1" 89 | waitForChaincode "peer1.org1.example.com" "my-channel2" "chaincode2" "0.0.1" 90 | waitForChaincode "peer1.org2.example.com" "my-channel2" "chaincode2" "0.0.1" 91 | 92 | fablo_rest_org1="localhost:8802" 93 | 94 | # invoke Node chaincode 95 | expectInvokeRest "$fablo_rest_org1" "my-channel1" "chaincode1" \ 96 | "KVContract:put" '["name", "Jack Sparrow"]' \ 97 | '{"response":{"success":"OK"}}' 98 | expectInvokeCli "peer0.org2.example.com" "my-channel1" "chaincode1" \ 99 | '{"Args":["KVContract:get", "name"]}' \ 100 | '{\"success\":\"Jack Sparrow\"}' 101 | 102 | # invoke Java chaincode 103 | expectInvokeRest "$fablo_rest_org1" "my-channel2" "chaincode2" \ 104 | "PokeballContract:createPokeball" '["id1", "Pokeball 1"]' \ 105 | '{"response":""}' 106 | expectInvokeCli "peer1.org2.example.com" "my-channel2" "chaincode2" \ 107 | '{"Args":["PokeballContract:readPokeball", "id1"]}' \ 108 | '{\"value\":\"Pokeball 1\"}' 109 | 110 | # restart the network and wait for chaincodes 111 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" stop && "$FABLO_HOME/fablo.sh" start) 112 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1" 113 | waitForChaincode "peer0.org2.example.com" "my-channel1" "chaincode1" "0.0.1" 114 | 115 | # upgrade chaincode 116 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" chaincode upgrade "chaincode1" "0.0.2") 117 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.2" 118 | waitForChaincode "peer0.org2.example.com" "my-channel1" "chaincode1" "0.0.2" 119 | 120 | # check if state is kept after update 121 | expectInvokeRest "$fablo_rest_org1" "my-channel1" "chaincode1" \ 122 | "KVContract:get" '["name"]' \ 123 | '{"response":{"success":"Jack Sparrow"}}' 124 | -------------------------------------------------------------------------------- /e2e-network/docker/test-03-private-data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" 6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" 7 | FABLO_HOME="$TEST_TMP/../../.." 8 | 9 | export FABLO_HOME 10 | 11 | FABLO_CONFIG="$FABLO_HOME/samples/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml" 12 | 13 | networkUp() { 14 | "$FABLO_HOME/fablo-build.sh" 15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up "$FABLO_CONFIG") 16 | } 17 | 18 | dumpLogs() { 19 | echo "Saving logs of $1 to $TEST_LOGS/$1.log" 20 | mkdir -p "$TEST_LOGS" 21 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 22 | } 23 | 24 | networkDown() { 25 | rm -rf "$TEST_LOGS" 26 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done) 27 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down) 28 | } 29 | 30 | waitForContainer() { 31 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" 32 | } 33 | 34 | waitForChaincode() { 35 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4") 36 | } 37 | 38 | expectInvoke() { 39 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "$6") 40 | } 41 | 42 | trap networkDown EXIT 43 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT 44 | 45 | # start the network 46 | networkUp 47 | 48 | # check if network is ready 49 | waitForContainer "orderer0.group1.orderer.example.com" "Created and started new channel my-channel1" 50 | waitForContainer "ca.org1.example.com" "Listening on http://0.0.0.0:7054" 51 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 2 organizations" 52 | waitForContainer "ca.org2.example.com" "Listening on http://0.0.0.0:7054" 53 | waitForContainer "peer0.org2.example.com" "Joining gossip network of channel my-channel1 with 2 organizations" 54 | 55 | waitForChaincode "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" "0.0.1" 56 | waitForChaincode "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" "0.0.1" 57 | waitForChaincode "peer0.org1.example.com" "my-channel1" "and-policy-chaincode" "0.0.1" 58 | waitForChaincode "peer0.org2.example.com" "my-channel1" "and-policy-chaincode" "0.0.1" 59 | 60 | sleep 3 # extra time needed: peers need to discover themselves before private data call. 61 | 62 | # Org1: Test chaincode with transient fields and private data 63 | expectInvoke "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" \ 64 | '{"Args":["KVContract:putPrivateMessage", "org1-collection"]}' \ 65 | '{\"success\":\"OK\"}' \ 66 | '{"message":"VmVyeSBzZWNyZXQgbWVzc2FnZQ=="}' 67 | expectInvoke "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" \ 68 | '{"Args":["KVContract:getPrivateMessage", "org1-collection"]}' \ 69 | '{\"success\":\"Very secret message\"}' 70 | expectInvoke "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" \ 71 | '{"Args":["KVContract:verifyPrivateMessage", "org1-collection"]}' \ 72 | '{\"success\":\"OK\"}' \ 73 | '{"message":"VmVyeSBzZWNyZXQgbWVzc2FnZQ=="}' 74 | 75 | # Org2: Access private data from org1-collection 76 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \ 77 | '{"Args":["KVContract:verifyPrivateMessage", "org1-collection"]}' \ 78 | '{\"success\":\"OK\"}' \ 79 | '{"message":"VmVyeSBzZWNyZXQgbWVzc2FnZQ=="}' 80 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \ 81 | '{"Args":["KVContract:verifyPrivateMessage", "org1-collection"]}' \ 82 | '{\"error\":\"VERIFICATION_FAILED\"}' \ 83 | '{"message":"XXXXXSBzZWNyZXQgbWVzc2FnZQ=="}' 84 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \ 85 | '{"Args":["KVContract:getPrivateMessage", "org1-collection"]}' \ 86 | 'tx creator does not have read access permission on privatedata in chaincodeName:or-policy-chaincode collectionName: org1-collection' 87 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \ 88 | '{"Args":["KVContract:putPrivateMessage", "org1-collection"]}' \ 89 | 'tx creator does not have write access permission on privatedata in chaincodeName:or-policy-chaincode collectionName: org1-collection' \ 90 | '{"message":"Q29ycnVwdGVkIG1lc3NhZ2U="}' 91 | 92 | # Org1 and Org2: Test chaincode with AND endorsement policy 93 | expectInvoke "peer0.org2.example.com,peer0.org1.example.com" "my-channel1" "and-policy-chaincode" \ 94 | '{"Args":["KVContract:putPrivateMessage", "both-orgs-collection"]}' \ 95 | '{\"success\":\"OK\"}' \ 96 | '{"message":"QW5kIGFub3RoZXIgb25l"}' 97 | expectInvoke "peer0.org1.example.com,peer0.org2.example.com" "my-channel1" "and-policy-chaincode" \ 98 | '{"Args":["KVContract:getPrivateMessage", "both-orgs-collection"]}' \ 99 | '{\"success\":\"And another one\"}' 100 | -------------------------------------------------------------------------------- /e2e-network/docker/test-04-snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" 6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" 7 | FABLO_HOME="$TEST_TMP/../../.." 8 | 9 | export FABLO_HOME 10 | 11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf2-1org-1chaincode-raft-explorer.json" 12 | 13 | networkUp() { 14 | "$FABLO_HOME/fablo-build.sh" 15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up "$CONFIG") 16 | } 17 | 18 | dumpLogs() { 19 | echo "Saving logs of $1 to $TEST_LOGS/$1.log" 20 | mkdir -p "$TEST_LOGS" 21 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 22 | } 23 | 24 | networkDown() { 25 | sleep 2 26 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done) 27 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down) 28 | } 29 | 30 | waitForContainer() { 31 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" 32 | } 33 | 34 | waitForChaincode() { 35 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4") 36 | } 37 | 38 | expectInvokeRest() { 39 | sh "$TEST_TMP/../expect-invoke-rest.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7" 40 | } 41 | 42 | expectCARest() { 43 | sh "$TEST_TMP/../expect-ca-rest.sh" "$1" "$2" "$3" "$4" 44 | } 45 | 46 | trap networkDown EXIT 47 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT 48 | 49 | # start the network 50 | networkUp 51 | 52 | # check if all nodes are ready 53 | waitForContainer "orderer0.group1.orderer.example.com" "Starting Raft node channel=my-channel1" 54 | waitForContainer "db.ca.org1.example.com" "database system is ready to accept connections" 55 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054" 56 | waitForContainer "couchdb.peer0.org1.example.com" "Apache CouchDB has started. Time to relax." 57 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations" 58 | waitForContainer "db.explorer.example.com" "database system is ready to accept connections" "200" 59 | waitForContainer "explorer.example.com" "Successfully created channel event hub for \[my-channel1\]" "200" 60 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1" 61 | 62 | fablo_rest_org1="localhost:8801" 63 | snapshot_name="fablo-snapshot-$(date -u +"%Y%m%d%H%M%S")" 64 | 65 | # register and enroll test user 66 | admin_token_response="$(expectCARest "$fablo_rest_org1/user/enroll" '' '{"id": "admin", "secret": "adminpw"}' 'token')" 67 | echo "$admin_token_response" 68 | admin_token="$(echo "$admin_token_response" | jq -r '.token')" 69 | 70 | register_response="$(expectCARest "$fablo_rest_org1/user/register" "$admin_token" '{"id": "gordon", "secret": "gordonpw"}' 'ok')" 71 | echo "$register_response" 72 | 73 | user_token_response="$(expectCARest "$fablo_rest_org1/user/enroll" '' '{"id": "gordon", "secret": "gordonpw"}' 'token')" 74 | echo "$user_token_response" 75 | user_token="$(echo "$user_token_response" | jq -r '.token')" 76 | 77 | # save some data 78 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 79 | "KVContract:put" '["name", "Mr Freeze"]' \ 80 | '{"response":{"success":"OK"}}' 81 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 82 | "KVContract:putPrivateMessage" '["_implicit_org_Org1MSP"]' \ 83 | '{"success":"OK"}' \ 84 | '{"message":"RHIgVmljdG9yIEZyaWVz"}' 85 | 86 | # create snapshot 87 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" snapshot "$snapshot_name") 88 | 89 | # overwrite the data 90 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 91 | "KVContract:put" '["name", "Poison Ivy"]' \ 92 | '{"response":{"success":"OK"}}' 93 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 94 | "KVContract:putPrivateMessage" '["_implicit_org_Org1MSP"]' \ 95 | '{"success":"OK"}' \ 96 | '{"message":"RHIgUGFtZWxhIElzbGV5"}' 97 | 98 | # verify it is updated 99 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 100 | "KVContract:get" '["name"]' \ 101 | '{"response":{"success":"Poison Ivy"}}' 102 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 103 | "KVContract:getPrivateMessage" '["_implicit_org_Org1MSP"]' \ 104 | '{"success":"RHIgUGFtZWxhIElzbGV5"}' 105 | 106 | # restore hook to update fabric version 107 | hook_command="perl -i -pe 's/FABRIC_VERSION=2\.3\.3/FABRIC_VERSION=2\.4\.2/g' ./fablo-target/fabric-docker/.env" 108 | 109 | # prune the network and restore from snapshot 110 | ( 111 | cd "$TEST_TMP" && 112 | "$FABLO_HOME/fablo.sh" prune && 113 | "$FABLO_HOME/fablo.sh" restore "$snapshot_name" "$hook_command" && 114 | "$FABLO_HOME/fablo.sh" start 115 | ) 116 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1" 117 | 118 | sleep 5 119 | 120 | user_token_response="$(expectCARest "$fablo_rest_org1/user/enroll" '' '{"id": "gordon", "secret": "gordonpw"}' 'token')" 121 | echo "$user_token_response" 122 | user_token="$(echo "$user_token_response" | jq -r '.token')" 123 | 124 | # check if state is kept after restoration 125 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 126 | "KVContract:get" '["name"]' \ 127 | '{"response":{"success":"Mr Freeze"}}' 128 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \ 129 | "KVContract:getPrivateMessage" '["_implicit_org_Org1MSP"]' \ 130 | '{"success":"RHIgVmljdG9yIEZyaWVz"}' 131 | -------------------------------------------------------------------------------- /e2e-network/docker/test-05-version3-BFT.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" 6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" 7 | FABLO_HOME="$TEST_TMP/../../.." 8 | 9 | export FABLO_HOME 10 | 11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf3-bft-1orgs-1chaincode.json" 12 | 13 | networkUp() { 14 | "$FABLO_HOME/fablo-build.sh" 15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" generate "$CONFIG") 16 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up) 17 | } 18 | 19 | dumpLogs() { 20 | echo "Saving logs of $1 to $TEST_LOGS/$1.log" 21 | mkdir -p "$TEST_LOGS" 22 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 23 | } 24 | 25 | networkDown() { 26 | rm -rf "$TEST_LOGS" 27 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done) 28 | dumpLogs orderer0.group1.orderer.example.com 29 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down) 30 | } 31 | 32 | waitForContainer() { 33 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" 34 | } 35 | 36 | waitForChaincode() { 37 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4") 38 | } 39 | 40 | expectInvoke() { 41 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "") 42 | } 43 | 44 | expectCommand() { 45 | sh "$TEST_TMP/../expect-command.sh" "$1" "$2" 46 | } 47 | 48 | trap networkDown EXIT 49 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT 50 | 51 | # start the network 52 | networkUp 53 | 54 | waitForContainer "orderer0.group1.orderer.example.com" "Channel created" 55 | waitForContainer "orderer1.group1.orderer.example.com" "Channel created" 56 | waitForContainer "orderer2.group1.orderer.example.com" "Channel created" 57 | waitForContainer "orderer3.group1.orderer.example.com" "Channel created" 58 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054" 59 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations" 60 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations" 61 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 62 | waitForContainer "peer0.org1.example.com" "Anchor peer.*with same endpoint, skipping connecting to myself" 63 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7042" 64 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 65 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7041" 66 | 67 | # Test simple chaincode 68 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \ 69 | '{"Args":["KVContract:put", "name", "Willy Wonka"]}' \ 70 | '{\"success\":\"OK\"}' 71 | expectInvoke "peer1.org1.example.com" "my-channel1" "chaincode1" \ 72 | '{"Args":["KVContract:get", "name"]}' \ 73 | '{\"success\":\"Willy Wonka\"}' 74 | 75 | # Verify channel query scripts 76 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch newest my-channel1 org1 peer1) 77 | expectCommand "cat \"$TEST_TMP/newest.block\"" "KVContract:get" 78 | 79 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch 3 my-channel1 org1 peer1 "another.block") 80 | expectCommand "cat \"$TEST_TMP/another.block\"" "KVContract:put" 81 | 82 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch config my-channel1 org1 peer1 "channel-config.json") 83 | expectCommand "cat \"$TEST_TMP/channel-config.json\"" "\"mod_policy\": \"Admins\"," 84 | 85 | expectCommand "(cd \"$TEST_TMP\" && \"$FABLO_HOME/fablo.sh\" channel getinfo my-channel1 org1 peer1)" "\"height\":5" 86 | 87 | echo "🎉 Test passed! 🎉" -------------------------------------------------------------------------------- /e2e-network/docker/test-05-version3.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" 6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" 7 | FABLO_HOME="$TEST_TMP/../../.." 8 | 9 | export FABLO_HOME 10 | 11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf3-1orgs-1chaincode.json" 12 | 13 | networkUp() { 14 | "$FABLO_HOME/fablo-build.sh" 15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" generate "$CONFIG") 16 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up) 17 | } 18 | 19 | dumpLogs() { 20 | echo "Saving logs of $1 to $TEST_LOGS/$1.log" 21 | mkdir -p "$TEST_LOGS" 22 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 23 | } 24 | 25 | networkDown() { 26 | rm -rf "$TEST_LOGS" 27 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done) 28 | dumpLogs orderer0.group1.orderer.example.com 29 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down) 30 | } 31 | 32 | waitForContainer() { 33 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" 34 | } 35 | 36 | waitForChaincode() { 37 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4") 38 | } 39 | 40 | expectInvoke() { 41 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "") 42 | } 43 | 44 | expectCommand() { 45 | sh "$TEST_TMP/../expect-command.sh" "$1" "$2" 46 | } 47 | 48 | trap networkDown EXIT 49 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT 50 | 51 | # start the network 52 | networkUp 53 | 54 | waitForContainer "orderer0.group1.orderer.example.com" "Starting raft node as part of a new channel channel=my-channel1" 55 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054" 56 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations" 57 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations" 58 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 59 | waitForContainer "peer0.org1.example.com" "Anchor peer.*with same endpoint, skipping connecting to myself" 60 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7042" 61 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 62 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7041" 63 | 64 | # Test simple chaincode 65 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \ 66 | '{"Args":["KVContract:put", "name", "Willy Wonka"]}' \ 67 | '{\"success\":\"OK\"}' 68 | expectInvoke "peer1.org1.example.com" "my-channel1" "chaincode1" \ 69 | '{"Args":["KVContract:get", "name"]}' \ 70 | '{\"success\":\"Willy Wonka\"}' 71 | 72 | # Verify channel query scripts 73 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch newest my-channel1 org1 peer1) 74 | expectCommand "cat \"$TEST_TMP/newest.block\"" "KVContract:get" 75 | 76 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch 3 my-channel1 org1 peer1 "another.block") 77 | expectCommand "cat \"$TEST_TMP/another.block\"" "KVContract:put" 78 | 79 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch config my-channel1 org1 peer1 "channel-config.json") 80 | expectCommand "cat \"$TEST_TMP/channel-config.json\"" "\"mod_policy\": \"Admins\"," 81 | 82 | expectCommand "(cd \"$TEST_TMP\" && \"$FABLO_HOME/fablo.sh\" channel getinfo my-channel1 org1 peer1)" "\"height\":5" 83 | 84 | echo "🎉 Test passed! 🎉" -------------------------------------------------------------------------------- /e2e-network/docker/wait-for-chaincode.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | peer=$1 4 | channel=$2 5 | chaincode=$3 6 | version=$4 7 | search_string="Name: $chaincode, Version: $version" 8 | 9 | if [ -z "$version" ]; then 10 | echo "Usage: ./wait-for-chaincode.sh [peer:port] [channel] [chaincode] [version]" 11 | exit 1 12 | fi 13 | 14 | listChaincodes() { 15 | "$FABLO_HOME/fablo.sh" chaincodes list "$peer" "$channel" 16 | } 17 | 18 | for i in $(seq 1 90); do 19 | echo "➜ verifying if chaincode ($chaincode/$version) is committed on $channel/$peer ($i)..." 20 | if listChaincodes 2>&1 | grep "$search_string"; then 21 | listChaincodes 22 | echo "✅ ok: Chaincode $chaincode/$version is ready on $channel/$peer!" 23 | exit 0 24 | else 25 | sleep 1 26 | fi 27 | done 28 | 29 | #timeout 30 | echo "❌ failed: Failed to verify chaincode $chaincode/$version on $channel/$peer" 31 | listChaincodes 32 | exit 1 33 | -------------------------------------------------------------------------------- /e2e-network/docker/wait-for-container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | container="$1" 4 | expected_message="$2" 5 | max_attempts="${3:-90}" 6 | 7 | end='\e[0m' 8 | darkGray='\e[90m' 9 | 10 | if [ -z "$expected_message" ]; then 11 | echo "Usage: ./wait-for-container.sh [container_name] [expected_message]" 12 | exit 1 13 | fi 14 | 15 | for i in $(seq 1 "$max_attempts"); do 16 | echo "➜ verifying if container $container logs contain ($i)... ${darkGray}'$expected_message'${end}" 17 | 18 | if docker logs "$container" 2>&1 | grep -q "$expected_message"; then 19 | echo "✅ ok: Container $container is ready!" 20 | exit 0 21 | else 22 | sleep 1 23 | fi 24 | done 25 | 26 | #timeout 27 | echo "❌ failed: Container $container logs does not contain ${darkGray}'$expected_message'${end}" 28 | echo "Last log messages:" 29 | docker logs "$container" | tail -n 30 30 | exit 1 31 | -------------------------------------------------------------------------------- /e2e-network/k8s/expect-invoke-cli.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | user="$1" 6 | peer="$2" 7 | channel="$3" 8 | chaincode="$4" 9 | fcn="$5" 10 | key="$6" 11 | value="$7" 12 | expected="$8" 13 | config="$(find . -type f -iname 'org1.yaml')" 14 | 15 | 16 | 17 | 18 | if [ -z "$expected" ]; then 19 | echo "Usage: ./expect-invoke.sh [user] [peer] [chaincdoe] [channel] [fcn] [arg1] [arg2] [expected_substring]" 20 | exit 1 21 | fi 22 | 23 | label="Invoke $channel/$peer" 24 | echo "" 25 | echo "➜ testing: $label" 26 | 27 | response="$( 28 | kubectl hlf chaincode invoke \ 29 | --config "$config" \ 30 | --user "$user" \ 31 | --peer "$peer" \ 32 | --chaincode "$chaincode" \ 33 | --channel "$channel" \ 34 | --fcn "$fcn" \ 35 | -a "$key" \ 36 | ${value:+ -a "$value"} \ 37 | 38 | # shellcheck disable=SC2188 39 | 2>&1 40 | )" 41 | 42 | echo "$response" 43 | 44 | if echo "$response" | grep -F "$expected"; then 45 | echo "✅ ok (cli): $label" 46 | else 47 | echo "❌ failed (cli): $label | expected: $expected" 48 | exit 1 49 | fi 50 | -------------------------------------------------------------------------------- /e2e-network/k8s/test-01-simple-k8s.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" 6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" 7 | FABLO_HOME="$TEST_TMP/../../.." 8 | 9 | networkUp() { 10 | "$FABLO_HOME/fablo-build.sh" 11 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" init kubernetes node) 12 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up) 13 | } 14 | 15 | dumpLogs() { 16 | echo "Saving logs of $1 to $TEST_LOGS/$1.log" 17 | mkdir -p "$TEST_LOGS" 18 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 19 | } 20 | 21 | networkDown() { 22 | rm -rf "$TEST_LOGS" 23 | (cd "$TEST_TMP" && "$(find . -type f -iname 'fabric-k8s.sh')" down) 24 | } 25 | 26 | waitForContainer() { 27 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" 28 | } 29 | 30 | waitForChaincode() { 31 | sh "$TEST_TMP/../wait-for-chaincode.sh" "$1" "$2" "$3" "$4" "$5" 32 | } 33 | 34 | expectInvoke() { 35 | sh "$TEST_TMP/../expect-invoke-cli.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" 36 | } 37 | 38 | trap networkDown EXIT 39 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT 40 | 41 | # start the network 42 | networkUp 43 | 44 | peer0="$(kubectl get pods | grep peer0 | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1 | head -n 1) peer" 45 | peer1="$(kubectl get pods | grep peer1 | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1 | head -n 1) peer" 46 | ca=$(kubectl get pods | grep org1-ca | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1) 47 | orderer=$(kubectl get pods | grep orderer-node | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1) 48 | 49 | waitForContainer "$orderer" "Starting raft node as part of a new channel channel=my-channel1 node=1" 50 | waitForContainer "$ca" "Listening on https://0.0.0.0:7054" 51 | waitForContainer "$peer0" "Joining gossip network of channel my-channel1 with 1 organizations" 52 | waitForContainer "$peer1" "Joining gossip network of channel my-channel1 with 1 organizations" 53 | waitForContainer "$peer0" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 54 | waitForContainer "$peer0" "Anchor peer.*with same endpoint, skipping connecting to myself" 55 | waitForContainer "$peer0" "Membership view has changed. peers went online:" 56 | waitForContainer "$peer1" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" 57 | waitForContainer "$peer1" "Membership view has changed. peers went online:" 58 | 59 | 60 | #Test simple chaincode 61 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ 62 | "put" "[\"name\"]" "Willy Wonka" "{\"success\":\"OK\"}" 63 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ 64 | "get" "[\"name\"]" "" '{"success":"Willy Wonka"}' 65 | 66 | 67 | # Reset and ensure the state is lost after reset 68 | (cd "$TEST_TMP" && "$(find . -type f -iname 'fabric-k8s.sh')" reset) 69 | waitForChaincode "admin" "peer0.default" "my-channel1" "chaincode1" "1.0" 70 | waitForChaincode "admin" "peer1.default" "my-channel1" "chaincode1" "1.0" 71 | 72 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ 73 | "get" "[\"name\"]" "" '{"error":"NOT_FOUND"}' 74 | 75 | # Put some data again 76 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ 77 | "put" "[\"name\"]" "James Bond" "{\"success\":\"OK\"}" 78 | -------------------------------------------------------------------------------- /e2e-network/k8s/wait-for-chaincode.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | user=$1 4 | peer=$2 5 | channel=$3 6 | chaincode=$4 7 | version=$5 8 | config="$(find . -type f -iname 'org1.yaml')" 9 | 10 | if [ -z "$version" ]; then 11 | echo "Usage: ./wait-for-chaincode.sh [user] [peer] [channel] [chaincode] [version]" 12 | exit 1 13 | fi 14 | 15 | listChaincodes() { 16 | kubectl hlf chaincode querycommitted \ 17 | --config "$config" \ 18 | --user "$user" \ 19 | --peer "$peer" \ 20 | --channel "$channel" 21 | } 22 | 23 | echo "➜ verifying if chaincode ($chaincode/$version) is committed on $channel/$peer ..." 24 | 25 | if listChaincodes 2>&1 | grep -q "$chaincode\|$version"; then 26 | echo "✅ ok: Chaincode $chaincode/$version is ready on $channel/$peer!" 27 | exit 0 28 | else 29 | sleep 1 30 | fi 31 | 32 | #timeout 33 | echo "❌ failed: Failed to verify chaincode $chaincode/$version on $channel/$peer" 34 | listChaincodes 35 | exit 1 36 | -------------------------------------------------------------------------------- /e2e-network/k8s/wait-for-container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | container="$1" 4 | expected_message="$2" 5 | max_attempts="${3:-10}" 6 | 7 | end="$(printf '\e[0m')" 8 | darkGray="$(printf '\e[90m')" 9 | 10 | if [ -z "$expected_message" ]; then 11 | echo "Usage: ./wait-for-container.sh [container_name] [expected_message]" 12 | exit 1 13 | fi 14 | 15 | for i in $(seq 1 "$max_attempts"); do 16 | echo "➜ verifying if container $container logs contain ($i)... ${darkGray}'$expected_message'${end}" 17 | 18 | # in some cases you need to pass two arguments at once 19 | # shellcheck disable=SC2086 20 | if kubectl logs $container 2>&1 | grep -q "$expected_message"; then 21 | echo "✅ ok: Container $container is ready!" 22 | exit 0 23 | else 24 | sleep 1 25 | fi 26 | done 27 | 28 | #timeout 29 | echo "❌ failed: Container $container logs does not contain ${darkGray}'$expected_message'${end}" 30 | 31 | exit 1 32 | -------------------------------------------------------------------------------- /e2e/TestCommands.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | 3 | interface CommandOutput { 4 | status: number; 5 | output: string; 6 | outputJson: () => Record; 7 | } 8 | 9 | const commandOutput = (status: number, output: string): CommandOutput => ({ 10 | status, 11 | output, 12 | outputJson() { 13 | try { 14 | return JSON.parse(output); 15 | } catch (e) { 16 | // eslint-disable-next-line no-console 17 | console.error(e); 18 | // eslint-disable-next-line no-console 19 | console.error(output); 20 | throw e; 21 | } 22 | }, 23 | }); 24 | 25 | const executeCommand = (c: string, noConsole = false): CommandOutput => { 26 | // eslint-disable-next-line no-console 27 | const log = !noConsole ? (out: string) => console.log(out) : () => ({}); 28 | try { 29 | log(c); 30 | const output = execSync(c, { encoding: "utf-8" }); 31 | log(output); 32 | return commandOutput(0, output); 33 | } catch (e) { 34 | const output = ((e as { output?: string[] }).output ?? []).join(""); 35 | // eslint-disable-next-line no-console 36 | console.error(`Error executing command ${c}`, e, output); 37 | return commandOutput((e as { status: number }).status, output); 38 | } 39 | }; 40 | 41 | class TestCommands { 42 | static success = (): unknown => expect.objectContaining({ status: 0 }); 43 | 44 | static failure = (): unknown => expect.objectContaining({ status: 1 }); 45 | 46 | readonly relativeRoot: string; 47 | 48 | constructor(readonly workdir: string) { 49 | this.relativeRoot = workdir.replace(/[^\/]+/g, ".."); 50 | } 51 | 52 | execute(command: string): CommandOutput { 53 | return executeCommand(command); 54 | } 55 | 56 | fabloExec(command: string, noConsole = false): CommandOutput { 57 | return executeCommand(`cd ${this.workdir} && ${this.relativeRoot}/fablo.sh ${command}`, noConsole); 58 | } 59 | 60 | getFiles(dir?: string): string[] { 61 | return executeCommand(`find ${dir || this.workdir} -type f`) 62 | .output.split("\n") 63 | .filter((s) => !!s.length) 64 | .sort(); 65 | } 66 | 67 | getFileContent(file: string): string { 68 | return executeCommand(`cat "${this.workdir}/${file}"`, true).output; 69 | } 70 | 71 | cleanupWorkdir(): void { 72 | execSync(`rm -rf ${this.workdir} ; mkdir -p ${this.workdir}`); 73 | } 74 | } 75 | 76 | export default TestCommands; 77 | export { CommandOutput }; 78 | -------------------------------------------------------------------------------- /e2e/create-test-cases.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | FABLO_HOME="$(dirname "$0")/.." 6 | 7 | ( 8 | cd "$FABLO_HOME/samples" 9 | for f in fablo-config-*; do 10 | echo "import performTests from \"./performTests\"; 11 | 12 | const config = \"samples/$f\"; 13 | 14 | describe(config, () => { 15 | performTests(config); 16 | });" >"../e2e/${f}.test.ts" 17 | done 18 | ) 19 | -------------------------------------------------------------------------------- /e2e/extendConfig.test.ts: -------------------------------------------------------------------------------- 1 | import TestCommands from "./TestCommands"; 2 | import parseFabloConfig from "../src/utils/parseFabloConfig"; 3 | import extendConfig from "../src/extend-config/extendConfig"; 4 | 5 | const commands = new TestCommands("e2e/__tmp__/extend-config-tests"); 6 | 7 | describe("extend config", () => { 8 | beforeEach(() => commands.cleanupWorkdir()); 9 | 10 | beforeAll(() => { 11 | process.env.FABLO_CONFIG = ""; 12 | process.env.CHAINCODES_BASE_DIR = ""; 13 | }); 14 | 15 | afterAll(() => { 16 | delete process.env.FABLO_CONFIG; 17 | delete process.env.CHAINCODES_BASE_DIR; 18 | }); 19 | 20 | const files = commands.getFiles("samples/*.json").concat(commands.getFiles("samples/*.yaml")); 21 | 22 | files.forEach((file) => { 23 | it(file, () => { 24 | const fileContent = commands.getFileContent(`${commands.relativeRoot}/${file}`); 25 | const json = parseFabloConfig(fileContent); 26 | 27 | // when 28 | const extended = extendConfig(json); 29 | 30 | // then 31 | expect(extended).toMatchSnapshot(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | const config = "samples/fablo-config-hlf2-1org-1chaincode-k8s.json"; 4 | 5 | describe(config, () => { 6 | performTests(config); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf2-1org-1chaincode-raft-explorer.json.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | // TODO RENAME 4 | const config = "samples/fablo-config-hlf2-1org-1chaincode-raft-explorer.json"; 5 | 6 | describe(config, () => { 7 | performTests(config); 8 | }); 9 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf2-1org-1chaincode.json.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | const config = "samples/fablo-config-hlf2-1org-1chaincode.json"; 4 | 5 | describe(config, () => { 6 | performTests(config); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | const config = "samples/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml"; 4 | 5 | describe(config, () => { 6 | performTests(config); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | const config = "samples/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml"; 4 | 5 | describe(config, () => { 6 | performTests(config); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | const config = "samples/fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json"; 4 | 5 | describe(config, () => { 6 | performTests(config); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf3-1orgs-1chaincode.json.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | const config = "samples/fablo-config-hlf3-1orgs-1chaincode.json"; 4 | 5 | describe(config, () => { 6 | performTests(config); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/fablo-config-hlf3-bft-1orgs-1chaincode.json.test.ts: -------------------------------------------------------------------------------- 1 | import performTests from "./performTests"; 2 | 3 | const config = "samples/fablo-config-hlf3-bft-1orgs-1chaincode.json"; 4 | 5 | describe(config, () => { 6 | performTests(config); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/performTests.ts: -------------------------------------------------------------------------------- 1 | import TestCommands from "./TestCommands"; 2 | import { resolve } from "path"; 3 | 4 | const testFilesExistence = (config: string, files: string[]) => { 5 | it(`should create proper files from ${config}`, () => { 6 | expect(files).toMatchSnapshot(); 7 | }); 8 | }; 9 | 10 | const testFilesContent = (commands: TestCommands, config: string, files: string[]) => 11 | files.forEach((f) => { 12 | it(`should create proper ${f} from ${config}`, () => { 13 | const content = commands.getFileContent(`${commands.relativeRoot}/${f}`); 14 | const rootPath = resolve(__dirname + "/../"); 15 | const cleaned = content 16 | .replace(/FABLO_BUILD=(.*?)(\n|$)/g, "FABLO_BUILD=\n") 17 | .replace(/FABLO_CONFIG=(.*?)(\n|$)/g, "FABLO_CONFIG=\n") 18 | .replace(/CHAINCODES_BASE_DIR=(.*?)(\n|$)/g, "CHAINCODES_BASE_DIR=\n") 19 | .replace(/COMPOSE_PROJECT_NAME=(.*?)(\n|$)/g, "COMPOSE_PROJECT_NAME=\n") 20 | .replace(new RegExp(rootPath, "g"), ""); 21 | expect(cleaned).toMatchSnapshot(); 22 | }); 23 | }); 24 | 25 | export default (config: string): void => { 26 | const commands = new TestCommands(`e2e/__tmp__/${config}.tmpdir`); 27 | commands.cleanupWorkdir(); 28 | commands.fabloExec(`generate "${commands.relativeRoot}/${config}"`); 29 | 30 | const files = commands.getFiles(); 31 | testFilesExistence(config, files); 32 | testFilesContent(commands, config, files); 33 | }; 34 | -------------------------------------------------------------------------------- /e2e/schemaFilesMatch.test.ts: -------------------------------------------------------------------------------- 1 | import { matchers } from "jest-json-schema"; 2 | import TestCommands from "./TestCommands"; 3 | import schema from "../docs/schema.json"; 4 | 5 | expect.extend(matchers); 6 | 7 | const commands = new TestCommands("./e2e/__tmp__/schema-files-match-tests"); 8 | 9 | describe("schema files match", () => { 10 | const files = commands.getFiles("samples/*.json"); 11 | 12 | files.forEach((file) => { 13 | it(file, () => { 14 | // eslint-disable-next-line @typescript-eslint/no-var-requires 15 | const json = require(`../${file}`); 16 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 17 | // @ts-ignore 18 | expect(json).toMatchSchema(schema); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /fablo-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo 4 | 5 | FABLO_HOME="$(cd "$(dirname "$0")" && pwd)" 6 | # shellcheck disable=2002 7 | FABLO_VERSION=$(cat "$FABLO_HOME"/package.json | jq -r '.version') 8 | 9 | COMMIT_HASH=$(git rev-parse --short HEAD) 10 | BUILD_DATE=$(date +'%Y-%m-%d-%H:%M:%S') 11 | VERSION_DETAILS="$BUILD_DATE-$COMMIT_HASH" 12 | 13 | echo "Building new image..." 14 | echo " FABLO_HOME: $FABLO_HOME" 15 | echo " FABLO_VERSION: $FABLO_VERSION" 16 | echo " VERSION_DETAILS: $VERSION_DETAILS" 17 | 18 | IMAGE_BASE_NAME="ghcr.io/fablo-io/fablo:$FABLO_VERSION" 19 | 20 | if [ "$(command -v nvm)" != "nvm" ] && [ -f ~/.nvm/nvm.sh ]; then 21 | set +e 22 | # shellcheck disable=SC2039 23 | source ~/.nvm/nvm.sh 24 | set -e 25 | fi 26 | if [ "$(command -v nvm)" = "nvm" ]; then 27 | set +u 28 | nvm install 29 | set -u 30 | fi 31 | 32 | npm install 33 | npm run build:dist 34 | 35 | # if --push is passed, then build for all platforms and push the image to the registry 36 | if [ "${1:-''}" = "--push" ]; then 37 | docker buildx build \ 38 | --build-arg VERSION_DETAILS="$VERSION_DETAILS" \ 39 | --platform linux/amd64,linux/arm64 \ 40 | --tag "ghcr.io/fablo-io/fablo:$FABLO_VERSION" \ 41 | --push \ 42 | "$FABLO_HOME" 43 | else 44 | docker build \ 45 | --build-arg VERSION_DETAILS="$VERSION_DETAILS" \ 46 | --tag "$IMAGE_BASE_NAME" "$FABLO_HOME" 47 | 48 | docker tag "$IMAGE_BASE_NAME" "ghcr.io/fablo-io/fablo:$FABLO_VERSION" 49 | fi -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | }; 4 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script runs linter for bash and YAML files in Fablo root and for 4 | # generated network configs in 'e2e/__tmp__' directory. It fails if generated 5 | # network configs are missing. 6 | # 7 | # Required libs: shellcheck and yamllint 8 | 9 | set -e 10 | 11 | FABLO_HOME="$(dirname "$0")" 12 | shellcheck "$FABLO_HOME"/*.sh 13 | shellcheck "$FABLO_HOME"/e2e-network/docker/*.sh 14 | shellcheck "$FABLO_HOME"/e2e-network/k8s/*.sh 15 | 16 | for config in samples/fablo-config-*; do 17 | network="$FABLO_HOME/e2e/__tmp__/${config}.tmpdir" 18 | 19 | if [ -z "$(ls -A "$network")" ]; then 20 | echo "Missing network $network" 21 | exit 1 22 | fi 23 | 24 | echo "Linting network $network" 25 | 26 | # shellcheck disable=2044 27 | for file in $(find "$network" -name "*.sh"); do 28 | shellcheck "$file" 29 | done 30 | 31 | yamllint "$network" 32 | 33 | done 34 | -------------------------------------------------------------------------------- /logo-sygnet-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-labs/fablo/683b8991a72c6731162d1a55383c00d8bf32d259/logo-sygnet-192.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-fablo", 3 | "version": "2.2.0", 4 | "description": "Fablo is a simple tool to generate the Hyperledger Fabric blockchain network and run it on Docker. It supports RAFT and solo consensus protocols, multiple organizations and channels, chaincode installation and upgrade.", 5 | "author": "Piotr Hejwowski , Jakub Dzikowski ", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/hyperledger-labs/fablo.git" 9 | }, 10 | "keywords": [ 11 | "hyperledger fabric", 12 | "blockchain", 13 | "blockchain network", 14 | "config generator", 15 | "CI" 16 | ], 17 | "license": "Apache-2.0", 18 | "bugs": { 19 | "url": "https://github.com/hyperledger-labs/fablo/issues" 20 | }, 21 | "homepage": "https://github.com/hyperledger-labs/fablo#readme", 22 | "scripts": { 23 | "clean": "rimraf generators", 24 | "build": "tsc", 25 | "build:dist": "npm run clean && tsc -p tsconfig-dist.json && npm run copydeps", 26 | "copydeps": "copyfiles --all --up 1 'src/*/templates/**' generators", 27 | "lint": "eslint --fix src e2e && madge --circular --warning src e2e && ejslint src", 28 | "test:unit": "jest src", 29 | "test:e2e": "jest e2e --runInBand", 30 | "test:e2e-update": "./fablo-build.sh && jest e2e --runInBand --updateSnapshot && ./lint.sh" 31 | }, 32 | "dependencies": { 33 | "chalk": "^4.1.0", 34 | "got": "^11.8.5", 35 | "js-yaml": "^4.1.0", 36 | "jsonschema": "^1.2.6", 37 | "lodash": "^4.17.21", 38 | "winston": "^2.4.7", 39 | "yeoman-generator": "^5.10.0" 40 | }, 41 | "devDependencies": { 42 | "@types/jest": "^27.0.1", 43 | "@types/jest-json-schema": "^2.1.3", 44 | "@types/js-yaml": "^4.0.1", 45 | "@types/lodash": "^4.14.168", 46 | "@types/node-fetch": "^2.5.10", 47 | "@types/yeoman-generator": "^5.2.14", 48 | "@typescript-eslint/eslint-plugin": "^4.22.0", 49 | "@typescript-eslint/parser": "^4.22.0", 50 | "copyfiles": "2.4.1", 51 | "ejs-lint": "^2.0.1", 52 | "eslint": "^7.24.0", 53 | "eslint-config-airbnb": "^18.2.1", 54 | "eslint-config-prettier": "^8.2.0", 55 | "eslint-import-resolver-typescript": "^2.4.0", 56 | "eslint-plugin-import": "^2.22.1", 57 | "eslint-plugin-json": "^2.1.2", 58 | "eslint-plugin-prettier": "^3.4.0", 59 | "jest": "^29.7.0", 60 | "jest-json-schema": "^6.1.0", 61 | "madge": "^4.0.2", 62 | "prettier": "^2.2.1", 63 | "rimraf": "^3.0.2", 64 | "ts-jest": "^29.1.4", 65 | "ts-loader": "^9.1.0", 66 | "ts-node": "^9.1.1", 67 | "typescript": "^4.2.4", 68 | "yeoman-test": "^5.1.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /samples/chaincodes/.gitignore: -------------------------------------------------------------------------------- 1 | */node_modules 2 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/.fabricignore: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache2.0 3 | # 4 | 5 | /.classpath 6 | /.git/ 7 | /.gradle/ 8 | /.project 9 | /.settings/ 10 | /bin/ 11 | /build/ 12 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache2.0 3 | # 4 | 5 | /.classpath 6 | /.gradle/ 7 | /.project 8 | /.settings/ 9 | /bin/ 10 | /build/ 11 | /target 12 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache2.0 3 | */ 4 | plugins { 5 | id 'com.github.johnrengelman.shadow' version '7.0.0' 6 | id 'java' 7 | } 8 | 9 | version '0.0.1' 10 | 11 | sourceCompatibility = 1.8 12 | 13 | repositories { 14 | mavenLocal() 15 | mavenCentral() 16 | maven { 17 | url 'https://jitpack.io' 18 | } 19 | maven { 20 | url "https://nexus.hyperledger.org/content/repositories/snapshots/" 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation group: 'org.hyperledger.fabric-chaincode-java', name: 'fabric-chaincode-shim', version: '2.3.0' 26 | implementation group: 'org.json', name: 'json', version: '20180813' 27 | testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' 28 | testImplementation 'org.assertj:assertj-core:3.11.1' 29 | testImplementation 'org.mockito:mockito-core:2.+' 30 | } 31 | 32 | shadowJar { 33 | baseName = 'chaincode' 34 | version = null 35 | classifier = null 36 | 37 | manifest { 38 | attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter' 39 | } 40 | } 41 | 42 | test { 43 | useJUnitPlatform() 44 | testLogging { 45 | events "passed", "skipped", "failed" 46 | } 47 | } 48 | 49 | 50 | tasks.withType(JavaCompile) { 51 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-parameters" 52 | } 53 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-labs/fablo/683b8991a72c6731162d1a55383c00d8bf32d259/samples/chaincodes/chaincode-java-simple/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache2.0 3 | */ 4 | rootProject.name = 'PokeballContract' 5 | 6 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/src/main/java/org/example/Pokeball.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache2.0 3 | */ 4 | 5 | package org.example; 6 | 7 | import org.hyperledger.fabric.contract.annotation.DataType; 8 | import org.hyperledger.fabric.contract.annotation.Property; 9 | import org.json.JSONObject; 10 | 11 | @DataType() 12 | public class Pokeball { 13 | 14 | @Property() 15 | private String value; 16 | 17 | public Pokeball(){ 18 | } 19 | 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | public void setValue(String value) { 25 | this.value = value; 26 | } 27 | 28 | public String toJSONString() { 29 | return new JSONObject(this).toString(); 30 | } 31 | 32 | public static Pokeball fromJSONString(String json) { 33 | String value = new JSONObject(json).getString("value"); 34 | Pokeball asset = new Pokeball(); 35 | asset.setValue(value); 36 | return asset; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-java-simple/src/main/java/org/example/PokeballContract.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache2.0 3 | */ 4 | package org.example; 5 | 6 | import org.hyperledger.fabric.contract.Context; 7 | import org.hyperledger.fabric.contract.ContractInterface; 8 | import org.hyperledger.fabric.contract.annotation.Contract; 9 | import org.hyperledger.fabric.contract.annotation.Default; 10 | import org.hyperledger.fabric.contract.annotation.Transaction; 11 | import org.hyperledger.fabric.contract.annotation.Contact; 12 | import org.hyperledger.fabric.contract.annotation.Info; 13 | import org.hyperledger.fabric.contract.annotation.License; 14 | import static java.nio.charset.StandardCharsets.UTF_8; 15 | 16 | @Contract(name = "PokeballContract", 17 | info = @Info(title = "Pokeball contract", 18 | description = "Pokeball implementation", 19 | version = "0.0.1", 20 | license = 21 | @License(name = "Apache2.0", 22 | url = ""), 23 | contact = @Contact(email = "PokeballContract@example.com", 24 | name = "PokeballContract", 25 | url = "http://PokeballContract.me"))) 26 | @Default 27 | public class PokeballContract implements ContractInterface { 28 | public PokeballContract() { 29 | 30 | } 31 | @Transaction() 32 | public boolean pokeballExists(Context ctx, String pokeballId) { 33 | byte[] buffer = ctx.getStub().getState(pokeballId); 34 | return (buffer != null && buffer.length > 0); 35 | } 36 | 37 | @Transaction() 38 | public void createPokeball(Context ctx, String pokeballId, String value) { 39 | boolean exists = pokeballExists(ctx,pokeballId); 40 | if (exists) { 41 | throw new RuntimeException("The asset "+pokeballId+" already exists"); 42 | } 43 | Pokeball asset = new Pokeball(); 44 | asset.setValue(value); 45 | ctx.getStub().putState(pokeballId, asset.toJSONString().getBytes(UTF_8)); 46 | } 47 | 48 | @Transaction() 49 | public Pokeball readPokeball(Context ctx, String pokeballId) { 50 | boolean exists = pokeballExists(ctx,pokeballId); 51 | if (!exists) { 52 | throw new RuntimeException("The asset "+pokeballId+" does not exist"); 53 | } 54 | 55 | Pokeball newAsset = Pokeball.fromJSONString(new String(ctx.getStub().getState(pokeballId),UTF_8)); 56 | return newAsset; 57 | } 58 | 59 | @Transaction() 60 | public void updatePokeball(Context ctx, String pokeballId, String newValue) { 61 | boolean exists = pokeballExists(ctx,pokeballId); 62 | if (!exists) { 63 | throw new RuntimeException("The asset "+pokeballId+" does not exist"); 64 | } 65 | Pokeball asset = new Pokeball(); 66 | asset.setValue(newValue); 67 | 68 | ctx.getStub().putState(pokeballId, asset.toJSONString().getBytes(UTF_8)); 69 | } 70 | 71 | @Transaction() 72 | public void deletePokeball(Context ctx, String pokeballId) { 73 | boolean exists = pokeballExists(ctx,pokeballId); 74 | if (!exists) { 75 | throw new RuntimeException("The asset "+pokeballId+" does not exist"); 76 | } 77 | ctx.getStub().delState(pokeballId); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-kv-node/.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-kv-node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | RUN npm install 7 | 8 | COPY . . 9 | 10 | EXPOSE 7052 11 | 12 | CMD ["npm", "run", "start:ccaas"] 13 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-kv-node/index.js: -------------------------------------------------------------------------------- 1 | const { Contract } = require("fabric-contract-api"); 2 | const crypto = require("crypto"); 3 | 4 | class KVContract extends Contract { 5 | constructor() { 6 | super("KVContract"); 7 | } 8 | 9 | async instantiate() { 10 | // function that will be invoked on chaincode instantiation 11 | } 12 | 13 | async put(ctx, key, value) { 14 | await ctx.stub.putState(key, Buffer.from(value)); 15 | return { success: "OK" }; 16 | } 17 | 18 | async get(ctx, key) { 19 | const buffer = await ctx.stub.getState(key); 20 | if (!buffer || !buffer.length) return { error: "NOT_FOUND" }; 21 | return { success: buffer.toString() }; 22 | } 23 | 24 | async putPrivateMessage(ctx, collection) { 25 | const transient = ctx.stub.getTransient(); 26 | const message = transient.get("message"); 27 | await ctx.stub.putPrivateData(collection, "message", message); 28 | return { success: "OK" }; 29 | } 30 | 31 | async getPrivateMessage(ctx, collection) { 32 | const message = await ctx.stub.getPrivateData(collection, "message"); 33 | const messageString = message.toBuffer ? message.toBuffer().toString() : message.toString(); 34 | return { success: messageString }; 35 | } 36 | 37 | async verifyPrivateMessage(ctx, collection) { 38 | const transient = ctx.stub.getTransient(); 39 | const message = transient.get("message"); 40 | const messageString = message.toBuffer ? message.toBuffer().toString() : message.toString(); 41 | const currentHash = crypto.createHash("sha256").update(messageString).digest("hex"); 42 | const privateDataHash = (await ctx.stub.getPrivateDataHash(collection, "message")).toString("hex"); 43 | if (privateDataHash !== currentHash) { 44 | return { error: "VERIFICATION_FAILED" }; 45 | } 46 | return { success: "OK" }; 47 | } 48 | } 49 | 50 | exports.contracts = [KVContract]; 51 | -------------------------------------------------------------------------------- /samples/chaincodes/chaincode-kv-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chaincode-kv-node", 3 | "version": "0.2.0", 4 | "main": "index.js", 5 | "engines": { 6 | "node": ">=8", 7 | "npm": ">=5" 8 | }, 9 | "scripts": { 10 | "start": "fabric-chaincode-node start", 11 | "start:ccaas": "fabric-chaincode-node server --chaincode-address 0.0.0.0:7052 --chaincode-id \"chaincode1:0.0.1\"", 12 | "start:dev": "fabric-chaincode-node start --peer.address \"127.0.0.1:8541\" --chaincode-id-name \"chaincode1:0.0.1\" --tls.enabled false", 13 | "start:watch": "nodemon --exec \"npm run start:dev\"", 14 | "build": "echo \"No need to build the chaincode\"", 15 | "lint": "eslint . --fix --ext .js" 16 | }, 17 | "dependencies": { 18 | "fabric-contract-api": "2.4.2", 19 | "fabric-shim": "2.4.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf2-1org-1chaincode-k8s.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 3 | "global": { 4 | "fabricVersion": "2.4.7", 5 | "tls": false, 6 | "engine": "kubernetes" 7 | }, 8 | "orgs": [ 9 | { 10 | "organization": { 11 | "name": "Orderer", 12 | "domain": "orderer.example.com" 13 | }, 14 | "orderers": [ 15 | { 16 | "groupName": "group1", 17 | "type": "solo", 18 | "instances": 1 19 | } 20 | ] 21 | }, 22 | { 23 | "organization": { 24 | "name": "Org1", 25 | "domain": "org1.example.com" 26 | }, 27 | "peer": { 28 | "instances": 2, 29 | "db": "LevelDb" 30 | } 31 | } 32 | ], 33 | "channels": [ 34 | { 35 | "name": "my-channel1", 36 | "orgs": [ 37 | { 38 | "name": "Org1", 39 | "peers": [ 40 | "peer0", 41 | "peer1" 42 | ] 43 | } 44 | ] 45 | } 46 | ], 47 | "chaincodes": [ 48 | { 49 | "name": "chaincode1", 50 | "version": "0.0.1", 51 | "lang": "node", 52 | "channel": "my-channel1", 53 | "directory": "./chaincodes/chaincode-kv-node" 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf2-1org-1chaincode-raft-explorer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 3 | "global": { 4 | "fabricVersion": "2.3.3", 5 | "tls": true, 6 | "tools": { 7 | "explorer": true 8 | } 9 | }, 10 | "orgs": [ 11 | { 12 | "organization": { 13 | "name": "Orderer", 14 | "domain": "orderer.example.com" 15 | }, 16 | "orderers": [ 17 | { 18 | "groupName": "group1", 19 | "type": "raft", 20 | "instances": 1 21 | } 22 | ] 23 | }, 24 | { 25 | "organization": { 26 | "name": "Org1", 27 | "domain": "org1.example.com" 28 | }, 29 | "ca": { 30 | "db": "postgres" 31 | }, 32 | "peer": { 33 | "instances": 1, 34 | "db": "CouchDb" 35 | }, 36 | "tools": { 37 | "fabloRest": true 38 | } 39 | } 40 | ], 41 | "channels": [ 42 | { 43 | "name": "my-channel1", 44 | "orgs": [ 45 | { 46 | "name": "Org1", 47 | "peers": [ 48 | "peer0" 49 | ] 50 | } 51 | ] 52 | } 53 | ], 54 | "chaincodes": [ 55 | { 56 | "name": "chaincode1", 57 | "version": "0.0.1", 58 | "lang": "node", 59 | "channel": "my-channel1", 60 | "directory": "./chaincodes/chaincode-kv-node" 61 | } 62 | ], 63 | "hooks": { 64 | "postGenerate": "perl -i -pe 's/MaxMessageCount: 10/MaxMessageCount: 1/g' \"./fablo-target/fabric-config/configtx.yaml\"" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf2-1org-1chaincode.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 3 | "global": { 4 | "fabricVersion": "2.5.9", 5 | "tls": false 6 | }, 7 | "orgs": [ 8 | { 9 | "organization": { 10 | "name": "Orderer", 11 | "domain": "orderer.example.com" 12 | }, 13 | "orderers": [ 14 | { 15 | "groupName": "group1", 16 | "type": "solo", 17 | "instances": 1 18 | } 19 | ] 20 | }, 21 | { 22 | "organization": { 23 | "name": "Org1", 24 | "domain": "org1.example.com" 25 | }, 26 | "peer": { 27 | "instances": 2, 28 | "db": "LevelDb" 29 | } 30 | } 31 | ], 32 | "channels": [ 33 | { 34 | "name": "my-channel1", 35 | "orgs": [ 36 | { 37 | "name": "Org1", 38 | "peers": [ 39 | "peer0", 40 | "peer1" 41 | ] 42 | } 43 | ] 44 | } 45 | ], 46 | "chaincodes": [ 47 | { 48 | "name": "chaincode1", 49 | "version": "0.0.1", 50 | "lang": "node", 51 | "channel": "my-channel1", 52 | "directory": "./chaincodes/chaincode-kv-node" 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | "$schema": https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json 3 | global: 4 | fabricVersion: 2.4.7 5 | tls: false 6 | 7 | orgs: 8 | - organization: 9 | name: Orderer 10 | domain: orderer.example.com 11 | orderers: 12 | - groupName: group1 13 | prefix: orderer 14 | type: solo 15 | instances: 1 16 | - organization: 17 | name: Org1 18 | domain: org1.example.com 19 | peer: 20 | instances: 2 21 | - organization: 22 | name: Org2 23 | domain: org2.example.com 24 | peer: 25 | instances: 1 26 | channels: 27 | - name: my-channel1 28 | orgs: 29 | - name: Org1 30 | peers: 31 | - peer0 32 | - peer1 33 | - name: Org2 34 | peers: 35 | - peer0 36 | chaincodes: 37 | - name: or-policy-chaincode 38 | version: 0.0.1 39 | lang: node 40 | channel: my-channel1 41 | init: '{"Args":[]}' 42 | endorsement: OR('Org1MSP.member', 'Org2MSP.member') 43 | directory: "./chaincodes/chaincode-kv-node" 44 | privateData: 45 | - name: org1-collection 46 | orgNames: 47 | - Org1 48 | - name: and-policy-chaincode 49 | version: 0.0.1 50 | lang: node 51 | channel: my-channel1 52 | endorsement: AND('Org1MSP.member', 'Org2MSP.member') 53 | directory: "./chaincodes/chaincode-kv-node" 54 | privateData: 55 | - name: both-orgs-collection 56 | orgNames: 57 | - Org1 58 | - Org2 59 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | "$schema": https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json 3 | global: 4 | fabricVersion: 2.4.3 5 | tls: true 6 | monitoring: 7 | loglevel: debug 8 | orgs: 9 | - organization: 10 | name: Orderer1 11 | domain: orderer1.com 12 | orderers: 13 | - groupName: group1 14 | type: raft 15 | instances: 3 16 | - organization: 17 | name: Orderer2 18 | domain: orderer2.com 19 | orderers: 20 | - groupName: group2 21 | type: solo 22 | instances: 1 23 | - organization: 24 | name: Org1 25 | domain: org1.example.com 26 | # this is the default configuration for peers that may be used in other orgs 27 | peer: &defaultPeerConfig 28 | prefix: peer 29 | instances: 2 30 | anchorPeerInstances: 2 31 | db: LevelDb 32 | tools: 33 | fabloRest: true 34 | - organization: 35 | name: Org2 36 | domain: org2.example.com 37 | peer: *defaultPeerConfig 38 | channels: 39 | - name: my-channel1 40 | orgs: 41 | - name: Org1 42 | peers: 43 | - peer0 44 | - name: Org2 45 | peers: 46 | - peer0 47 | - name: my-channel2 48 | orgs: 49 | - name: Org1 50 | peers: 51 | - peer1 52 | - name: Org2 53 | peers: 54 | - peer1 55 | - name: my-channel3 56 | ordererGroup: group2 57 | orgs: 58 | - name: Org1 59 | peers: 60 | - peer0 61 | - name: Org2 62 | peers: 63 | - peer1 64 | chaincodes: 65 | - name: chaincode1 66 | version: 0.0.1 67 | lang: node 68 | channel: my-channel1 69 | endorsement: OR ('Org1MSP.member', 'Org2MSP.member') 70 | directory: "./chaincodes/chaincode-kv-node" 71 | - name: chaincode2 72 | version: 0.0.1 73 | lang: java 74 | channel: my-channel2 75 | endorsement: OR ('Org1MSP.member', 'Org2MSP.member') 76 | directory: "./chaincodes/chaincode-java-simple" 77 | hooks: 78 | # changes MaxMessageCount to 1 79 | postGenerate: "perl -i -pe 's/MaxMessageCount: 10/MaxMessageCount: 1/g' \"./fablo-target/fabric-config/configtx.yaml\"" 80 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 3 | "global": { 4 | "fabricVersion": "2.3.2", 5 | "tls": true, 6 | "tools": { 7 | "explorer": true 8 | } 9 | }, 10 | "orgs": [ 11 | { 12 | "organization": { 13 | "name": "Orderer", 14 | "domain": "orderer.example.com" 15 | }, 16 | "orderers": [ 17 | { 18 | "groupName": "group1", 19 | "type": "raft", 20 | "instances": 2 21 | } 22 | ] 23 | }, 24 | { 25 | "organization": { 26 | "name": "Org1", 27 | "domain": "org1.example.com" 28 | }, 29 | "peer": { 30 | "instances": 2, 31 | "db": "LevelDb" 32 | }, 33 | "orderers": [ 34 | { 35 | "groupName": "group1", 36 | "type": "raft", 37 | "instances": 2 38 | } 39 | ] 40 | }, 41 | { 42 | "organization": { 43 | "name": "Org2", 44 | "domain": "org2.example.com" 45 | }, 46 | "peer": { 47 | "instances": 2, 48 | "db": "LevelDb" 49 | }, 50 | "orderers": [ 51 | { 52 | "groupName": "group1", 53 | "type": "raft", 54 | "instances": 2 55 | } 56 | ] 57 | }, 58 | { 59 | "organization": { 60 | "name": "Org3", 61 | "domain": "org3.example.com" 62 | }, 63 | "peer": { 64 | "instances": 2, 65 | "db": "LevelDb" 66 | }, 67 | "orderers": [ 68 | { 69 | "groupName": "group1", 70 | "type": "raft", 71 | "instances": 2 72 | } 73 | ] 74 | } 75 | ], 76 | "channels": [ 77 | { 78 | "name": "my-channel1", 79 | "orgs": [ 80 | { 81 | "name": "Org1", 82 | "peers": [ 83 | "peer0", 84 | "peer1" 85 | ] 86 | } 87 | ] 88 | }, 89 | { 90 | "name": "my-channel2", 91 | "orgs": [ 92 | { 93 | "name": "Org2", 94 | "peers": [ 95 | "peer0", 96 | "peer1" 97 | ] 98 | } 99 | ] 100 | }, 101 | { 102 | "name": "my-channel3", 103 | "orgs": [ 104 | { 105 | "name": "Org1", 106 | "peers": [ 107 | "peer0", 108 | "peer1" 109 | ] 110 | }, 111 | { 112 | "name": "Org2", 113 | "peers": [ 114 | "peer0", 115 | "peer1" 116 | ] 117 | } 118 | ] 119 | } 120 | ], 121 | "chaincodes": [ 122 | { 123 | "name": "chaincode1", 124 | "version": "0.0.1", 125 | "lang": "node", 126 | "channel": "my-channel1", 127 | "directory": "./chaincodes/chaincode-kv-node" 128 | } 129 | ] 130 | } 131 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf3-1orgs-1chaincode.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 3 | "global": { 4 | "fabricVersion": "3.0.0", 5 | "tls": true, 6 | "monitoring": { 7 | "loglevel": "debug" 8 | } 9 | }, 10 | "orgs": [ 11 | { 12 | "organization": { 13 | "name": "Orderer", 14 | "domain": "orderer.example.com" 15 | }, 16 | "orderers": [ 17 | { 18 | "groupName": "group1", 19 | "type": "raft", 20 | "instances": 4 21 | } 22 | ] 23 | }, 24 | { 25 | "organization": { 26 | "name": "Org1", 27 | "domain": "org1.example.com" 28 | }, 29 | "peer": { 30 | "instances": 2, 31 | "db": "LevelDb" 32 | } 33 | } 34 | ], 35 | "channels": [ 36 | { 37 | "name": "my-channel1", 38 | "orgs": [ 39 | { 40 | "name": "Org1", 41 | "peers": [ 42 | "peer0", 43 | "peer1" 44 | ] 45 | } 46 | ] 47 | } 48 | ], 49 | "chaincodes": [ 50 | { 51 | "name": "chaincode1", 52 | "version": "0.0.1", 53 | "lang": "node", 54 | "channel": "my-channel1", 55 | "directory": "./chaincodes/chaincode-kv-node" 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /samples/fablo-config-hlf3-bft-1orgs-1chaincode.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 3 | "global": { 4 | "fabricVersion": "3.0.0-beta", 5 | "tls": true, 6 | "monitoring": { 7 | "loglevel": "debug" 8 | } 9 | }, 10 | "orgs": [ 11 | { 12 | "organization": { 13 | "name": "Orderer", 14 | "domain": "orderer.example.com" 15 | }, 16 | "orderers": [ 17 | { 18 | "groupName": "group1", 19 | "type": "BFT", 20 | "instances": 4 21 | } 22 | 23 | ] 24 | }, 25 | { 26 | "organization": { 27 | "name": "Org1", 28 | "domain": "org1.example.com" 29 | }, 30 | "peer": { 31 | "instances": 2, 32 | "db": "LevelDb" 33 | } 34 | } 35 | ], 36 | "channels": [ 37 | { 38 | "name": "my-channel1", 39 | "orgs": [ 40 | { 41 | "name": "Org1", 42 | "peers": [ 43 | "peer0", 44 | "peer1" 45 | ] 46 | } 47 | ] 48 | } 49 | ], 50 | "chaincodes": [ 51 | { 52 | "name": "chaincode1", 53 | "version": "0.0.1", 54 | "lang": "node", 55 | "channel": "my-channel1", 56 | "directory": "./chaincodes/chaincode-kv-node" 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /samples/gateway/node/.env.example: -------------------------------------------------------------------------------- 1 | CHANNEL_NAME=my-channel1 2 | CONTRACT_NAME=chaincode1 3 | PEER_GATEWAY_URL=localhost:7041 4 | PEER_ORG_NAME=peer0.org1.example.com 5 | MSP_ID=Org1MSP 6 | TLS_ROOT_CERT=../../../fablo-target/fabric-config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt 7 | CREDENTIALS=../../../fablo-target/fabric-config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem 8 | PRIVATE_KEY_PEM=../../../fablo-target/fabric-config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv-key.pem 9 | 10 | -------------------------------------------------------------------------------- /samples/gateway/node/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | To provide an example connection of Fablo with Node.js. 3 | 4 | # Pre-requisites 5 | Docker 6 | 7 | Node >22 8 | 9 | Git 10 | 11 | # Instructions 12 | 1. (If Fablo is not already installed) Clone the Fablo repo with `https://github.com/hyperledger-labs/fablo.git` and then `cd fablo`. 13 | 2. Start Docker. 14 | 3. Run `fablo up samples/fablo-config-hlf3-1orgs-1chaincode.json`. 15 | 4. Once Fablo is running, run `cd samples/gateway/node`. 16 | 5. Now install the Node server's dependencies with `npm i`. 17 | 6. Now let's copy the environment example to a usable file `cp .env.example .env`. 18 | 7. Start the node server with `node --env-file=.env server.js`. 19 | 20 | You should see a response like this: 21 | ``` 22 | Put result: {"success":"OK"} 23 | Get result: {"success":"2025-04-29T16:13:42.097Z"}` 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /samples/gateway/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-fablo-gateway", 3 | "version": "1.0.0", 4 | "description": "", 5 | "license": "ISC", 6 | "author": "", 7 | "main": "server.js", 8 | "type": "module", 9 | "engines" : { 10 | "node" : ">=22" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "start": "node server.js" 15 | }, 16 | "dependencies": { 17 | "@grpc/grpc-js": "^1.13.3", 18 | "@hyperledger/fabric-gateway": "^1.7.1" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /samples/gateway/node/server.js: -------------------------------------------------------------------------------- 1 | import * as grpc from '@grpc/grpc-js'; 2 | import { connect, hash, signers } from '@hyperledger/fabric-gateway'; 3 | import * as crypto from 'node:crypto'; 4 | import { promises as fs } from 'node:fs'; 5 | import { TextDecoder } from 'node:util'; 6 | 7 | const utf8Decoder = new TextDecoder(); 8 | 9 | async function connection() { 10 | const credentials = await fs.readFile(process.env.CREDENTIALS); 11 | const privateKeyPem = await fs.readFile(process.env.PRIVATE_KEY_PEM); 12 | const privateKey = crypto.createPrivateKey(privateKeyPem); 13 | const signer = signers.newPrivateKeySigner(privateKey); 14 | const tlsRootCert = await fs.readFile( 15 | process.env.TLS_ROOT_CERT, 16 | ); 17 | const client = new grpc.Client(process.env.PEER_GATEWAY_URL, grpc.credentials.createSsl(tlsRootCert), { 18 | "grpc.ssl_target_name_override": process.env.PEER_ORG_NAME, 19 | }); 20 | const gateway = connect({ 21 | identity: { mspId: process.env.MSP_ID, credentials }, 22 | signer, 23 | hash: hash.sha256, 24 | client, 25 | }); 26 | try { 27 | const network = gateway.getNetwork(process.env.CHANNEL_NAME); 28 | const contract = network.getContract(process.env.CONTRACT_NAME); 29 | const putResult = await contract.submitTransaction('put', 'time', new Date().toISOString()); 30 | console.log('Put result:', utf8Decoder.decode(putResult)); 31 | const getResult = await contract.evaluateTransaction('get', 'time'); 32 | console.log('Get result:', utf8Decoder.decode(getResult)); 33 | } finally { 34 | gateway.close(); 35 | client.close(); 36 | } 37 | } 38 | 39 | connection().catch(console.error); 40 | 41 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | 3 | export default class extends Generator { 4 | displayInfo(): void { 5 | const url = "https://github.com/hyperledger-labs/fablo"; 6 | console.log("This is main entry point for Yeoman app used in Fablo."); 7 | console.log("Visit the project page to get more information."); 8 | console.log(`---\n${url}\n---`); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/config.test.ts: -------------------------------------------------------------------------------- 1 | import { fabloVersion, getVersionFromSchemaUrl } from "./config"; 2 | 3 | it("should get version from schema URL", () => { 4 | const url = "https://github.com/hyperledger-labs/fablo/releases/download/0.0.1/schema.json"; 5 | const version = getVersionFromSchemaUrl(url); 6 | expect(version).toEqual("0.0.1"); 7 | }); 8 | 9 | it("should get current version in case of missing schema URL", () => { 10 | const version = getVersionFromSchemaUrl(undefined); 11 | expect(version).toEqual(fabloVersion); 12 | }); 13 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 2 | // @ts-ignore 3 | import { version as fabloVersion } from "../package.json"; 4 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 5 | // @ts-ignore 6 | import * as schemaJson from "../docs/schema.json"; 7 | import { Schema } from "jsonschema"; 8 | import { version } from "./repositoryUtils"; 9 | 10 | const schema: Schema = schemaJson as Schema; 11 | 12 | const supportedVersionPrefix = `${fabloVersion.split(".").slice(0, 2).join(".")}.`; 13 | 14 | const getVersionFromSchemaUrl = (url?: string): string => { 15 | const matches = (url || "").match(/\d+\.\d+\.\d+/g); 16 | return matches?.length ? matches[0] : fabloVersion; 17 | }; 18 | 19 | const isFabloVersionSupported = (versionName: string): boolean => versionName.startsWith(supportedVersionPrefix); 20 | 21 | const versionsSupportingRaft = (v: string): boolean => version(v).isGreaterOrEqual("1.4.3"); 22 | 23 | export { 24 | schema, 25 | fabloVersion, 26 | versionsSupportingRaft, 27 | getVersionFromSchemaUrl, 28 | isFabloVersionSupported, 29 | supportedVersionPrefix, 30 | }; 31 | -------------------------------------------------------------------------------- /src/extend-config/defaults.ts: -------------------------------------------------------------------------------- 1 | import { Capabilities, OrdererGroup, OrgConfig } from "../types/FabloConfigExtended"; 2 | 3 | export default { 4 | global: { 5 | monitoring: { 6 | loglevel: "info", 7 | }, 8 | tools: { 9 | explorer: false, 10 | }, 11 | }, 12 | organization: { 13 | mspName: (name: string): string => `${name}MSP`, 14 | }, 15 | orderer: { 16 | prefix: "orderer", 17 | }, 18 | ca: { 19 | prefix: "ca", 20 | db: "sqlite", 21 | }, 22 | peer: { 23 | prefix: "peer", 24 | db: "LevelDb", 25 | anchorPeerInstances: (peerCount: number): number => peerCount, 26 | }, 27 | channel: { 28 | ordererGroup: (ordererGroups: OrdererGroup[]): string => ordererGroups[0].name, 29 | }, 30 | chaincode: { 31 | init: '{"Args":[]}', 32 | initRequired: false, 33 | endorsement(orgs: OrgConfig[], capabilities: Capabilities): string | undefined { 34 | return capabilities.isV2 ? undefined : `AND (${orgs.map((o) => `'${o.mspName}.member'`).join(", ")})`; 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/extend-config/extendChaincodesConfig.ts: -------------------------------------------------------------------------------- 1 | import { ChaincodeJson } from "../types/FabloConfigJson"; 2 | import { ChaincodeConfig, ChannelConfig, Global, PrivateCollectionConfig } from "../types/FabloConfigExtended"; 3 | import defaults from "./defaults"; 4 | import { version } from "../repositoryUtils"; 5 | 6 | const createPrivateCollectionConfig = ( 7 | fabricVersion: string, 8 | channel: ChannelConfig, 9 | name: string, 10 | collectionOrgNames: string[], 11 | ): PrivateCollectionConfig => { 12 | // Organizations that will host the actual data 13 | const collectionOrgs = (channel.orgs || []).filter((o) => !!collectionOrgNames.find((n) => n === o.name)); 14 | if (collectionOrgs.length < collectionOrgNames.length) { 15 | throw new Error(`Cannot find all orgs for names ${collectionOrgNames}`); 16 | } 17 | const policy = `OR(${collectionOrgs.map((o) => `'${o.mspName}.member'`).join(",")})`; 18 | 19 | // We need to know the number of anchor peers per org in a channel to determine the following parameters: 20 | // - maxPeerCount -> all peers 21 | // - requiredPeerCount -> minimal number of anchor peers from one organization in a channel 22 | const anchorPeerCountsInChannel = channel.orgs.map((o) => (o.anchorPeers || []).length); 23 | const maxPeerCount = anchorPeerCountsInChannel.reduce((a, b) => a + b, 0); 24 | const requiredPeerCount = anchorPeerCountsInChannel.reduce((a, b) => Math.min(a, b), maxPeerCount); 25 | 26 | const memberOnlyRead = version(fabricVersion).isGreaterOrEqual("1.4.0") ? { memberOnlyRead: true } : {}; 27 | const memberOnlyWrite = version(fabricVersion).isGreaterOrEqual("2.0.0") ? { memberOnlyWrite: true } : {}; 28 | 29 | return { 30 | name, 31 | policy, 32 | requiredPeerCount, 33 | maxPeerCount, 34 | blockToLive: 0, 35 | ...memberOnlyRead, 36 | ...memberOnlyWrite, 37 | }; 38 | }; 39 | 40 | const extendChaincodesConfig = ( 41 | chaincodes: ChaincodeJson[], 42 | transformedChannels: ChannelConfig[], 43 | network: Global, 44 | ): ChaincodeConfig[] => { 45 | return chaincodes.map((chaincode) => { 46 | const channel = transformedChannels.find((c) => c.name === chaincode.channel); 47 | if (!channel) throw new Error(`No matching channel with name '${chaincode.channel}'`); 48 | 49 | const initParams: { initRequired: boolean } | { init: string } = network.capabilities.isV2 50 | ? { initRequired: chaincode.initRequired || defaults.chaincode.initRequired } 51 | : { init: chaincode.init || defaults.chaincode.init }; 52 | 53 | const endorsement = chaincode.endorsement ?? defaults.chaincode.endorsement(channel.orgs, network.capabilities); 54 | 55 | const privateData = (chaincode.privateData ?? []).map((d) => 56 | createPrivateCollectionConfig(network.fabricVersion, channel, d.name, d.orgNames), 57 | ); 58 | const privateDataConfigFile = privateData.length > 0 ? `collections/${chaincode.name}.json` : undefined; 59 | 60 | return { 61 | directory: chaincode.directory, 62 | name: chaincode.name, 63 | version: chaincode.version, 64 | lang: chaincode.lang, 65 | channel, 66 | ...initParams, 67 | endorsement, 68 | instantiatingOrg: channel.instantiatingOrg, 69 | privateDataConfigFile, 70 | privateData, 71 | }; 72 | }); 73 | }; 74 | 75 | export default extendChaincodesConfig; 76 | -------------------------------------------------------------------------------- /src/extend-config/extendChannelsConfig.ts: -------------------------------------------------------------------------------- 1 | import { ChannelConfig, OrdererGroup, OrgConfig } from "../types/FabloConfigExtended"; 2 | import { ChannelJson } from "../types/FabloConfigJson"; 3 | import * as _ from "lodash"; 4 | import defaults from "./defaults"; 5 | 6 | const filterToAvailablePeers = (orgTransformedFormat: OrgConfig, peersTransformedFormat: string[]) => { 7 | const filteredPeers = orgTransformedFormat.peers.filter((p) => peersTransformedFormat.includes(p.name)); 8 | return { 9 | ...orgTransformedFormat, 10 | peers: filteredPeers, 11 | headPeer: filteredPeers[0], 12 | }; 13 | }; 14 | 15 | const extendChannelConfig = ( 16 | channelJsonFormat: ChannelJson, 17 | orgsTransformed: OrgConfig[], 18 | ordererGroups: OrdererGroup[], 19 | ): ChannelConfig => { 20 | const channelName = channelJsonFormat.name; 21 | const profileName = _.chain(channelName).camelCase().upperFirst().value(); 22 | const ordererGroupName = channelJsonFormat.ordererGroup ?? defaults.channel.ordererGroup(ordererGroups); 23 | 24 | const orgNames = channelJsonFormat.orgs.map((o) => o.name); 25 | const orgPeers = channelJsonFormat.orgs.map((o) => o.peers).reduce((a, b) => a.concat(b), []); 26 | const orgsForChannel = orgsTransformed 27 | .filter((org) => orgNames.includes(org.name)) 28 | .map((org) => filterToAvailablePeers(org, orgPeers)); 29 | 30 | const ordererGroup = ordererGroups.filter((group) => group.name == ordererGroupName)[0]; 31 | const ordererHead = ordererGroup.ordererHeads[0]; 32 | 33 | return { 34 | name: channelName, 35 | profileName, 36 | orgs: orgsForChannel, 37 | ordererGroup, 38 | ordererHead, 39 | instantiatingOrg: orgsForChannel[0], 40 | }; 41 | }; 42 | 43 | const extendChannelsConfig = ( 44 | channelsJsonConfigFormat: ChannelJson[], 45 | orgsTransformed: OrgConfig[], 46 | ordererGroups: OrdererGroup[], 47 | ): ChannelConfig[] => channelsJsonConfigFormat.map((ch) => extendChannelConfig(ch, orgsTransformed, ordererGroups)); 48 | 49 | export default extendChannelsConfig; 50 | -------------------------------------------------------------------------------- /src/extend-config/extendConfig.ts: -------------------------------------------------------------------------------- 1 | import { FabloConfigJson } from "../types/FabloConfigJson"; 2 | import { FabloConfigExtended } from "../types/FabloConfigExtended"; 3 | import { extendOrgsConfig } from "./extendOrgsConfig"; 4 | import extendGlobal from "./extendGlobal"; 5 | import extendChannelsConfig from "./extendChannelsConfig"; 6 | import extendChaincodesConfig from "./extendChaincodesConfig"; 7 | import extendHooksConfig from "./extendHooksConfig"; 8 | import { distinctOrdererHeads, mergeOrdererGroups } from "./mergeOrdererGroups"; 9 | 10 | const extendConfig = (json: FabloConfigJson): FabloConfigExtended => { 11 | const { 12 | global: globalJson, 13 | orgs: orgsJson, 14 | channels: channelsJson, 15 | chaincodes: chaincodesJson, 16 | hooks: hooksJson, 17 | } = json; 18 | 19 | const global = extendGlobal(globalJson); 20 | const orgs = extendOrgsConfig(orgsJson, global); 21 | const ordererGroups = mergeOrdererGroups(orgs); 22 | const orderedHeadsDistinct = distinctOrdererHeads(ordererGroups); 23 | 24 | const channels = extendChannelsConfig(channelsJson, orgs, ordererGroups); 25 | const chaincodes = extendChaincodesConfig(chaincodesJson, channels, global); 26 | const hooks = extendHooksConfig(hooksJson); 27 | 28 | return { 29 | global, 30 | ordererGroups, 31 | orderedHeadsDistinct, 32 | orgs, 33 | channels, 34 | chaincodes, 35 | hooks, 36 | }; 37 | }; 38 | 39 | export default extendConfig; 40 | -------------------------------------------------------------------------------- /src/extend-config/extendGlobal.ts: -------------------------------------------------------------------------------- 1 | // Used https://github.com/hyperledger/fabric/blob/v1.4.8/sampleconfig/configtx.yaml for values 2 | import { Capabilities, FabricVersions, Global } from "../types/FabloConfigExtended"; 3 | import { version } from "../repositoryUtils"; 4 | import { GlobalJson } from "../types/FabloConfigJson"; 5 | import defaults from "./defaults"; 6 | 7 | const getNetworkCapabilities = (fabricVersion: string): Capabilities => { 8 | if (version(fabricVersion).isGreaterOrEqual("2.5.0") && !version(fabricVersion).isGreaterOrEqual("3.0.0")) 9 | return { channel: "V2_0", orderer: "V2_0", application: "V2_5", isV2: true, isV3: false }; 10 | 11 | if (version(fabricVersion).isGreaterOrEqual("3.0.0")) 12 | return { channel: "V3_0", orderer: "V2_0", application: "V2_5", isV2: false, isV3: true }; 13 | 14 | return { channel: "V2_0", orderer: "V2_0", application: "V2_0", isV2: true, isV3: false }; 15 | }; 16 | 17 | const getVersions = (fabricVersion: string): FabricVersions => { 18 | const majorMinor = version(fabricVersion).takeMajorMinor(); 19 | 20 | const fabricNodeenvExceptions: Record = { 21 | "2.4": "2.4.2", 22 | "2.4.1": "2.4.2", 23 | }; 24 | 25 | const below3_0_0 = (v: string) => (v.startsWith("3.") ? "2.5" : v); 26 | 27 | const is_or_above3_0_0 = (v: string) => (v.startsWith("3.") ? "3.0.0" : v); 28 | 29 | return { 30 | fabricVersion, 31 | fabricToolsVersion: is_or_above3_0_0(fabricVersion), 32 | fabricCaVersion: version(fabricVersion).isGreaterOrEqual("1.4.10") ? "1.5.5" : fabricVersion, 33 | fabricCcenvVersion: fabricVersion, 34 | fabricBaseosVersion: version(fabricVersion).isGreaterOrEqual("2.0") ? fabricVersion : "0.4.9", 35 | fabricJavaenvVersion: below3_0_0(majorMinor), 36 | fabricNodeenvVersion: fabricNodeenvExceptions[fabricVersion] ?? below3_0_0(majorMinor), 37 | fabricRecommendedNodeVersion: version(fabricVersion).isGreaterOrEqual("2.4") ? "16" : "12", 38 | }; 39 | }; 40 | 41 | const getEnvVarOrThrow = (name: string): string => { 42 | const value = process.env[name]; 43 | if (!value || !value.length) throw new Error(`Missing environment variable ${name}`); 44 | return value; 45 | }; 46 | 47 | const getPathsFromEnv = () => ({ 48 | fabloConfig: getEnvVarOrThrow("FABLO_CONFIG"), 49 | chaincodesBaseDir: getEnvVarOrThrow("CHAINCODES_BASE_DIR"), 50 | }); 51 | 52 | const extendGlobal = (globalJson: GlobalJson): Global => { 53 | const engine = globalJson.engine ?? "docker"; 54 | 55 | const monitoring = { 56 | loglevel: globalJson?.monitoring?.loglevel || defaults.global.monitoring.loglevel, 57 | }; 58 | 59 | const explorer = !globalJson?.tools?.explorer 60 | ? {} 61 | : { 62 | explorer: { address: "explorer.example.com", port: 7010 }, 63 | }; 64 | 65 | return { 66 | ...globalJson, 67 | ...getVersions(globalJson.fabricVersion), 68 | engine, 69 | paths: getPathsFromEnv(), 70 | monitoring, 71 | capabilities: getNetworkCapabilities(globalJson.fabricVersion), 72 | tools: { ...explorer }, 73 | }; 74 | }; 75 | 76 | export { getNetworkCapabilities }; 77 | export default extendGlobal; 78 | -------------------------------------------------------------------------------- /src/extend-config/extendHooksConfig.ts: -------------------------------------------------------------------------------- 1 | import { HooksJson } from "../types/FabloConfigJson"; 2 | import { HooksConfig } from "../types/FabloConfigExtended"; 3 | 4 | const extendHooksConfig = (hooksJson: HooksJson | undefined): HooksConfig => { 5 | const postGenerate = typeof hooksJson?.postGenerate === "string" ? hooksJson.postGenerate : ""; 6 | return { 7 | postGenerate, 8 | }; 9 | }; 10 | export default extendHooksConfig; 11 | -------------------------------------------------------------------------------- /src/extend-config/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | import parseFabloConfig from "../utils/parseFabloConfig"; 3 | import extendConfig from "./extendConfig"; 4 | import { getNetworkCapabilities } from "./extendGlobal"; 5 | 6 | const ValidateGeneratorPath = require.resolve("../validate"); 7 | 8 | class ExtendConfigGenerator extends Generator { 9 | constructor(args: string[], opts: Generator.GeneratorOptions) { 10 | super(args, opts); 11 | this.argument("fabloConfig", { 12 | type: String, 13 | optional: true, 14 | description: "Fablo config file path", 15 | default: "../../network/fablo-config.json", 16 | }); 17 | 18 | this.composeWith(ValidateGeneratorPath, { arguments: [this.options.fabloConfig] }); 19 | } 20 | 21 | async writing(): Promise { 22 | const fabloConfigPath = `${this.env.cwd}/${this.options.fabloConfig}`; 23 | const json = parseFabloConfig(this.fs.read(fabloConfigPath)); 24 | const configExtended = extendConfig(json); 25 | console.log(JSON.stringify(configExtended, undefined, 2)); 26 | } 27 | } 28 | 29 | export { extendConfig, getNetworkCapabilities }; 30 | export default ExtendConfigGenerator; 31 | -------------------------------------------------------------------------------- /src/extend-config/mergeOrdererGroups.ts: -------------------------------------------------------------------------------- 1 | import { OrdererConfig, OrdererGroup, OrgConfig } from "../types/FabloConfigExtended"; 2 | import * as _ from "lodash"; 3 | 4 | export const mergeOrdererGroups = (orgs: OrgConfig[]): OrdererGroup[] => { 5 | const ordererGroups = orgs.flatMap((o) => o.ordererGroups); 6 | 7 | const ordererGroupsGrouped: Record = _.groupBy(ordererGroups, (group) => group.name); 8 | 9 | return Object.values(ordererGroupsGrouped).flatMap((groupsWithSameGroupName) => { 10 | const orderers = groupsWithSameGroupName.flatMap((group) => group.orderers); 11 | const hostingOrgs = groupsWithSameGroupName.flatMap((group) => group.hostingOrgs); 12 | const ordererHeads = groupsWithSameGroupName.flatMap((group) => group.ordererHeads); 13 | 14 | return { 15 | ...groupsWithSameGroupName[0], 16 | hostingOrgs, 17 | orderers, 18 | ordererHeads, 19 | ordererHead: ordererHeads[0], 20 | }; 21 | }); 22 | }; 23 | 24 | export const distinctOrdererHeads = (ordererGroups: OrdererGroup[]): OrdererConfig[] => { 25 | const allOrdererHeads = ordererGroups.flatMap((g) => g.ordererHeads); 26 | return _.unionBy(allOrdererHeads, (o) => o.domain); 27 | }; 28 | -------------------------------------------------------------------------------- /src/init/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | import * as chalk from "chalk"; 3 | import { GlobalJson, FabloConfigJson } from "../types/FabloConfigJson"; 4 | 5 | function getDefaultFabloConfig(): FabloConfigJson { 6 | return { 7 | $schema: "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json", 8 | global: { 9 | fabricVersion: "2.5.9", 10 | tls: false, 11 | peerDevMode: false, 12 | }, 13 | orgs: [ 14 | { 15 | organization: { 16 | name: "Orderer", 17 | domain: "orderer.example.com", 18 | mspName: "OrdererMSP", 19 | }, 20 | ca: { 21 | prefix: "ca", 22 | db: "sqlite", 23 | }, 24 | orderers: [ 25 | { 26 | groupName: "group1", 27 | type: "solo", 28 | instances: 1, 29 | prefix: "orderer", 30 | }, 31 | ], 32 | }, 33 | { 34 | organization: { 35 | name: "Org1", 36 | domain: "org1.example.com", 37 | mspName: "Org1MSP", 38 | }, 39 | ca: { 40 | prefix: "ca", 41 | db: "sqlite", 42 | }, 43 | orderers: [], 44 | peer: { 45 | instances: 2, 46 | db: "LevelDb", 47 | prefix: "peer", 48 | }, 49 | }, 50 | ], 51 | channels: [ 52 | { 53 | name: "my-channel1", 54 | orgs: [ 55 | { 56 | name: "Org1", 57 | peers: ["peer0", "peer1"], 58 | }, 59 | ], 60 | }, 61 | ], 62 | chaincodes: [ 63 | { 64 | name: "chaincode1", 65 | version: "0.0.1", 66 | lang: "node", 67 | channel: "my-channel1", 68 | directory: "./chaincodes/chaincode-kv-node", 69 | privateData: [], 70 | }, 71 | ], 72 | hooks: {}, 73 | }; 74 | } 75 | 76 | export default class InitGenerator extends Generator { 77 | constructor(readonly args: string[], opts: Generator.GeneratorOptions) { 78 | super(args, opts); 79 | } 80 | 81 | async copySampleConfig(): Promise { 82 | let fabloConfigJson = getDefaultFabloConfig(); 83 | 84 | const shouldInitWithNodeChaincode = this.args.length && this.args.find((v) => v === "node"); 85 | if (shouldInitWithNodeChaincode) { 86 | console.log("Creating sample Node.js chaincode"); 87 | this.fs.copy(this.templatePath("chaincodes"), this.destinationPath("chaincodes")); 88 | // force build on Node 12, since dev deps (@theledger/fabric-mock-stub) may not work on 16 89 | this.fs.write(this.destinationPath("chaincodes/chaincode-kv-node/.nvmrc"), "12"); 90 | } else { 91 | fabloConfigJson = { ...fabloConfigJson, chaincodes: [] }; 92 | } 93 | 94 | const shouldAddFabloRest = this.args.length && this.args.find((v) => v === "rest"); 95 | if (shouldAddFabloRest) { 96 | const orgs = fabloConfigJson.orgs.map((org) => ({ ...org, tools: { fabloRest: true } })); 97 | fabloConfigJson = { ...fabloConfigJson, orgs }; 98 | } else { 99 | const orgs = fabloConfigJson.orgs.map((org) => ({ ...org, tools: {} })); 100 | fabloConfigJson = { ...fabloConfigJson, orgs }; 101 | } 102 | 103 | const shouldUseKubernetes = this.args.length && this.args.find((v) => v === "kubernetes" || v === "k8s"); 104 | const shouldRunInDevMode = this.args.length && this.args.find((v) => v === "dev"); 105 | const global: GlobalJson = { 106 | ...fabloConfigJson.global, 107 | engine: shouldUseKubernetes ? "kubernetes" : "docker", 108 | peerDevMode: !!shouldRunInDevMode, 109 | }; 110 | fabloConfigJson = { ...fabloConfigJson, global }; 111 | 112 | this.fs.write(this.destinationPath("fablo-config.json"), JSON.stringify(fabloConfigJson, undefined, 2)); 113 | 114 | this.on("end", () => { 115 | console.log("==========================================================="); 116 | console.log(chalk.bold("Sample config file created! :)")); 117 | console.log("You can start your network with 'fablo up' command"); 118 | console.log("==========================================================="); 119 | }); 120 | } 121 | } -------------------------------------------------------------------------------- /src/list-compatible-updates/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | import * as chalk from "chalk"; 3 | import * as config from "../config"; 4 | import * as repositoryUtils from "../repositoryUtils"; 5 | 6 | export default class ListCompatibleUpdatesGenerator extends Generator { 7 | async checkForCompatibleUpdates(): Promise { 8 | const allNewerVersions = (await repositoryUtils.getAvailableTags()).filter( 9 | (name) => config.isFabloVersionSupported(name) && name > config.fabloVersion, 10 | ); 11 | 12 | this._printVersions(allNewerVersions); 13 | } 14 | 15 | _printVersions(versionsToPrint: string[]): void { 16 | if (versionsToPrint.length > 0) { 17 | console.log(chalk.bold("====== !Compatible Fablo versions found! :) =============")); 18 | console.log(`${chalk.underline.bold("Compatible")} versions:`); 19 | versionsToPrint.forEach((version) => console.log(`- ${version}`)); 20 | console.log(""); 21 | console.log("To update just run command:"); 22 | console.log(`\t${chalk.bold("fablo use [version]")}`); 23 | console.log(chalk.bold("===========================================================")); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/list-versions/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | import * as config from "../config"; 3 | import * as repositoryUtils from "../repositoryUtils"; 4 | 5 | export default class ListVersionsGenerator extends Generator { 6 | async printAllVersions(): Promise { 7 | const allVersions = await repositoryUtils.getAvailableTags(); 8 | const versionsSortedAndMarked = allVersions 9 | .map((v) => (v === config.fabloVersion ? `${v} <== current` : v)) 10 | .map((v) => (config.isFabloVersionSupported(v) && !v.includes("current") ? `${v} (compatible)` : v)); 11 | 12 | versionsSortedAndMarked.forEach((version) => console.log(`- ${version}`)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/repositoryUtils.test.ts: -------------------------------------------------------------------------------- 1 | import { sortVersions, version } from "./repositoryUtils"; 2 | 3 | describe("repositoryUtils", () => { 4 | 5 | it("should sort versions", () => { 6 | const unsorted = [ 7 | "0.0.2", 8 | "0.0.2-unstable", 9 | "0.0.1", 10 | "0.1.11", 11 | "0.1.1", 12 | "0.1.1-unstable", 13 | "1.21.2", 14 | ]; 15 | 16 | const expected = [ 17 | "1.21.2", 18 | "0.1.11", 19 | "0.1.1", 20 | "0.0.2", 21 | "0.0.1", 22 | "0.1.1-unstable", 23 | "0.0.2-unstable", 24 | ]; 25 | 26 | expect(sortVersions(unsorted)).toEqual(expected); 27 | }); 28 | 29 | it("should place pre‑release (named) tags after the matching stable tag", () => { 30 | const unsorted = [ 31 | "0.1.1-alpha", 32 | "0.1.1-beta", 33 | "0.1.2-alpha", 34 | "0.1.2", 35 | "0.1.1", 36 | ]; 37 | 38 | const expected = [ 39 | "0.1.2", 40 | "0.1.1", 41 | "0.1.2-alpha", 42 | "0.1.1-beta", 43 | "0.1.1-alpha", 44 | ]; 45 | 46 | expect(sortVersions(unsorted)).toEqual(expected); 47 | }); 48 | 49 | it("should handle multi digit fragments correctly", () => { 50 | const unsorted = ["1.9.9", "1.10.0", "1.9.10"]; 51 | const expected = ["1.10.0", "1.9.10", "1.9.9"]; 52 | expect(sortVersions(unsorted)).toEqual(expected); 53 | }); 54 | 55 | 56 | it("should compare versions", () => { 57 | expect(version("1.4.0").isGreaterOrEqual("1.4.0")).toBe(true); 58 | expect(version("1.4.0").isGreaterOrEqual("1.4.1")).toBe(false); 59 | expect(version("1.4.0").isGreaterOrEqual("1.3.0")).toBe(true); 60 | expect(version("1.4.0").isGreaterOrEqual("2.1.0")).toBe(false); 61 | expect(version("3.0.0").isGreaterOrEqual("3.0.0-beta")).toBe(true); 62 | expect(version("3.0.0-beta").isGreaterOrEqual("3.0.0")).toBe(true); 63 | expect(version("3.0.0").isGreaterOrEqual("3.0.0")).toBe(true); 64 | expect(version("3.0.0").isGreaterOrEqual("3.0.1")).toBe(false); 65 | }); 66 | 67 | it("should treat pre-release tags as equal to the corresponding stable tag for ≥ comparison", () => { 68 | expect(version("0.0.1-alpha").isGreaterOrEqual("0.0.1")).toBe(true); 69 | expect(version("0.0.1").isGreaterOrEqual("0.0.1-alpha")).toBe(true); 70 | }); 71 | 72 | 73 | it("should check membership with isOneOf()", () => { 74 | const list = ["1.2.3", "2.0.0", "3.1.4-alpha"]; 75 | expect(version("1.2.3").isOneOf(list)).toBe(true); 76 | expect(version("3.1.4").isOneOf(list)).toBe(false); 77 | }); 78 | 79 | 80 | it("should take major.minor fragments only", () => { 81 | expect(version("10.5.7").takeMajorMinor()).toBe("10.5"); 82 | expect(version("2.0.0-beta").takeMajorMinor()).toBe("2.0"); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /src/repositoryUtils.ts: -------------------------------------------------------------------------------- 1 | import got from "got"; 2 | 3 | const repositoryTagsListUrl = `https://api.github.com/repos/hyperledger-labs/fablo/releases`; 4 | 5 | const incrementVersionFragment = (versionFragment: string) => { 6 | if (versionFragment.includes("-")) { 7 | const splitted = versionFragment.split("-"); 8 | return `${+splitted[0] + 100000}-${splitted[1]}`; 9 | } 10 | return +versionFragment + 100000; 11 | }; 12 | 13 | const incrementVersionFragments = (v: string) => v.split(".").map(incrementVersionFragment).join("."); 14 | 15 | const decrementVersionFragment = (incrementedVersionFragment: string) => { 16 | if (incrementedVersionFragment.includes("-")) { 17 | const splitted = incrementedVersionFragment.split("-"); 18 | return `${+splitted[0] - 100000}-${splitted[1]}`; 19 | } 20 | return +incrementedVersionFragment - 100000; 21 | }; 22 | 23 | const decrementVersionFragments = (v: string) => v.split(".").map(decrementVersionFragment).join("."); 24 | 25 | const namedVersionsAsLast = (a: string, b: string) => { 26 | if (/[a-z]/i.test(a) && !/[a-z]/i.test(b)) return -1; 27 | if (/[a-z]/i.test(b) && !/[a-z]/i.test(a)) return 1; 28 | if (a.toString() < b.toString()) return -1; 29 | if (a.toString() > b.toString()) return 1; 30 | return 0; 31 | }; 32 | 33 | const sortVersions = (versions: string[]): string[] => 34 | versions.map(incrementVersionFragments).sort(namedVersionsAsLast).map(decrementVersionFragments).reverse(); 35 | 36 | interface Version { 37 | isGreaterOrEqual(v2: string): boolean; 38 | isOneOf(vs: string[]): boolean; 39 | takeMajorMinor(): string; 40 | } 41 | 42 | const version = (v: string): Version => ({ 43 | isGreaterOrEqual(v2: string) { 44 | const vStd = incrementVersionFragments(v).split("-")[0]; 45 | const v2Std = incrementVersionFragments(v2).split("-")[0]; 46 | return vStd >= v2Std; 47 | }, 48 | isOneOf(vs: string[]): boolean { 49 | return vs.includes(v); 50 | }, 51 | takeMajorMinor(): string { 52 | const [major, minor] = v.split("."); 53 | return `${major}.${minor}`; 54 | }, 55 | }); 56 | 57 | const requestVersionNames = (): Promise<{ tag_name: string }[]> => { 58 | const params = { 59 | searchParams: { 60 | tag_status: "active", 61 | page_size: 1024, 62 | }, 63 | }; 64 | return got(repositoryTagsListUrl, params).json<{ tag_name: string }[]>(); 65 | }; 66 | 67 | const versionRegex = /^v\d+\.\d+\.\d+(-[a-zA-Z0-9-]+)?$/; 68 | 69 | const getAvailableTags = async (): Promise => { 70 | try { 71 | const versionNames = (await requestVersionNames()) 72 | .filter((release) => versionRegex.test(release.tag_name)) 73 | .map((release) => release.tag_name.slice(1)); 74 | return sortVersions(versionNames); 75 | } catch (err) { 76 | console.log(`Could not check for updates. Url: '${repositoryTagsListUrl}' not available`); 77 | return []; 78 | } 79 | }; 80 | 81 | export { getAvailableTags, sortVersions, version }; 82 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-config/.gitignore: -------------------------------------------------------------------------------- 1 | /config 2 | /crypto-config 3 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-config/configtx-raft-template.yaml.ejs: -------------------------------------------------------------------------------- 1 | <% if (ordererGroup.consensus == 'etcdraft') { %> 2 | EtcdRaft: 3 | Consenters:<% ordererGroup.orderers.forEach(function(orderer) { %> 4 | - Host: <%= orderer.address %> 5 | Port: <%= orderer.port %> 6 | ClientTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt 7 | ServerTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt 8 | <% })} -%> 9 | 10 | <% if (ordererGroup.consensus == "BFT") { %> 11 | SmartBFT: 12 | RequestBatchMaxCount: 100 13 | RequestBatchMaxInterval: 50ms 14 | RequestForwardTimeout: 2s 15 | RequestComplainTimeout: 20s 16 | RequestAutoRemoveTimeout: 3m0s 17 | ViewChangeResendInterval: 5s 18 | ViewChangeTimeout: 20s 19 | LeaderHeartbeatTimeout: 1m0s 20 | CollectTimeout: 1s 21 | RequestBatchMaxBytes: 10485760 22 | IncomingMessageBufferSize: 200 23 | RequestPoolSize: 100000 24 | LeaderHeartbeatCount: 10 25 | ConsenterMapping:<% ordererGroup.orderers.forEach(function(orderer, index) { %> 26 | - ID: <%= index+1 %> 27 | Host: <%= orderer.address %> 28 | Port: <%= orderer.port %> 29 | MSPID: <%= orderer.orgMspName %> 30 | Identity: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/msp/signcerts/<%= orderer.address %>-cert.pem 31 | ClientTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt 32 | ServerTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt 33 | <% })} -%> 34 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-config/crypto-config-org.yaml: -------------------------------------------------------------------------------- 1 | PeerOrgs: 2 | - Name: <%= org.name %> 3 | Domain: <%= org.domain %> 4 | Specs: 5 | <%_ org.ordererGroups.forEach(function(ordererGroup) { _%> 6 | <%_ ordererGroup.orderers.forEach(function(orderer) { _%> 7 | - Hostname: <%= orderer.name %> 8 | <%_ }) _%> 9 | <%_ }) _%> 10 | Template: 11 | Count: <%= org.peersCount %> 12 | Users: 13 | Count: 1 14 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-config/explorer/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "network-configs": { 3 | <%_ orgs.forEach(function(org) { _%> 4 | "network-<%= org.name.toLowerCase() %>": { 5 | "name": "Network of <%= org.name %>", 6 | "profile": "/opt/explorer/app/platform/fabric/connection-profile/connection-profile-<%= org.name.toLowerCase() %>.json" 7 | }, 8 | <%_ }) _%> 9 | }, 10 | "license": "Apache-2.0" 11 | } 12 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | FABLO_NETWORK_ROOT="$(cd "$(dirname "$0")" && pwd)" 6 | 7 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/base-help.sh" 8 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/base-functions.sh" 9 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/chaincode-functions.sh" 10 | source "$FABLO_NETWORK_ROOT/fabric-docker/channel-query-scripts.sh" 11 | source "$FABLO_NETWORK_ROOT/fabric-docker/snapshot-scripts.sh" 12 | source "$FABLO_NETWORK_ROOT/fabric-docker/commands-generated.sh" 13 | source "$FABLO_NETWORK_ROOT/fabric-docker/.env" 14 | source "$FABLO_NETWORK_ROOT/fabric-docker/chaincode-scripts.sh" 15 | 16 | 17 | networkUp() { 18 | generateArtifacts 19 | startNetwork 20 | generateChannelsArtifacts 21 | installChannels 22 | installChaincodes 23 | notifyOrgsAboutChannels 24 | printStartSuccessInfo 25 | } 26 | 27 | if [ "$1" = "up" ]; then 28 | networkUp 29 | elif [ "$1" = "down" ]; then 30 | networkDown 31 | elif [ "$1" = "reset" ]; then 32 | networkDown 33 | networkUp 34 | elif [ "$1" = "start" ]; then 35 | startNetwork 36 | elif [ "$1" = "stop" ]; then 37 | stopNetwork 38 | elif [ "$1" = "chaincodes" ] && [ "$2" = "install" ]; then 39 | installChaincodes 40 | elif [ "$1" = "chaincode" ] && [ "$2" = "install" ]; then 41 | installChaincode "$3" "$4" 42 | elif [ "$1" = "chaincode" ] && [ "$2" = "upgrade" ]; then 43 | upgradeChaincode "$3" "$4" 44 | elif [ "$1" = "chaincode" ] && [ "$2" = "dev" ]; then 45 | runDevModeChaincode "$3" "$4" 46 | elif [ "$1" = "chaincode" ] && [ "$2" = "invoke" ]; then 47 | chaincodeInvoke "$3" "$4" "$5" "$6" "$7" 48 | elif [ "$1" = "chaincodes" ] && [ "$2" = "list" ]; then 49 | chaincodeList "$3" "$4" 50 | elif [ "$1" = "channel" ]; then 51 | channelQuery "${@:2}" 52 | elif [ "$1" = "snapshot" ]; then 53 | createSnapshot "$2" 54 | elif [ "$1" = "clone-to" ]; then 55 | cloneSnapshot "$2" "${3:-""}" 56 | elif [ "$1" = "help" ]; then 57 | printHelp 58 | elif [ "$1" = "--help" ]; then 59 | printHelp 60 | else 61 | echo "No command specified" 62 | echo "Basic commands are: up, down, start, stop, reset" 63 | echo "To list channel query helper commands type: 'fablo channel --help'" 64 | echo "Also check: 'chaincode install'" 65 | echo "Use 'help' or '--help' for more information" 66 | fi -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/.env: -------------------------------------------------------------------------------- 1 | FABLO_VERSION=<%= fabloVersion %> 2 | FABLO_BUILD=<%= fabloBuild %> 3 | FABLO_REST_VERSION=<%= fabloRestVersion %> 4 | HYPERLEDGER_EXPLORER_VERSION=<%= hyperledgerExplorerVersion %> 5 | 6 | COUCHDB_VERSION=<%= couchDbVersion %> 7 | FABRIC_COUCHDB_VERSION=<%= fabricCouchDbVersion %> 8 | 9 | FABLO_CONFIG=<%= paths.fabloConfig %> 10 | CHAINCODES_BASE_DIR=<%= paths.chaincodesBaseDir %> 11 | 12 | COMPOSE_PROJECT_NAME=<%= composeNetworkName %> 13 | LOGGING_LEVEL=<%= global.monitoring.loglevel %> 14 | 15 | FABRIC_VERSION=<%= global.fabricVersion %> 16 | FABRIC_TOOLS_VERSION=<%= global.fabricToolsVersion %> 17 | FABRIC_CA_VERSION=<%= global.fabricCaVersion %> 18 | FABRIC_CA_POSTGRES_VERSION=<%= fabricCaPostgresVersion %> 19 | FABRIC_CCENV_VERSION=<%= global.fabricCcenvVersion %> 20 | FABRIC_BASEOS_VERSION=<%= global.fabricBaseosVersion %> 21 | FABRIC_JAVAENV_VERSION=<%= global.fabricJavaenvVersion %> 22 | FABRIC_NODEENV_VERSION=<%= global.fabricNodeenvVersion %> 23 | RECOMMENDED_NODE_VERSION=<%= global.fabricRecommendedNodeVersion %> 24 | 25 | ROOT_CA_ADMIN_NAME=admin 26 | ROOT_CA_ADMIN_PASSWORD=adminpw 27 | <% orgs.forEach(function(org){ %> 28 | <%= org.ca.caAdminNameVar %>=admin 29 | <%= org.ca.caAdminPassVar %>=adminpw 30 | <% }); %> 31 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/chaincode-scripts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | chaincodeList() { 4 | if [ "$#" -ne 2 ]; then 5 | echo "Expected 2 parameters for chaincode list, but got: $*" 6 | exit 1 7 | <% orgs.forEach((org) => { org.peers.forEach((peer) => { %> 8 | elif [ "$1" = "<%= peer.address %>" ]; then 9 | <% if(!global.tls) { %> 10 | peerChaincodeList "<%= org.cli.address %>" "<%= peer.fullAddress %>" "$2" # $2 is channel name 11 | <% } else { %> 12 | peerChaincodeListTls "<%= org.cli.address %>" "<%= peer.fullAddress %>" "$2" "crypto-orderer/tlsca.<%= ordererGroups[0].ordererHeads[0].domain %>-cert.pem" # Third argument is channel name 13 | <% } %> 14 | <% })}) %> 15 | else 16 | echo "Fail to call listChaincodes. No peer or channel found. Provided peer: $1, channel: $2" 17 | exit 1 18 | 19 | fi 20 | } 21 | 22 | # Function to perform chaincode invoke. Accepts 5 parameters: 23 | # 1. comma-separated peers 24 | # 2. channel name 25 | # 3. chaincode name 26 | # 4. chaincode command 27 | # 5. transient data (optional) 28 | chaincodeInvoke() { 29 | if [ "$#" -ne 4 ] && [ "$#" -ne 5 ]; then 30 | echo "Expected 4 or 5 parameters for chaincode list, but got: $*" 31 | echo "Usage: fablo chaincode invoke [transient]" 32 | exit 1 33 | fi 34 | 35 | # Cli needs to be from the same org as the first peer 36 | <% orgs.forEach((org) => { -%> 37 | <% org.peers.forEach((peer) => { -%> 38 | if [[ "$1" == "<%= peer.address %>"* ]]; then 39 | cli="<%= org.cli.address %>" 40 | fi 41 | <% }) -%> 42 | <% }) -%> 43 | 44 | peer_addresses="$1" 45 | <% orgs.forEach((org) => { -%> 46 | <% org.peers.forEach((peer) => { -%> 47 | peer_addresses="${peer_addresses//<%= peer.address %>/<%= peer.fullAddress %>}" 48 | <% }) -%> 49 | <% }) -%> 50 | 51 | <% if (global.tls) { -%> 52 | peer_certs="$1" 53 | <% orgs.forEach((org) => { -%> 54 | <% org.peers.forEach((peer) => { -%> 55 | peer_certs="${peer_certs//<%= peer.address %>/crypto/peers/<%= peer.address %>/tls/ca.crt}" 56 | <% }) -%> 57 | <% }) -%> 58 | <% } -%> 59 | 60 | <% if(!global.tls) { -%> 61 | peerChaincodeInvoke "$cli" "$peer_addresses" "$2" "$3" "$4" "$5" 62 | <% } else { -%> 63 | <% channels.forEach((channel) => { -%> 64 | if [ "$2" = "<%= channel.name %>" ]; then 65 | ca_cert="crypto-orderer/tlsca.<%= ordererGroups[0].ordererHeads[0].domain %>-cert.pem" 66 | fi 67 | <% }) -%> 68 | peerChaincodeInvokeTls "$cli" "$peer_addresses" "$2" "$3" "$4" "$5" "$peer_certs" "$ca_cert" 69 | <% } -%> 70 | } 71 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/channel-query-scripts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/channel-query-functions.sh" 4 | 5 | set -eu 6 | 7 | channelQuery() { 8 | if [ "$#" -eq 1 ]; then 9 | printChannelsHelp 10 | <% orgs.forEach((org) => { org.peers.forEach((peer) => { %> 11 | elif [ "$1" = "list" ] && [ "$2" = "<%= org.name.toLowerCase(); %>" ] && [ "$3" = "<%= peer.name %>" ]; then 12 | <% if(!global.tls) { %> 13 | peerChannelList "<%= org.cli.address %>" "<%= peer.fullAddress %>" 14 | <% } else { %> 15 | peerChannelListTls "<%= org.cli.address %>" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= ordererGroups[0].ordererHeads[0].domain %>-cert.pem" 16 | <% } %> 17 | <% })}) %> 18 | 19 | <% channels.forEach((channel) => { channel.orgs.forEach((org) => { org.peers.forEach((peer) => { %> 20 | elif [ "$1" = "getinfo" ] && [ "$2" = "<%= channel.name %>" ] && [ "$3" = "<%= org.name.toLowerCase(); %>" ] && [ "$4" = "<%= peer.name %>" ]; then 21 | <% if(!global.tls) { %> 22 | peerChannelGetInfo "<%= channel.name %>" "<%= org.cli.address %>" "<%= peer.fullAddress %>" 23 | <% } else { %> 24 | peerChannelGetInfoTls "<%= channel.name %>" "<%= org.cli.address %>" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= channel.ordererHead.domain %>-cert.pem" 25 | <% } %> 26 | elif [ "$1" = "fetch" ] && [ "$2" = "config" ] && [ "$3" = "<%= channel.name %>" ] && [ "$4" = "<%= org.name.toLowerCase(); %>" ] && [ "$5" = "<%= peer.name %>" ]; then 27 | TARGET_FILE=${6:-"$channel-config.json"} 28 | <% if(!global.tls) { %> 29 | peerChannelFetchConfig "<%= channel.name %>" "<%= org.cli.address %>" "$TARGET_FILE" "<%= peer.fullAddress %>" 30 | <% } else { %> 31 | peerChannelFetchConfigTls "<%= channel.name %>" "<%= org.cli.address %>" "$TARGET_FILE" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= channel.ordererHead.domain %>-cert.pem" 32 | <% } %> 33 | elif [ "$1" = "fetch" ] && [ "$3" = "<%= channel.name %>" ] && [ "$4" = "<%= org.name.toLowerCase(); %>" ] && [ "$5" = "<%= peer.name %>" ]; then 34 | BLOCK_NAME=$2 35 | TARGET_FILE=${6:-"$BLOCK_NAME.block"} 36 | <% if(!global.tls) { %> 37 | peerChannelFetchBlock "<%= channel.name %>" "<%= org.cli.address %>" "${BLOCK_NAME}" "<%= peer.fullAddress %>" "$TARGET_FILE" 38 | <% } else { %> 39 | peerChannelFetchBlockTls "<%= channel.name %>" "<%= org.cli.address %>" "${BLOCK_NAME}" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= channel.ordererHead.domain %>-cert.pem" "$TARGET_FILE" 40 | <% } %> 41 | <% })})}) %> 42 | else 43 | echo "$@" 44 | echo "$1, $2, $3, $4, $5, $6, $7, $#" 45 | printChannelsHelp 46 | fi 47 | 48 | } 49 | 50 | printChannelsHelp() { 51 | echo "Channel management commands:" 52 | echo "" 53 | <% orgs.forEach((org) => { org.peers.forEach((peer) => { %> 54 | echo "fablo channel list <%= org.name.toLowerCase(); %> <%= peer.name %>" 55 | echo -e "\t List channels on '<%= peer.name %>' of '<%= org.name %>'". 56 | echo "" 57 | <% })}) %> 58 | <% channels.forEach((channel) => { channel.orgs.forEach((org) => { org.peers.forEach((peer) => { %> 59 | echo "fablo channel getinfo <%= channel.name %> <%= org.name.toLowerCase(); %> <%= peer.name %>" 60 | echo -e "\t Get channel info on '<%= peer.name %>' of '<%= org.name %>'". 61 | echo "" 62 | echo "fablo channel fetch config <%= channel.name %> <%= org.name.toLowerCase(); %> <%= peer.name %> [file-name.json]" 63 | echo -e "\t Download latest config block and save it. Uses first peer '<%= peer.name %>' of '<%= org.name %>'". 64 | echo "" 65 | echo "fablo channel fetch <%= channel.name %> <%= org.name.toLowerCase(); %> <%= peer.name %> [file name]" 66 | echo -e "\t Fetch a block with given number and save it. Uses first peer '<%= peer.name %>' of '<%= org.name %>'". 67 | echo "" 68 | <% })})}) %> 69 | } 70 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/commands-generated/chaincode-dev-v2.sh: -------------------------------------------------------------------------------- 1 | <%/* 2 | Run chaincode in dev mode for V2 capabilities. 3 | 4 | Required template parameters: 5 | - chaincode 6 | */-%> 7 | <% chaincode.channel.orgs.forEach((org) => { -%> 8 | printHeadline "Approving '<%= chaincode.name %>' for <%= org.name %> (dev mode)" "U1F60E" 9 | chaincodeApprove <% -%> 10 | "<%= org.cli.address %>" <% -%> 11 | "<%= org.headPeer.fullAddress %>" <% -%> 12 | "<%= chaincode.channel.name %>" <% -%> 13 | "<%= chaincode.name %>" <% -%> 14 | "<%= chaincode.version %>" <% -%> 15 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%> 16 | "<%- chaincode.endorsement || '' %>" <% -%> 17 | "false" <% -%> 18 | "" <% -%> 19 | "<%= chaincode.privateDataConfigFile || '' %>" 20 | <% }) -%> 21 | printItalics "Committing chaincode '<%= chaincode.name %>' on channel '<%= chaincode.channel.name %>' as '<%= chaincode.instantiatingOrg.name %>' (dev mode)" "U1F618" 22 | chaincodeCommit <% -%> 23 | "<%= chaincode.instantiatingOrg.cli.address %>" <% -%> 24 | "<%= chaincode.instantiatingOrg.headPeer.fullAddress %>" <% -%> 25 | "<%= chaincode.channel.name %>" <% -%> 26 | "<%= chaincode.name %>" <% -%> 27 | "<%= chaincode.version %>" <% -%> 28 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%> 29 | "<%- chaincode.endorsement || '' %>" <% -%> 30 | "false" <% -%> 31 | "" <% -%> 32 | "<%= chaincode.channel.orgs.map((o) => o.headPeer.fullAddress).join(',') %>" <% -%> 33 | "" <% -%> 34 | "<%= chaincode.privateDataConfigFile || '' %>" 35 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/commands-generated/chaincode-install-v2.sh: -------------------------------------------------------------------------------- 1 | <%/* 2 | Chaincode install and upgrade for V2 capabilities. 3 | 4 | Required bash variables: 5 | - version 6 | Required template parameters: 7 | - chaincode 8 | - global 9 | */-%> 10 | printHeadline "Packaging chaincode '<%= chaincode.name %>'" "U1F60E" 11 | chaincodeBuild <% -%> 12 | "<%= chaincode.name %>" <% -%> 13 | "<%= chaincode.lang %>" <% -%> 14 | "$CHAINCODES_BASE_DIR/<%= chaincode.directory %>" <% -%> 15 | "<%= global.fabricRecommendedNodeVersion %>" 16 | chaincodePackage <% -%> 17 | "<%= chaincode.instantiatingOrg.cli.address %>" <% -%> 18 | "<%= chaincode.instantiatingOrg.headPeer.fullAddress %>" <% -%> 19 | "<%= chaincode.name %>" <% -%> 20 | "$version" <% -%> 21 | "<%= chaincode.lang %>" <% -%> 22 | <% chaincode.channel.orgs.forEach((org) => { -%> 23 | printHeadline "Installing '<%= chaincode.name %>' for <%= org.name %>" "U1F60E" 24 | <% org.peers.forEach((peer) => { -%> 25 | chaincodeInstall <% -%> 26 | "<%= org.cli.address %>" <% -%> 27 | "<%= peer.fullAddress %>" <% -%> 28 | "<%= chaincode.name %>" <% -%> 29 | "$version" <% -%> 30 | "<%= !global.tls ? '' : `crypto-orderer/tlsca.${chaincode.channel.ordererHead.domain}-cert.pem` %>" 31 | <% }) -%> 32 | chaincodeApprove <% -%> 33 | "<%= org.cli.address %>" <% -%> 34 | "<%= org.headPeer.fullAddress %>" <% -%> 35 | "<%= chaincode.channel.name %>" <% -%> 36 | "<%= chaincode.name %>" <% -%> 37 | "$version" <% -%> 38 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%> 39 | "<%- chaincode.endorsement || '' %>" <% -%> 40 | "<%= `${chaincode.initRequired}` %>" <% -%> 41 | "<%= !global.tls ? '' : `crypto-orderer/tlsca.${chaincode.channel.ordererHead.domain}-cert.pem` %>" <% -%> 42 | "<%= chaincode.privateDataConfigFile || '' %>" 43 | <% }) -%> 44 | printItalics "Committing chaincode '<%= chaincode.name %>' on channel '<%= chaincode.channel.name %>' as '<%= chaincode.instantiatingOrg.name %>'" "U1F618" 45 | chaincodeCommit <% -%> 46 | "<%= chaincode.instantiatingOrg.cli.address %>" <% -%> 47 | "<%= chaincode.instantiatingOrg.headPeer.fullAddress %>" <% -%> 48 | "<%= chaincode.channel.name %>" <% -%> 49 | "<%= chaincode.name %>" <% -%> 50 | "$version" <% -%> 51 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%> 52 | "<%- chaincode.endorsement || '' %>" <% -%> 53 | "<%= `${chaincode.initRequired}` %>" <% -%> 54 | "<%= !global.tls ? '' : `crypto-orderer/tlsca.${chaincode.channel.ordererHead.domain}-cert.pem` %>" <% -%> 55 | "<%= chaincode.channel.orgs.map((o) => o.headPeer.fullAddress).join(',') %>" <% -%> 56 | "<%= !global.tls ? '' : chaincode.channel.orgs.map(o => `crypto-peer/${o.headPeer.address}/tls/ca.crt`).join(',') %>" <% -%> 57 | "<%= chaincode.privateDataConfigFile || '' %>" 58 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/scripts/base-help.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | printHelp() { 4 | echo "Fablo is powered by SoftwareMill" 5 | 6 | echo "" 7 | echo "usage: ./fabric-docker.sh " 8 | echo "" 9 | 10 | echo "Commands: " 11 | echo "" 12 | echo "./fabric-docker.sh up" 13 | echo -e "\t Use for first run. Creates all needed artifacts (certs, genesis block) and starts network for the first time." 14 | echo -e "\t After 'up' commands start/stop are used to manage network and rerun to rerun it" 15 | echo "" 16 | echo "./fabric-docker.sh down" 17 | echo -e "\t Back to empty state - destorys created containers, prunes generated certificates, configs." 18 | echo "" 19 | echo "./fabric-docker.sh start" 20 | echo -e "\t Starts already created network." 21 | echo "" 22 | echo "./fabric-docker.sh stop" 23 | echo -e "\t Stops already running network." 24 | echo "" 25 | echo "./fabric-docker.sh reset" 26 | echo -e "\t Fresh start - it destroys whole network, certs, configs and then reruns everything." 27 | echo "" 28 | echo "./fabric-docker.sh channel --help" 29 | echo -e "\t Detailed help for channel management scripts." 30 | echo "" 31 | } 32 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/scripts/channel-query-functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | peerChannelList() { 4 | local CLI_NAME=$1 5 | local PEER_ADDRESS=$2 6 | 7 | echo "Listing channels using $CLI_NAME using peer $PEER_ADDRESS..." 8 | inputLog "CLI_NAME: $CLI_NAME" 9 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 10 | 11 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel list 12 | } 13 | 14 | peerChannelGetInfo() { 15 | local CHANNEL_NAME=$1 16 | local CLI_NAME=$2 17 | local PEER_ADDRESS=$3 18 | 19 | echo "Getting info about $CHANNEL_NAME using peer $PEER_ADDRESS..." 20 | inputLog "CHANNEL_NAME: $CHANNEL_NAME" 21 | inputLog "CLI_NAME: $CLI_NAME" 22 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 23 | 24 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel getinfo \ 25 | -c "$CHANNEL_NAME" 26 | } 27 | 28 | peerChannelFetchConfig() { 29 | local CHANNEL_NAME=$1 30 | local CLI_NAME=$2 31 | local CONFIG_FILE_NAME=$3 32 | local PEER_ADDRESS=$4 33 | 34 | echo "Fetching config block from $CHANNEL_NAME using peer $PEER_ADDRESS..." 35 | inputLog "CHANNEL_NAME: $CHANNEL_NAME" 36 | inputLog "CLI_NAME: $CLI_NAME" 37 | inputLog "CONFIG_FILE_NAME: $CONFIG_FILE_NAME" 38 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 39 | 40 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/assets/ 41 | docker exec \ 42 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \ 43 | "$CLI_NAME" peer channel fetch config /tmp/hyperledger/assets/config_block_before.pb \ 44 | -c "$CHANNEL_NAME" 45 | 46 | docker exec "$CLI_NAME" chmod 777 /tmp/hyperledger/assets/config_block_before.pb 47 | docker exec \ 48 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \ 49 | "$CLI_NAME" configtxlator proto_decode \ 50 | --input /tmp/hyperledger/assets/config_block_before.pb \ 51 | --type common.Block | \ 52 | jq .data.data[0].payload.data.config > "$CONFIG_FILE_NAME" 53 | 54 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/assets/ 55 | } 56 | 57 | peerChannelFetchBlock() { 58 | local CHANNEL_NAME="$1" 59 | local CLI_NAME="$2" 60 | local BLOCK_NAME="$3" 61 | local PEER_ADDRESS="$4" 62 | local TARGET_FILE="$5" 63 | local TEMP_FILE="/tmp/hyperledger/blocks/$BLOCK_NAME.block" 64 | 65 | echo "Fetching block $BLOCK_NAME from $CHANNEL_NAME using peer $PEER_ADDRESS..." 66 | inputLog "CHANNEL_NAME: $CHANNEL_NAME" 67 | inputLog "CLI_NAME: $CLI_NAME" 68 | inputLog "BLOCK_NAME: $BLOCK_NAME" 69 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 70 | inputLog "TARGET_FILE: $TARGET_FILE" 71 | 72 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/blocks/ 73 | 74 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \ 75 | "$CLI_NAME" peer channel fetch "$BLOCK_NAME" "$TEMP_FILE" \ 76 | -c "$CHANNEL_NAME" 77 | 78 | docker exec "$CLI_NAME" cat "$TEMP_FILE" > "$TARGET_FILE" 79 | 80 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/blocks/ 81 | } 82 | 83 | #=== TLS equivalents ========================================================= 84 | 85 | peerChannelListTls() { 86 | local CLI_NAME=$1 87 | local PEER_ADDRESS=$2 88 | local CA_CERT=$3 89 | 90 | echo "Listing channels using $CLI_NAME using peer $PEER_ADDRESS (TLS)..." 91 | inputLog "CLI_NAME: $CLI_NAME" 92 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 93 | 94 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel list --tls --cafile "$CA_CERT" 95 | } 96 | 97 | peerChannelGetInfoTls() { 98 | local CHANNEL_NAME=$1 99 | local CLI_NAME=$2 100 | local PEER_ADDRESS=$3 101 | local CA_CERT=$4 102 | 103 | 104 | echo "Getting info about $CHANNEL_NAME using peer $PEER_ADDRESS (TLS)..." 105 | inputLog "CHANNEL_NAME: $CHANNEL_NAME" 106 | inputLog "CLI_NAME: $CLI_NAME" 107 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 108 | 109 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel getinfo \ 110 | -c "$CHANNEL_NAME" --tls --cafile "$CA_CERT" 111 | } 112 | 113 | peerChannelFetchConfigTls() { 114 | local CHANNEL_NAME=$1 115 | local CLI_NAME=$2 116 | local CONFIG_FILE_NAME=$3 117 | local PEER_ADDRESS=$4 118 | local CA_CERT=$5 119 | 120 | echo "Fetching config block from $CHANNEL_NAME using peer $PEER_ADDRESS (TLS)..." 121 | inputLog "CHANNEL_NAME: $CHANNEL_NAME" 122 | inputLog "CLI_NAME: $CLI_NAME" 123 | inputLog "CONFIG_FILE_NAME: $CONFIG_FILE_NAME" 124 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 125 | 126 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/assets/ 127 | docker exec \ 128 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \ 129 | "$CLI_NAME" peer channel fetch config /tmp/hyperledger/assets/config_block_before.pb \ 130 | -c "$CHANNEL_NAME" --tls --cafile "$CA_CERT" 131 | 132 | docker exec "$CLI_NAME" chmod 777 /tmp/hyperledger/assets/config_block_before.pb 133 | docker exec \ 134 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \ 135 | "$CLI_NAME" configtxlator proto_decode \ 136 | --input /tmp/hyperledger/assets/config_block_before.pb \ 137 | --type common.Block | \ 138 | jq .data.data[0].payload.data.config > "$CONFIG_FILE_NAME" 139 | 140 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/assets/ 141 | } 142 | 143 | peerChannelFetchBlockTls() { 144 | local CHANNEL_NAME="$1" 145 | local CLI_NAME="$2" 146 | local BLOCK_NAME="$3" 147 | local PEER_ADDRESS="$4" 148 | local CA_CERT="$5" 149 | local TARGET_FILE="$6" 150 | local TEMP_FILE="/tmp/hyperledger/blocks/$BLOCK_NAME.block" 151 | 152 | echo "Fetching block $BLOCK_NAME from $CHANNEL_NAME using peer $PEER_ADDRESS..." 153 | inputLog "CHANNEL_NAME: $CHANNEL_NAME" 154 | inputLog "CLI_NAME: $CLI_NAME" 155 | inputLog "BLOCK_NAME: $BLOCK_NAME" 156 | inputLog "PEER_ADDRESS: $PEER_ADDRESS" 157 | inputLog "TARGET_FILE: $TARGET_FILE" 158 | 159 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/blocks/ 160 | 161 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \ 162 | "$CLI_NAME" peer channel fetch "$BLOCK_NAME" "$TEMP_FILE" \ 163 | -c "$CHANNEL_NAME" --tls --cafile "$CA_CERT" 164 | 165 | docker exec "$CLI_NAME" cat "$TEMP_FILE" > "$TARGET_FILE" 166 | 167 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/blocks/ 168 | } 169 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/scripts/cli/channel_fns-v2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | createChannelAndJoin() { 6 | local CHANNEL_NAME=$1 7 | local CORE_PEER_LOCALMSPID=$2 8 | local CORE_PEER_ADDRESS=$3 9 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4") 10 | local ORDERER_URL=$5 11 | 12 | local DIR_NAME=step-createChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS 13 | 14 | echo "Creating channel with name: ${CHANNEL_NAME}" 15 | echo " Orderer: $ORDERER_URL" 16 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID" 17 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS" 18 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH" 19 | 20 | mkdir "$DIR_NAME" && cd "$DIR_NAME" 21 | 22 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".tx . 23 | peer channel create -o "${ORDERER_URL}" -c "${CHANNEL_NAME}" -f ./"$CHANNEL_NAME".tx 24 | peer channel join -b "${CHANNEL_NAME}".block 25 | 26 | rm -rf "$DIR_NAME" 27 | } 28 | 29 | createChannelAndJoinTls() { 30 | local CHANNEL_NAME=$1 31 | local CORE_PEER_LOCALMSPID=$2 32 | local CORE_PEER_ADDRESS=$3 33 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4") 34 | local CORE_PEER_TLS_MSPCONFIGPATH=$(realpath "$5") 35 | local TLS_CA_CERT_PATH=$(realpath "$6") 36 | local ORDERER_URL=$7 37 | 38 | local CORE_PEER_TLS_CERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.crt 39 | local CORE_PEER_TLS_KEY_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.key 40 | local CORE_PEER_TLS_ROOTCERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/ca.crt 41 | 42 | local DIR_NAME=step-createChannelAndJoinTls-$CHANNEL_NAME-$CORE_PEER_ADDRESS 43 | 44 | echo "Creating channel with name (TLS): ${CHANNEL_NAME}" 45 | echo " Orderer: $ORDERER_URL" 46 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID" 47 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS" 48 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH" 49 | echo " TLS_CA_CERT_PATH is: $TLS_CA_CERT_PATH" 50 | echo " CORE_PEER_TLS_CERT_FILE: $CORE_PEER_TLS_CERT_FILE" 51 | echo " CORE_PEER_TLS_KEY_FILE: $CORE_PEER_TLS_KEY_FILE" 52 | echo " CORE_PEER_TLS_ROOTCERT_FILE: $CORE_PEER_TLS_ROOTCERT_FILE" 53 | 54 | mkdir "$DIR_NAME" && cd "$DIR_NAME" 55 | 56 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".tx . 57 | 58 | peer channel create -o "${ORDERER_URL}" -c "${CHANNEL_NAME}" -f ./"$CHANNEL_NAME".tx --tls --cafile "$TLS_CA_CERT_PATH" 59 | peer channel join -b "${CHANNEL_NAME}".block --tls --cafile "$TLS_CA_CERT_PATH" 60 | 61 | 62 | rm -rf "$DIR_NAME" 63 | } 64 | 65 | fetchChannelAndJoin() { 66 | local CHANNEL_NAME=$1 67 | local CORE_PEER_LOCALMSPID=$2 68 | local CORE_PEER_ADDRESS=$3 69 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4") 70 | local ORDERER_URL=$5 71 | 72 | local DIR_NAME=step-fetchChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS 73 | 74 | echo "Fetching channel with name: ${CHANNEL_NAME}" 75 | echo " Orderer: $ORDERER_URL" 76 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID" 77 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS" 78 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH" 79 | 80 | mkdir "$DIR_NAME" && cd "$DIR_NAME" 81 | 82 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}" 83 | peer channel join -b "${CHANNEL_NAME}"_newest.block 84 | 85 | rm -rf "$DIR_NAME" 86 | } 87 | 88 | fetchChannelAndJoinTls() { 89 | local CHANNEL_NAME=$1 90 | local CORE_PEER_LOCALMSPID=$2 91 | local CORE_PEER_ADDRESS=$3 92 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4") 93 | local CORE_PEER_TLS_MSPCONFIGPATH=$(realpath "$5") 94 | local TLS_CA_CERT_PATH=$(realpath "$6") 95 | local ORDERER_URL=$7 96 | 97 | local CORE_PEER_TLS_CERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.crt 98 | local CORE_PEER_TLS_KEY_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.key 99 | local CORE_PEER_TLS_ROOTCERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/ca.crt 100 | 101 | local DIR_NAME=step-fetchChannelAndJoinTls-$CHANNEL_NAME-$CORE_PEER_ADDRESS 102 | 103 | echo "Fetching channel with name (TLS): ${CHANNEL_NAME}" 104 | echo " Orderer: $ORDERER_URL" 105 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID" 106 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS" 107 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH" 108 | echo " TLS_CA_CERT_PATH is: $TLS_CA_CERT_PATH" 109 | echo " CORE_PEER_TLS_CERT_FILE: $CORE_PEER_TLS_CERT_FILE" 110 | echo " CORE_PEER_TLS_KEY_FILE: $CORE_PEER_TLS_KEY_FILE" 111 | echo " CORE_PEER_TLS_ROOTCERT_FILE: $CORE_PEER_TLS_ROOTCERT_FILE" 112 | 113 | mkdir "$DIR_NAME" && cd "$DIR_NAME" 114 | 115 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}" --tls --cafile "$TLS_CA_CERT_PATH" 116 | peer channel join -b "${CHANNEL_NAME}"_newest.block --tls --cafile "$TLS_CA_CERT_PATH" 117 | 118 | rm -rf "$DIR_NAME" 119 | } 120 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/scripts/cli/channel_fns-v3.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | createChannelAndJoin() { 6 | local CHANNEL_NAME=$1 7 | 8 | local CORE_PEER_LOCALMSPID=$2 9 | local CORE_PEER_ADDRESS=$3 10 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4") 11 | 12 | local ORDERER_URL=$5 13 | 14 | local DIR_NAME=step-createChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS 15 | 16 | echo "Creating channel with name: ${CHANNEL_NAME}" 17 | echo " Orderer: $ORDERER_URL" 18 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID" 19 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS" 20 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH" 21 | 22 | mkdir "$DIR_NAME" && cd "$DIR_NAME" 23 | 24 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".pb . 25 | 26 | osnadmin channel join --channelID "${CHANNEL_NAME}" --config-block ./"$CHANNEL_NAME".pb -o "${ORDERER_URL}" 27 | cd .. && rm -rf "$DIR_NAME" 28 | } 29 | 30 | createChannelAndJoinTls() { 31 | local CHANNEL_NAME=$1 32 | local ORDERER_MSP_NAME=$2 33 | local ORDERER_ADMIN_ADDRESS=$3 34 | local ADMIN_TLS_SIGN_CERT=$(realpath "$4") 35 | local ADMIN_TLS_PRIVATE_KEY=$(realpath "$5") 36 | local TLS_CA_CERT_PATH=$(realpath "$6") 37 | 38 | local DIR_NAME=step-createChannelAndJoinTls-$CHANNEL_NAME-$ORDERER_MSP_NAME 39 | 40 | echo "Creating channel with name (TLS): ${CHANNEL_NAME}" 41 | echo " ORDERER_MSP_NAME: $ORDERER_MSP_NAME" 42 | echo " ORDERER_ADMIN_ADDRESS: $ORDERER_ADMIN_ADDRESS" 43 | echo " ADMIN_TLS_SIGN_CERT: $ADMIN_TLS_SIGN_CERT" 44 | echo " ADMIN_TLS_PRIVATE_KEY: $ADMIN_TLS_PRIVATE_KEY" 45 | echo " TLS_CA_CERT_PATH: $TLS_CA_CERT_PATH" 46 | 47 | if [ ! -d "$DIR_NAME" ]; then 48 | mkdir -p "$DIR_NAME" 49 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".pb "$DIR_NAME" 50 | fi 51 | 52 | osnadmin channel join \ 53 | --channelID "${CHANNEL_NAME}" \ 54 | --config-block "$DIR_NAME/$CHANNEL_NAME.pb" \ 55 | -o "${ORDERER_ADMIN_ADDRESS}" \ 56 | --client-cert "${ADMIN_TLS_SIGN_CERT}" \ 57 | --client-key "${ADMIN_TLS_PRIVATE_KEY}" \ 58 | --ca-file "${TLS_CA_CERT_PATH}" 59 | 60 | cd .. 61 | rm -rf "$DIR_NAME" 62 | } 63 | 64 | fetchChannelAndJoin() { 65 | local CHANNEL_NAME=$1 66 | local CORE_PEER_LOCALMSPID=$2 67 | local CORE_PEER_ADDRESS=$3 68 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4") 69 | local ORDERER_URL=$5 70 | 71 | local DIR_NAME=step-fetchChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS 72 | 73 | echo "Fetching channel with name: ${CHANNEL_NAME}" 74 | echo " Orderer: $ORDERER_URL" 75 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID" 76 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS" 77 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH" 78 | 79 | mkdir "$DIR_NAME" && cd "$DIR_NAME" 80 | 81 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}" 82 | peer channel join -b "${CHANNEL_NAME}"_newest.block 83 | 84 | cd .. && rm -rf "$DIR_NAME" 85 | } 86 | 87 | fetchChannelAndJoinTls() { 88 | local CHANNEL_NAME=$1 89 | local CORE_PEER_LOCALMSPID=$2 90 | local CORE_PEER_ADDRESS=$3 91 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4") 92 | local CORE_PEER_TLS_MSPCONFIGPATH=$(realpath "$5") 93 | local TLS_CA_CERT_PATH=$(realpath "$6") 94 | local ORDERER_URL=$7 95 | 96 | local CORE_PEER_TLS_CERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.crt 97 | local CORE_PEER_TLS_KEY_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.key 98 | local CORE_PEER_TLS_ROOTCERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/ca.crt 99 | 100 | local DIR_NAME=step-fetchChannelAndJoinTls-$CHANNEL_NAME-$CORE_PEER_ADDRESS 101 | 102 | echo "Fetching channel with name (TLS): ${CHANNEL_NAME}" 103 | echo " Orderer: $ORDERER_URL" 104 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID" 105 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS" 106 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH" 107 | echo " TLS_CA_CERT_PATH is: $TLS_CA_CERT_PATH" 108 | echo " CORE_PEER_TLS_CERT_FILE: $CORE_PEER_TLS_CERT_FILE" 109 | echo " CORE_PEER_TLS_KEY_FILE: $CORE_PEER_TLS_KEY_FILE" 110 | echo " CORE_PEER_TLS_ROOTCERT_FILE: $CORE_PEER_TLS_ROOTCERT_FILE" 111 | 112 | mkdir "$DIR_NAME" && cd "$DIR_NAME" 113 | 114 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}" --tls --cafile "$TLS_CA_CERT_PATH" 115 | peer channel join -b "${CHANNEL_NAME}"_newest.block --tls --cafile "$TLS_CA_CERT_PATH" 116 | 117 | cd .. && rm -rf "$DIR_NAME" 118 | } 119 | -------------------------------------------------------------------------------- /src/setup-docker/templates/fabric-docker/snapshot-scripts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | __getOrdererAndPeerNodes() { 4 | echo " 5 | <%_ orgs.forEach((org) => { _%> 6 | <%_ org.ordererGroups.forEach((g) => g.orderers.forEach((o) => { _%> 7 | <%= o.address %> 8 | <%_ })) _%> 9 | <%_ org.peers.forEach((p) => { _%> 10 | <%= p.address %> 11 | <%_ }) _%> 12 | <%_ }) _%> 13 | " 14 | } 15 | 16 | __getCASQLiteNodes() { 17 | echo " 18 | <%_ orgs.filter((org) => org.ca.db === 'sqlite').forEach((org) => { _%> 19 | <%= org.ca.address %> 20 | <%_ }) _%> 21 | " 22 | } 23 | 24 | __getCAPostgresNodes() { 25 | echo " 26 | <%_ orgs.filter((org) => org.ca.db === 'postgres').forEach((org) => { _%> 27 | db.<%= org.ca.address %> 28 | <%_ }) _%> 29 | " 30 | } 31 | 32 | __createSnapshot() { 33 | cd "$FABLO_NETWORK_ROOT/.." 34 | backup_dir="${1:-"snapshot-$(date -u +"%Y%m%d%H%M%S")"}" 35 | 36 | if [ -d "$backup_dir" ] && [ "$(ls -A "$backup_dir")" ]; then 37 | echo "Error: Directory '$backup_dir' already exists and is not empty!" 38 | exit 1 39 | fi 40 | 41 | mkdir -p "$backup_dir" 42 | cp -R ./fablo-target "$backup_dir/" 43 | 44 | for node in $(__getCASQLiteNodes); do 45 | echo "Saving state of $node..." 46 | mkdir -p "$backup_dir/$node" 47 | docker cp "$node:/etc/hyperledger/fabric-ca-server/fabric-ca-server.db" "$backup_dir/$node/fabric-ca-server.db" 48 | done 49 | 50 | for node in $(__getCAPostgresNodes); do 51 | echo "Saving state of $node..." 52 | mkdir -p "$backup_dir/$node/pg-data" 53 | docker exec "$node" pg_dump -c --if-exists -U postgres fabriccaserver >"$backup_dir/$node/fabriccaserver.sql" 54 | done 55 | 56 | for node in $(__getOrdererAndPeerNodes); do 57 | echo "Saving state of $node..." 58 | docker cp "$node:/var/hyperledger/production/" "$backup_dir/$node/" 59 | done 60 | } 61 | 62 | __cloneSnapshot() { 63 | cd "$FABLO_NETWORK_ROOT/.." 64 | target_dir="$1" 65 | hook_cmd="$2" 66 | 67 | if [ -d "$target_dir/fablo-target" ]; then 68 | echo "Error: Directory '$target_dir/fablo-target' already exists! Execute 'fablo prune' to remove the current network." 69 | exit 1 70 | fi 71 | 72 | cp -R ./fablo-target "$target_dir/fablo-target" 73 | 74 | if [ -n "$hook_cmd" ]; then 75 | echo "Executing pre-restore hook: '$hook_cmd'" 76 | (cd "$target_dir" && eval "$hook_cmd") 77 | fi 78 | 79 | (cd "$target_dir/fablo-target/fabric-docker" && docker compose up --no-start) 80 | 81 | for node in $(__getCASQLiteNodes); do 82 | echo "Restoring $node..." 83 | if [ ! -d "$node" ]; then 84 | echo "Warning: Cannot restore '$node', directory does not exist!" 85 | else 86 | docker cp "./$node/fabric-ca-server.db" "$node:/etc/hyperledger/fabric-ca-server/fabric-ca-server.db" 87 | fi 88 | done 89 | 90 | for node in $(__getCAPostgresNodes); do 91 | echo "Restoring $node..." 92 | if [ ! -d "$node" ]; then 93 | echo "Warning: Cannot restore '$node', directory does not exist!" 94 | else 95 | docker cp "./$node/fabriccaserver.sql" "$node:/docker-entrypoint-initdb.d/fabriccaserver.sql" 96 | fi 97 | done 98 | 99 | for node in $(__getOrdererAndPeerNodes); do 100 | echo "Restoring $node..." 101 | if [ ! -d "$node" ]; then 102 | echo "Warning: Cannot restore '$node', directory does not exist!" 103 | else 104 | docker cp "./$node/" "$node:/var/hyperledger/production/" 105 | fi 106 | done 107 | } 108 | 109 | createSnapshot() { 110 | (set -eu && __createSnapshot "$1") 111 | } 112 | 113 | cloneSnapshot() { 114 | (set -eu && __cloneSnapshot "$1" "$2") 115 | } 116 | -------------------------------------------------------------------------------- /src/setup-docker/templates/hooks/post-generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # The code from this file was called after Fablo generated Hyperledger Fabric configuration 4 | echo "Executing post-generate hook" 5 | 6 | <%- hooks.postGenerate %> 7 | -------------------------------------------------------------------------------- /src/setup-k8s/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | import * as config from "../config"; 3 | import { getBuildInfo } from "../version/buildUtil"; 4 | import parseFabloConfig from "../utils/parseFabloConfig"; 5 | import { Capabilities, FabloConfigExtended, HooksConfig, Global, OrgConfig } from "../types/FabloConfigExtended"; 6 | import { extendConfig } from "../extend-config/"; 7 | 8 | const ValidateGeneratorPath = require.resolve("../validate"); 9 | 10 | export default class SetupDockerGenerator extends Generator { 11 | constructor(args: string[], opts: Generator.GeneratorOptions) { 12 | super(args, opts); 13 | this.argument("fabloConfig", { 14 | type: String, 15 | optional: true, 16 | description: "Fablo config file path", 17 | default: "../../network/fablo-config.json", 18 | }); 19 | 20 | this.composeWith(ValidateGeneratorPath, { arguments: [this.options.fabloConfig] }); 21 | } 22 | 23 | async writing(): Promise { 24 | const fabloConfigPath = `${this.env.cwd}/${this.options.fabloConfig}`; 25 | const json = parseFabloConfig(this.fs.read(fabloConfigPath)); 26 | const config = extendConfig(json); 27 | const { global, orgs } = config; 28 | 29 | const dateString = new Date() 30 | .toISOString() 31 | .substring(0, 16) 32 | .replace(/[^0-9]+/g, ""); 33 | const composeNetworkName = `fablo_network_${dateString}`; 34 | 35 | console.log(`Used network config: ${fabloConfigPath}`); 36 | console.log(`Fabric version is: ${global.fabricVersion}`); 37 | 38 | this._copyGitIgnore(); 39 | 40 | // ======= fabric-k8s =========================================================== 41 | this._copyEnvFile(global, orgs, composeNetworkName); 42 | 43 | // ======= scripts ================================================================== 44 | // this._copyCommandsGeneratedScript(config); 45 | this._copyCommandsGeneratedScript(config); 46 | this._copyUtilityScripts(config.global.capabilities); 47 | 48 | // ======= hooks ==================================================================== 49 | this._copyHooks(config.hooks); 50 | 51 | this.on("end", () => { 52 | console.log("Done & done !!! Try the network out: "); 53 | console.log("-> fablo up - to start network"); 54 | console.log("-> fablo help - to view all commands"); 55 | }); 56 | } 57 | 58 | _copyGitIgnore(): void { 59 | this.fs.copyTpl(this.templatePath("fabric-config/.gitignore"), this.destinationPath("fabric-config/.gitignore")); 60 | } 61 | 62 | _copyEnvFile(global: Global, orgsTransformed: OrgConfig[], composeNetworkName: string): void { 63 | const settings = { 64 | composeNetworkName, 65 | fabricCaVersion: global.fabricCaVersion, 66 | global, 67 | orgs: orgsTransformed, 68 | paths: global.paths, 69 | fabloVersion: config.fabloVersion, 70 | fabloBuild: getBuildInfo(), 71 | fabloRestVersion: "0.1.2", 72 | hyperledgerExplorerVersion: "1.1.8", 73 | fabricCouchDbVersion: "0.4.18", 74 | couchDbVersion: "3.1", 75 | fabricCaPostgresVersion: "14", 76 | }; 77 | this.fs.copyTpl(this.templatePath("fabric-k8s/.env"), this.destinationPath("fabric-k8s/.env"), settings); 78 | } 79 | 80 | _copyCommandsGeneratedScript(config: FabloConfigExtended): void { 81 | this.fs.copyTpl( 82 | this.templatePath("fabric-k8s/scripts/base-functions.sh"), 83 | this.destinationPath("fabric-k8s/scripts/base-functions.sh"), 84 | config, 85 | ); 86 | } 87 | _copyUtilityScripts(capabilities: Capabilities): void { 88 | this.fs.copyTpl(this.templatePath("fabric-k8s.sh"), this.destinationPath("fabric-k8s.sh")); 89 | this.fs.copyTpl( 90 | this.templatePath("fabric-k8s/scripts/base-help.sh"), 91 | this.destinationPath("fabric-k8s/scripts/base-help.sh"), 92 | ); 93 | 94 | this.fs.copyTpl( 95 | this.templatePath("fabric-k8s/scripts/util.sh"), 96 | this.destinationPath("fabric-k8s/scripts/util.sh"), 97 | ); 98 | 99 | this.fs.copyTpl( 100 | this.templatePath(`fabric-k8s/scripts/chaincode-functions.sh`), 101 | this.destinationPath("fabric-k8s/scripts/chaincode-functions.sh"), 102 | ); 103 | 104 | this.fs.copyTpl( 105 | this.templatePath(`fabric-k8s/scripts/chaincode-functions-${capabilities.isV2 ? "v2" : "v1.4"}.sh`), 106 | this.destinationPath("fabric-k8s/scripts/chaincode-functions.sh"), 107 | ); 108 | } 109 | 110 | _copyHooks(hooks: HooksConfig): void { 111 | this.fs.copyTpl(this.templatePath("hooks/post-generate.sh"), this.destinationPath("hooks/post-generate.sh"), { 112 | hooks, 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/setup-k8s/templates/fabric-config/.gitignore: -------------------------------------------------------------------------------- 1 | /config 2 | /crypto-config 3 | -------------------------------------------------------------------------------- /src/setup-k8s/templates/fabric-k8s.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | FABLO_NETWORK_ROOT="$(cd "$(dirname "$0")" && pwd)" 6 | 7 | source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-help.sh" 8 | source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-functions.sh" 9 | source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/chaincode-functions.sh" 10 | source "$FABLO_NETWORK_ROOT/fabric-k8s/.env" 11 | 12 | # location of generated configurations 13 | CONFIG_DIR="$FABLO_NETWORK_ROOT/fabric-config" 14 | 15 | RESETBG="$(printf '\e[0m\n')" 16 | BLUE="$(printf '\033[34m')" 17 | 18 | networkUp() { 19 | printHeadline "Checking dependencies..." "U1F984" 20 | verifyKubernetesConnectivity 21 | printHeadline "Starting Network..." "U1F984" 22 | deployPeer 23 | deployOrderer 24 | installChannels 25 | installChaincodes 26 | printHeadline "Done! Enjoy your fresh network" "U1F984" 27 | } 28 | 29 | networkDown() { 30 | printHeadline "Destroying network" "U1F913" 31 | destroyNetwork 32 | } 33 | 34 | if [ "$1" = "up" ]; then 35 | networkUp 36 | elif [ "$1" = "down" ]; then 37 | networkDown 38 | elif [ "$1" = "reset" ]; then 39 | networkDown 40 | sleep 60 41 | networkUp 42 | elif [ "$1" = "start" ]; then 43 | startNetwork 44 | elif [ "$1" = "stop" ]; then 45 | stopNetwork 46 | elif [ "$1" = "chaincodes" ] && [ "$2" = "install" ]; then 47 | installChaincodes 48 | elif [ "$1" = "chaincode" ] && [ "$2" = "install" ]; then 49 | installChaincode "$3" "$4" 50 | elif [ "$1" = "chaincode" ] && [ "$2" = "upgrade" ]; then 51 | upgradeChaincode "$3" "$4" 52 | elif [ "$1" = "chaincode" ] && [ "$2" = "dev" ]; then 53 | runDevModeChaincode "$3" "$4" 54 | elif [ "$1" = "channel" ]; then 55 | channelQuery "${@:2}" 56 | elif [ "$1" = "snapshot" ]; then 57 | createSnapshot "$2" 58 | elif [ "$1" = "clone-to" ]; then 59 | cloneSnapshot "$2" "${3:-""}" 60 | elif [ "$1" = "help" ]; then 61 | printHelp 62 | elif [ "$1" = "--help" ]; then 63 | printHelp 64 | else 65 | echo "No command specified" 66 | echo "Basic commands are: up, down, start, stop, reset" 67 | echo "To list channel query helper commands type: 'fablo channel --help'" 68 | echo "Also check: 'chaincode install'" 69 | echo "Use 'help' or '--help' for more information" 70 | fi 71 | -------------------------------------------------------------------------------- /src/setup-k8s/templates/fabric-k8s/.env: -------------------------------------------------------------------------------- 1 | NAMESPACE=default 2 | 3 | REPOSITORY="https://kfsoftware.github.io/hlf-helm-charts" 4 | STORAGE_CLASS=$(kubectl describe sc | grep Name | tr -s ' ' | cut -d ':' -f 2 | cut -d ' ' -f 2) 5 | 6 | FABLO_VERSION=<%= fabloVersion %> 7 | FABLO_BUILD=<%= fabloBuild %> 8 | FABLO_CONFIG=<%= paths.fabloConfig %> 9 | ORDERER_IMAGE=hyperledger/fabric-orderer 10 | ORDERER_VERSION=<%= global.fabricVersion %> 11 | PEER_IMAGE=quay.io/kfsoftware/fabric-peer 12 | PEER_VERSION=2.4.1-v0.0.3 13 | # PEER_VERSION=<%= global.fabricVersion %> 14 | CA_IMAGE=hyperledger/fabric-ca 15 | CA_VERSION=<%= global.fabricCaVersion %> 16 | LOGGING_LEVEL=<%= global.monitoring.loglevel %> 17 | 18 | CHAINCODES_BASE_DIR=<%= paths.chaincodesBaseDir %> 19 | 20 | ROOT_CA_ADMIN_NAME=admin 21 | ROOT_CA_ADMIN_PASSWORD=adminpw 22 | 23 | <% orgs.forEach(function(org){ %> 24 | <%= org.ca.caAdminNameVar %>=admin 25 | <%= org.ca.caAdminPassVar %>=adminpw 26 | <% }); %> -------------------------------------------------------------------------------- /src/setup-k8s/templates/fabric-k8s/scripts/base-help.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | printHelp() { 4 | echo "Fablo is powered by SoftwareMill" 5 | 6 | echo "" 7 | echo "usage: ./fabric-k8.sh " 8 | echo "" 9 | 10 | echo "Commands: " 11 | echo "" 12 | echo "./fabric-k8.sh up" 13 | echo -e "\t Use for first run. Creates all needed artifacts (certs, genesis block) and starts network for the first time." 14 | echo -e "\t After 'up' commands start/stop are used to manage network and rerun to rerun it" 15 | echo "" 16 | echo "./fabric-k8.sh down" 17 | echo -e "\t Back to empty state - destorys created containers, prunes generated certificates, configs." 18 | echo "" 19 | echo "./fabric-k8.sh channel --help" 20 | echo -e "\t Detailed help for channel management scripts." 21 | echo "" 22 | } 23 | -------------------------------------------------------------------------------- /src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions-v2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | buildAndInstallChaincode() { 4 | local CHAINCODE_NAME="$1" 5 | local PEER="$2" 6 | local CHAINCODE_LANG="$3" 7 | local CHAINCODE_DIR_PATH="$4" 8 | local CHAINCODE_VERSION="$5" 9 | local CHAINCODE_LABEL="${CHAINCODE_NAME}_$CHAINCODE_VERSION" 10 | local USER="$6" 11 | local CONFIG="$7" 12 | 13 | if [ -z "$CHAINCODE_NAME" ]; then 14 | echo "Error: chaincode name is not provided" 15 | exit 1 16 | fi 17 | 18 | if [ -z "$PEER" ]; then 19 | echo "Error: peer number is not provided e.g 0, 1" 20 | exit 1 21 | fi 22 | 23 | if [ -z "$CHAINCODE_LANG" ]; then 24 | echo "Error: chaincode language is not provided" 25 | exit 1 26 | fi 27 | 28 | if [ -z "$CHAINCODE_VERSION" ]; then 29 | echo "Error: chaincode version is not provided e.g 1.0, 2.0" 30 | exit 1 31 | fi 32 | 33 | kubectl hlf chaincode install --path="$CHAINCODE_DIR_PATH" \ 34 | --config="$CONFIG" --language="$CHAINCODE_LANG" --label="$CHAINCODE_LABEL" --user="$USER" --peer="$PEER" 35 | } 36 | 37 | approveChaincode() { 38 | local CHAINCODE_NAME="$1" 39 | local PEER="$2" 40 | local CHAINCODE_VERSION="$3" 41 | local CHANNEL_NAME="$4" 42 | local USER="$5" 43 | local CONFIG="$6" 44 | local MSP="$7" 45 | local SEQUENCE 46 | 47 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p' )" 48 | SEQUENCE=$((SEQUENCE +1)) 49 | 50 | if [ -z "$CHAINCODE_NAME" ]; then 51 | echo "Error: chaincode name is not provided" 52 | exit 1 53 | fi 54 | 55 | if [ -z "$PEER" ]; then 56 | echo "Error: peer number is not provided e.g 0, 1" 57 | exit 1 58 | fi 59 | 60 | if [ -z "$CHAINCODE_VERSION" ]; then 61 | echo "Error: chaincode version is not provided e.g 1.0, 2.0" 62 | exit 1 63 | fi 64 | 65 | if [ -z "$CHANNEL_NAME" ]; then 66 | echo "Error: channel name is not provided" 67 | exit 1 68 | fi 69 | 70 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)" 71 | 72 | printItalics "Approving chaincode $CHAINCODE_NAME on $PEER" "U1F618" 73 | 74 | kubectl hlf chaincode approveformyorg --config="$CONFIG" --user="$USER" --peer="$PEER" \ 75 | --package-id="$PACKAGE_ID" --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ 76 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" 77 | } 78 | 79 | commitChaincode() { 80 | 81 | local CHAINCODE_NAME="$1" 82 | local PEER=$2 83 | local CHAINCODE_VERSION="$3" 84 | local CHANNEL_NAME="$4" 85 | local USER="$5" 86 | local CONFIG="$6" 87 | local MSP="$7" 88 | local SEQUENCE 89 | 90 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p' )" 91 | SEQUENCE=$((SEQUENCE +1)) 92 | 93 | if [ -z "$CHAINCODE_NAME" ]; then 94 | echo "Error: chaincode name is not provided" 95 | exit 1 96 | fi 97 | 98 | if [ -z "$PEER" ]; then 99 | echo "Error: peer number is not provided e.g 0, 1" 100 | exit 1 101 | fi 102 | 103 | if [ -z "$CHAINCODE_VERSION" ]; then 104 | echo "Error: chaincode version is not provided e.g 1.0, 2.0" 105 | exit 1 106 | fi 107 | 108 | if [ -z "$CHANNEL_NAME" ]; then 109 | echo "Error: channel name is not provided" 110 | exit 1 111 | fi 112 | 113 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)" 114 | 115 | kubectl hlf chaincode commit --config="$CONFIG" --user="$USER" --mspid="$MSP" \ 116 | --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ 117 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" 118 | } 119 | -------------------------------------------------------------------------------- /src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | buildAndInstallChaincode() { 4 | local CHAINCODE_NAME="$1" 5 | local PEER="$2" 6 | local CHAINCODE_LANG="$3" 7 | local CHAINCODE_DIR_PATH="$4" 8 | local CHAINCODE_VERSION="$5" 9 | local CHAINCODE_LABEL="${CHAINCODE_NAME}_$CHAINCODE_VERSION" 10 | local USER="$6" 11 | local CONFIG="$7" 12 | 13 | if [ -z "$CHAINCODE_NAME" ]; then 14 | echo "Error: chaincode name is not provided" 15 | exit 1 16 | fi 17 | 18 | if [ -z "$PEER" ]; then 19 | echo "Error: peer number is not provided e.g 0, 1" 20 | exit 1 21 | fi 22 | 23 | if [ -z "$CHAINCODE_LANG" ]; then 24 | echo "Error: chaincode language is not provided" 25 | exit 1 26 | fi 27 | 28 | if [ -z "$CHAINCODE_VERSION" ]; then 29 | echo "Error: chaincode version is not provided e.g 1.0, 2.0" 30 | exit 1 31 | fi 32 | 33 | kubectl hlf chaincode install --path="$CHAINCODE_DIR_PATH" \ 34 | --config="$CONFIG" --language="$CHAINCODE_LANG" --label="$CHAINCODE_LABEL" --user="$USER" --peer="$PEER" 35 | } 36 | 37 | approveChaincode() { 38 | local CHAINCODE_NAME="$1" 39 | local PEER="$2" 40 | local CHAINCODE_VERSION="$3" 41 | local CHANNEL_NAME="$4" 42 | local USER="$5" 43 | local CONFIG="$6" 44 | local MSP="$7" 45 | local SEQUENCE 46 | 47 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $3}' | sed -n '2p' )" 48 | SEQUENCE=$((SEQUENCE +1)) 49 | 50 | if [ -z "$CHAINCODE_NAME" ]; then 51 | echo "Error: chaincode name is not provided" 52 | exit 1 53 | fi 54 | 55 | if [ -z "$PEER" ]; then 56 | echo "Error: peer number is not provided e.g 0, 1" 57 | exit 1 58 | fi 59 | 60 | if [ -z "$CHAINCODE_VERSION" ]; then 61 | echo "Error: chaincode version is not provided e.g 1.0, 2.0" 62 | exit 1 63 | fi 64 | 65 | if [ -z "$CHANNEL_NAME" ]; then 66 | echo "Error: channel name is not provided" 67 | exit 1 68 | fi 69 | 70 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER.$NAMESPACE" | awk '{print $1}' | grep chaincode)" 71 | 72 | printItalics "Approving chaincode $CHAINCODE_NAME on $PEER" "U1F618" 73 | 74 | kubectl hlf chaincode approveformyorg --config="$CONFIG" --user="$USER" --peer="$PEER" \ 75 | --package-id="$PACKAGE_ID" --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ 76 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" 77 | } 78 | 79 | commitChaincode() { 80 | 81 | local CHAINCODE_NAME="$1" 82 | local PEER=$2 83 | local CHAINCODE_VERSION="$3" 84 | local CHANNEL_NAME="$4" 85 | local USER="$5" 86 | local CONFIG="$6" 87 | local MSP="$7" 88 | local SEQUENCE 89 | 90 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $3}' | sed -n '2p' )" 91 | SEQUENCE=$((SEQUENCE +1)) 92 | 93 | if [ -z "$CHAINCODE_NAME" ]; then 94 | echo "Error: chaincode name is not provided" 95 | exit 1 96 | fi 97 | 98 | if [ -z "$PEER" ]; then 99 | echo "Error: peer number is not provided e.g 0, 1" 100 | exit 1 101 | fi 102 | 103 | if [ -z "$CHAINCODE_VERSION" ]; then 104 | echo "Error: chaincode version is not provided e.g 1.0, 2.0" 105 | exit 1 106 | fi 107 | 108 | if [ -z "$CHANNEL_NAME" ]; then 109 | echo "Error: channel name is not provided" 110 | exit 1 111 | fi 112 | 113 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER.$NAMESPACE" | awk '{print $1}' | grep chaincode)" 114 | 115 | kubectl hlf chaincode commit --config="$CONFIG" --user="$USER" --mspid="$MSP" \ 116 | --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ 117 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" 118 | } 119 | -------------------------------------------------------------------------------- /src/setup-k8s/templates/fabric-k8s/scripts/util.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | retry() { 4 | local n=0 5 | local try=$1 6 | local cmd="${*:2}" 7 | [[ $# -le 1 ]] 8 | 9 | until [[ $n -ge $try ]]; do 10 | if [[ $cmd ]]; then 11 | break 12 | else 13 | echo $(printf '\033[31m') "Previous command FAILED" 14 | ((n++)) 15 | echo $(printf '\033[34m') "RETRYING..." 16 | sleep 1 17 | fi 18 | done 19 | } 20 | retry "$*" 21 | -------------------------------------------------------------------------------- /src/setup-k8s/templates/hooks/post-generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # The code from this file was called after Fablo generated Hyperledger Fabric configuration 4 | echo "Executing post-generate hook" 5 | 6 | <%- hooks.postGenerate %> 7 | -------------------------------------------------------------------------------- /src/setup-network/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | import parseFabloConfig from "../utils/parseFabloConfig"; 3 | 4 | const DockerGeneratorPath = require.resolve("../setup-docker"); 5 | const KubernetesGeneratorPath = require.resolve("../setup-k8s"); 6 | 7 | export default class SetupDockerGenerator extends Generator { 8 | constructor(args: string[], opts: Generator.GeneratorOptions) { 9 | super(args, opts); 10 | this.argument("fabloConfig", { 11 | type: String, 12 | optional: true, 13 | description: "Fablo config file path", 14 | default: "../../network/fablo-config.json", 15 | }); 16 | } 17 | 18 | redirectToProperGenerator(): void { 19 | const fabloConfigPath = `${this.env.cwd}/${this.options.fabloConfig}`; 20 | const json = parseFabloConfig(this.fs.read(fabloConfigPath)); 21 | 22 | if (json?.global?.engine === "kubernetes") { 23 | this.composeWith(KubernetesGeneratorPath); 24 | } else { 25 | this.composeWith(DockerGeneratorPath); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/types/ExplorerConfig.ts: -------------------------------------------------------------------------------- 1 | import { OrgConfig } from "./FabloConfigExtended"; 2 | 3 | interface ExplorerConfig { 4 | "network-configs": { [name: string]: NetworkConfig }; 5 | license: string; 6 | } 7 | 8 | interface NetworkConfig { 9 | name: string; 10 | profile: string; 11 | } 12 | 13 | export function createExplorerConfig(orgs: OrgConfig[]): ExplorerConfig { 14 | const networkConfigs: { [name: string]: NetworkConfig } = {}; 15 | orgs.forEach((o) => { 16 | const orgName = o.name.toLowerCase(); 17 | networkConfigs[`network-${orgName}`] = { 18 | name: `Network of ${o.name}`, 19 | profile: `/opt/explorer/app/platform/fabric/connection-profile/connection-profile-${orgName}.json`, 20 | }; 21 | }); 22 | return { 23 | "network-configs": networkConfigs, 24 | license: "Apache-2.0", 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/types/FabloConfigExtended.ts: -------------------------------------------------------------------------------- 1 | export interface FabricVersions { 2 | fabricVersion: string; 3 | fabricToolsVersion: string; 4 | fabricCaVersion: string; 5 | fabricCcenvVersion: string; 6 | fabricBaseosVersion: string; 7 | fabricJavaenvVersion: string; 8 | fabricNodeenvVersion: string; 9 | fabricRecommendedNodeVersion: string; 10 | } 11 | 12 | interface CapabilitiesV2 { 13 | application: "V2_0"; 14 | channel: "V2_0"; 15 | orderer: "V2_0"; 16 | isV2: true; 17 | isV3: false; 18 | } 19 | 20 | interface CapabilitiesV_2_5 { 21 | application: "V2_5"; 22 | channel: "V2_0"; 23 | orderer: "V2_0"; 24 | isV2: true; 25 | isV3: false; 26 | } 27 | 28 | interface CapabilitiesV3_0 { 29 | application: "V2_5"; 30 | channel: "V3_0"; 31 | orderer: "V2_0"; 32 | isV2: false; 33 | isV3: true; 34 | } 35 | 36 | export type Capabilities = CapabilitiesV2 | CapabilitiesV_2_5 | CapabilitiesV3_0; 37 | 38 | export interface Global extends FabricVersions { 39 | tls: boolean; 40 | engine: "kubernetes" | "docker"; 41 | monitoring: { loglevel: string }; 42 | paths: { fabloConfig: string; chaincodesBaseDir: string }; 43 | capabilities: Capabilities; 44 | tools: { explorer?: ExplorerConfig }; 45 | } 46 | 47 | export interface OrdererConfig { 48 | name: string; 49 | domain: string; 50 | address: string; 51 | adminPort: number; 52 | port: number; 53 | fullAddress: string; 54 | orgName: string; 55 | orgMspName: string; 56 | } 57 | 58 | export interface CAConfig { 59 | address: string; 60 | caAdminNameVar: string; 61 | caAdminPassVar: string; 62 | exposePort: number; 63 | fullAddress: string; 64 | port: number; 65 | prefix: string; 66 | db: "sqlite" | "postgres"; 67 | } 68 | 69 | export interface PeerConfig { 70 | address: string; 71 | couchDbExposePort: number; 72 | db: PeerDbConfig; 73 | fullAddress: string; 74 | isAnchorPeer: boolean; 75 | name: string; 76 | port: number; 77 | gatewayEnabled: boolean; 78 | } 79 | 80 | export interface PeerDbConfig { 81 | type: "LevelDb" | "CouchDb"; 82 | image?: string; 83 | } 84 | 85 | export interface CLIConfig { 86 | address: string; 87 | } 88 | 89 | export interface ChannelConfig { 90 | name: string; 91 | profileName: string; 92 | ordererGroup: OrdererGroup; 93 | ordererHead: OrdererConfig; 94 | orgs: OrgConfig[]; 95 | instantiatingOrg: OrgConfig; 96 | } 97 | 98 | export interface PrivateCollectionConfig { 99 | name: string; 100 | policy: string; 101 | requiredPeerCount: number; 102 | maxPeerCount: number; 103 | blockToLive: number; 104 | memberOnlyRead?: boolean; 105 | memberOnlyWrite?: boolean; 106 | } 107 | 108 | export interface FabloRestLoggingConfig { 109 | info?: "console" | string; 110 | warn?: "console" | string; 111 | error?: "console" | string; 112 | debug?: "console" | string; 113 | } 114 | 115 | export interface FabloRestConfig { 116 | address: string; 117 | port: number; 118 | mspId: string; 119 | fabricCaUrl: string; 120 | fabricCaName: string; 121 | discoveryUrls: string; 122 | discoverySslTargetNameOverrides: string; 123 | discoveryTlsCaCertFiles: string; 124 | logging: FabloRestLoggingConfig; 125 | } 126 | 127 | export interface ExplorerConfig { 128 | address: string; 129 | port: number; 130 | } 131 | 132 | export interface OrgConfig { 133 | anchorPeers: PeerConfig[]; 134 | bootstrapPeers: string; 135 | ca: CAConfig; 136 | cli: CLIConfig; 137 | cryptoConfigFileName: string; 138 | domain: string; 139 | headPeer?: PeerConfig; 140 | mspName: string; 141 | name: string; 142 | peers: PeerConfig[]; 143 | peersCount: number; 144 | ordererGroups: OrdererGroup[]; 145 | tools: { fabloRest?: FabloRestConfig; explorer?: ExplorerConfig }; 146 | } 147 | 148 | export interface ChaincodeConfig { 149 | directory: string; 150 | name: string; 151 | version: string; 152 | lang: string; 153 | channel: ChannelConfig; 154 | init?: string; 155 | initRequired?: boolean; 156 | endorsement?: string; 157 | instantiatingOrg: OrgConfig; 158 | privateDataConfigFile?: string; 159 | privateData: PrivateCollectionConfig[]; 160 | } 161 | 162 | export interface OrdererGroup { 163 | name: string; 164 | consensus: "solo" | "etcdraft" | "BFT"; 165 | profileName: string; 166 | genesisBlockName: string; 167 | configtxOrdererDefaults: string; 168 | hostingOrgs: string[]; 169 | orderers: OrdererConfig[]; 170 | ordererHeads: OrdererConfig[]; 171 | } 172 | 173 | export interface HooksConfig { 174 | postGenerate: string; 175 | } 176 | 177 | export interface FabloConfigExtended { 178 | global: Global; 179 | ordererGroups: OrdererGroup[]; 180 | orderedHeadsDistinct: OrdererConfig[]; 181 | orgs: OrgConfig[]; 182 | channels: ChannelConfig[]; 183 | chaincodes: ChaincodeConfig[]; 184 | hooks: HooksConfig; 185 | } 186 | -------------------------------------------------------------------------------- /src/types/FabloConfigJson.ts: -------------------------------------------------------------------------------- 1 | export interface GlobalJson { 2 | fabricVersion: string; 3 | tls: boolean; 4 | peerDevMode: boolean; 5 | engine?: "kubernetes" | "docker"; 6 | monitoring?: { loglevel: string }; 7 | tools?: { explorer?: boolean }; 8 | } 9 | 10 | export interface OrganizationDetailsJson { 11 | name: string; 12 | mspName: string; 13 | domain: string; 14 | } 15 | 16 | export interface CAJson { 17 | prefix: string; 18 | db: "sqlite" | "postgres"; 19 | } 20 | 21 | export interface OrdererJson { 22 | groupName: string; 23 | prefix: string; 24 | type: "solo" | "raft" | "BFT"; 25 | instances: number; 26 | } 27 | 28 | export interface PeerJson { 29 | prefix: string; 30 | instances: number; 31 | db: "LevelDb" | "CouchDb"; 32 | anchorPeerInstances?: number; 33 | } 34 | 35 | export interface OrgJson { 36 | organization: OrganizationDetailsJson; 37 | ca: CAJson; 38 | orderers: OrdererJson[] | undefined; 39 | peer?: PeerJson; 40 | tools?: { fabloRest?: boolean; explorer?: boolean }; 41 | } 42 | 43 | export interface ChannelJson { 44 | name: string; 45 | ordererGroup?: string; 46 | orgs: { name: string; peers: string[] }[]; 47 | } 48 | 49 | export interface PrivateDataJson { 50 | name: string; 51 | orgNames: string[]; 52 | } 53 | 54 | export interface ChaincodeJson { 55 | name: string; 56 | version: string; 57 | lang: "node" | "java" | "golang"; 58 | channel: string; 59 | init?: string; 60 | initRequired?: boolean; 61 | endorsement?: string; 62 | directory: string; 63 | privateData: PrivateDataJson[]; 64 | } 65 | 66 | export interface HooksJson { 67 | postGenerate?: string; 68 | } 69 | 70 | export interface FabloConfigJson { 71 | $schema: string; 72 | global: GlobalJson; 73 | orgs: OrgJson[]; 74 | channels: ChannelJson[]; 75 | chaincodes: ChaincodeJson[]; 76 | hooks: HooksJson; 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/parseFabloConfig.test.ts: -------------------------------------------------------------------------------- 1 | import parseFabloConfig from "./parseFabloConfig"; 2 | 3 | describe("parseFabloConfig", () => { 4 | it("should parse valid JSON config", () => { 5 | const jsonConfig = `{ 6 | "global": { 7 | "fabricVersion": "2.5.9", 8 | "tls": false, 9 | "engine": "docker" 10 | }, 11 | "orgs": [ 12 | { 13 | "organization": { 14 | "name": "Org1", 15 | "domain": "org1.example.com" 16 | } 17 | } 18 | ] 19 | }`; 20 | 21 | const result = parseFabloConfig(jsonConfig); 22 | 23 | expect(result).toEqual({ 24 | global: { 25 | fabricVersion: "2.5.9", 26 | tls: false, 27 | engine: "docker" 28 | }, 29 | orgs: [ 30 | { 31 | organization: { 32 | name: "Org1", 33 | domain: "org1.example.com" 34 | } 35 | } 36 | ] 37 | }); 38 | }); 39 | 40 | it("should parse valid YAML config", () => { 41 | const yamlConfig = ` 42 | global: 43 | fabricVersion: "2.5.9" 44 | tls: false 45 | engine: docker 46 | orgs: 47 | - organization: 48 | name: Org1 49 | domain: org1.example.com 50 | `; 51 | 52 | const result = parseFabloConfig(yamlConfig); 53 | 54 | expect(result).toEqual({ 55 | global: { 56 | fabricVersion: "2.5.9", 57 | tls: false, 58 | engine: "docker" 59 | }, 60 | orgs: [ 61 | { 62 | organization: { 63 | name: "Org1", 64 | domain: "org1.example.com" 65 | } 66 | } 67 | ] 68 | }); 69 | }); 70 | 71 | it("should parse complex YAML config with multiple orgs and channels", () => { 72 | const yamlConfig = ` 73 | global: 74 | fabricVersion: "2.4.7" 75 | tls: true 76 | engine: kubernetes 77 | orgs: 78 | - organization: 79 | name: Org1 80 | domain: org1.example.com 81 | peers: 82 | - name: peer0 83 | port: 7041 84 | - name: peer1 85 | port: 7042 86 | - organization: 87 | name: Org2 88 | domain: org2.example.com 89 | peers: 90 | - name: peer0 91 | port: 8041 92 | channels: 93 | - name: mychannel 94 | orgs: ["Org1", "Org2"] 95 | `; 96 | 97 | const result = parseFabloConfig(yamlConfig); 98 | 99 | expect(result).toEqual({ 100 | global: { 101 | fabricVersion: "2.4.7", 102 | tls: true, 103 | engine: "kubernetes" 104 | }, 105 | orgs: [ 106 | { 107 | organization: { 108 | name: "Org1", 109 | domain: "org1.example.com", 110 | peers: [ 111 | { 112 | name: "peer0", 113 | port: 7041 114 | }, 115 | { 116 | name: "peer1", 117 | port: 7042 118 | } 119 | ] 120 | } 121 | }, 122 | { 123 | organization: { 124 | name: "Org2", 125 | domain: "org2.example.com", 126 | peers: [ 127 | { 128 | name: "peer0", 129 | port: 8041 130 | } 131 | ] 132 | } 133 | } 134 | ], 135 | channels: [ 136 | { 137 | name: "mychannel", 138 | orgs: ["Org1", "Org2"] 139 | } 140 | ] 141 | }); 142 | }); 143 | 144 | it("should parse empty config", () => { 145 | const emptyConfig = `{}`; 146 | const result = parseFabloConfig(emptyConfig); 147 | expect(result).toEqual({}); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /src/utils/parseFabloConfig.ts: -------------------------------------------------------------------------------- 1 | import * as yaml from "js-yaml"; 2 | import { FabloConfigJson } from "../types/FabloConfigJson"; 3 | 4 | const parseFabloConfig = (str: string): FabloConfigJson => { 5 | try { 6 | return JSON.parse(str); 7 | } catch (e) { 8 | try { 9 | const yamlContent = yaml.load(str); 10 | return JSON.parse(JSON.stringify(yamlContent)); 11 | } catch (e2) { 12 | throw new Error("Cannot parse file neither as JSON nor YAML file."); 13 | } 14 | } 15 | }; 16 | 17 | export default parseFabloConfig; 18 | -------------------------------------------------------------------------------- /src/version/buildUtil.ts: -------------------------------------------------------------------------------- 1 | import * as config from "../config"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const getBuildInfo = (): Record => require("/fablo/version.json").buildInfo; 5 | 6 | const basicInfo = (): Record => { 7 | return { 8 | version: config.fabloVersion, 9 | build: getBuildInfo(), 10 | }; 11 | }; 12 | 13 | const fullInfo = (): Record => { 14 | return { 15 | version: config.fabloVersion, 16 | build: getBuildInfo(), 17 | supported: { 18 | fabloVersions: `${config.supportedVersionPrefix}x`, 19 | }, 20 | }; 21 | }; 22 | 23 | export { getBuildInfo, basicInfo, fullInfo }; 24 | -------------------------------------------------------------------------------- /src/version/index.ts: -------------------------------------------------------------------------------- 1 | import * as Generator from "yeoman-generator"; 2 | import { basicInfo, fullInfo } from "./buildUtil"; 3 | 4 | export default class VersionGenerator extends Generator { 5 | constructor(args: string[], opts: Generator.GeneratorOptions) { 6 | super(args, opts); 7 | this.option("verbose", { 8 | type: Boolean, 9 | alias: "v", 10 | }); 11 | } 12 | 13 | async printVersion(): Promise { 14 | if (typeof this.options.verbose !== "undefined") { 15 | console.log(JSON.stringify(fullInfo(), null, 2)); 16 | } else { 17 | console.log(JSON.stringify(basicInfo(), null, 2)); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig-dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "noEmit": false, 6 | "rootDir": "./src", 7 | "outDir": "./generators" 8 | }, 9 | "include": [ 10 | "src" 11 | ], 12 | "exclude": [ 13 | "node_modules", 14 | "generators", 15 | "src/*/templates", 16 | "**/*.test.ts", 17 | "**/*.spec.ts", 18 | "e2e" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "rootDir": "./", 6 | "target": "es2019", 7 | "module": "commonjs", 8 | "sourceMap": true, 9 | "strict": true, 10 | "allowJs": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true 17 | }, 18 | "include": [ 19 | "src", 20 | "e2e" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "generators" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------