├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml ├── scripts │ ├── jacoco2markdown.sh │ ├── maven_publish_release.sh │ ├── maven_publish_snapshot.sh │ └── npm_publish.sh └── workflows │ ├── build-docs.yml │ ├── codeql-analysis.yml │ ├── pull_request.yml │ ├── push.yml │ ├── release.yml │ ├── scan.yml │ ├── schedule.yml │ ├── scorecard.yml │ ├── test.yml │ ├── verify-versions.yml │ └── vulnerability-scan.yml ├── .gitignore ├── .golangci.yml ├── .mockery.yml ├── .nancy-ignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── RELEASING.md ├── SECURITY.md ├── docs ├── assets │ ├── Hyperledger_Fabric.svg │ ├── Hyperledger_Fabric_Icon.svg │ └── Hyperledger_Fabric_White.svg ├── community.md ├── compatibility.md ├── index.md └── migration.md ├── go.mod ├── go.sum ├── java ├── .gitignore ├── .mvn │ └── maven.config ├── LICENSE ├── README.md ├── dependency-suppression.xml ├── license-header.txt ├── pmd-ruleset.xml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── hyperledger │ │ │ └── fabric │ │ │ └── client │ │ │ ├── BlockAndPrivateDataEventsBuilder.java │ │ │ ├── BlockAndPrivateDataEventsRequest.java │ │ │ ├── BlockAndPrivateDataEventsRequestImpl.java │ │ │ ├── BlockEventsBuilder.java │ │ │ ├── BlockEventsEnvelopeBuilder.java │ │ │ ├── BlockEventsRequest.java │ │ │ ├── BlockEventsRequestImpl.java │ │ │ ├── Builder.java │ │ │ ├── CallOption.java │ │ │ ├── ChaincodeEvent.java │ │ │ ├── ChaincodeEventImpl.java │ │ │ ├── ChaincodeEventIterator.java │ │ │ ├── ChaincodeEventsBuilder.java │ │ │ ├── ChaincodeEventsRequest.java │ │ │ ├── ChaincodeEventsRequestImpl.java │ │ │ ├── Checkpoint.java │ │ │ ├── Checkpointer.java │ │ │ ├── CloseableIterator.java │ │ │ ├── Commit.java │ │ │ ├── CommitException.java │ │ │ ├── CommitImpl.java │ │ │ ├── CommitStatusException.java │ │ │ ├── Contract.java │ │ │ ├── ContractImpl.java │ │ │ ├── DefaultCallOptions.java │ │ │ ├── EndorseException.java │ │ │ ├── EventsBuilder.java │ │ │ ├── EventsRequest.java │ │ │ ├── FileCheckpointer.java │ │ │ ├── FilteredBlockEventsBuilder.java │ │ │ ├── FilteredBlockEventsRequest.java │ │ │ ├── FilteredBlockEventsRequestImpl.java │ │ │ ├── Gateway.java │ │ │ ├── GatewayClient.java │ │ │ ├── GatewayException.java │ │ │ ├── GatewayImpl.java │ │ │ ├── GatewayRuntimeException.java │ │ │ ├── GatewayUtils.java │ │ │ ├── GrpcStackTracePrinter.java │ │ │ ├── GrpcStatus.java │ │ │ ├── Hash.java │ │ │ ├── InMemoryCheckpointer.java │ │ │ ├── MappingCloseableIterator.java │ │ │ ├── Network.java │ │ │ ├── NetworkImpl.java │ │ │ ├── Proposal.java │ │ │ ├── ProposalBuilder.java │ │ │ ├── ProposalImpl.java │ │ │ ├── Signable.java │ │ │ ├── SignableBlockEventsRequest.java │ │ │ ├── SigningIdentity.java │ │ │ ├── StartPositionBuilder.java │ │ │ ├── Status.java │ │ │ ├── StatusImpl.java │ │ │ ├── SubmitException.java │ │ │ ├── SubmittedTransaction.java │ │ │ ├── SubmittedTransactionImpl.java │ │ │ ├── Transaction.java │ │ │ ├── TransactionContext.java │ │ │ ├── TransactionEnvelopeParser.java │ │ │ ├── TransactionException.java │ │ │ ├── TransactionImpl.java │ │ │ ├── identity │ │ │ ├── ECPrivateKeySigner.java │ │ │ ├── ECSignature.java │ │ │ ├── Identities.java │ │ │ ├── Identity.java │ │ │ ├── PrivateKeySigner.java │ │ │ ├── Signer.java │ │ │ ├── Signers.java │ │ │ ├── X509Identity.java │ │ │ └── package-info.java │ │ │ └── package-info.java │ └── javadoc │ │ ├── overview.html │ │ └── resources │ │ └── fonts │ │ ├── DejaVuLGCSans-Bold.woff2 │ │ ├── DejaVuLGCSans-BoldOblique.woff2 │ │ ├── DejaVuLGCSans-Oblique.woff2 │ │ ├── DejaVuLGCSans.woff2 │ │ ├── DejaVuLGCSansMono-Bold.woff2 │ │ ├── DejaVuLGCSansMono-BoldOblique.woff2 │ │ ├── DejaVuLGCSansMono-Oblique.woff2 │ │ ├── DejaVuLGCSansMono.woff2 │ │ ├── DejaVuLGCSerif-Bold.woff2 │ │ ├── DejaVuLGCSerif-BoldItalic.woff2 │ │ ├── DejaVuLGCSerif-Italic.woff2 │ │ ├── DejaVuLGCSerif.woff2 │ │ ├── LICENSE │ │ └── dejavu.css │ └── test │ ├── java │ ├── org │ │ └── hyperledger │ │ │ └── fabric │ │ │ └── client │ │ │ ├── BlockAndPrivateDataEventsTest.java │ │ │ ├── BlockEventsTest.java │ │ │ ├── ChaincodeEventsTest.java │ │ │ ├── CommonBlockEventsTest.java │ │ │ ├── CommonCheckpointerTest.java │ │ │ ├── CommonGatewayExceptionTest.java │ │ │ ├── DeliverServiceStub.java │ │ │ ├── EvaluateTransactionTest.java │ │ │ ├── FileCheckpointerTest.java │ │ │ ├── FilteredBlockEventsTest.java │ │ │ ├── GatewayExceptionTest.java │ │ │ ├── GatewayMocker.java │ │ │ ├── GatewayRuntimeExceptionTest.java │ │ │ ├── GatewayServiceStub.java │ │ │ ├── GatewayTest.java │ │ │ ├── HashTest.java │ │ │ ├── InMemoryCheckpointerTest.java │ │ │ ├── MockDeliverService.java │ │ │ ├── MockGatewayService.java │ │ │ ├── NetworkTest.java │ │ │ ├── OfflineSignTest.java │ │ │ ├── SubmitTransactionTest.java │ │ │ ├── TestUtils.java │ │ │ ├── WrappedManagedChannel.java │ │ │ └── identity │ │ │ ├── IdentitiesTest.java │ │ │ ├── SignerTest.java │ │ │ ├── X509Credentials.java │ │ │ └── X509IdentityTest.java │ └── scenario │ │ ├── BasicEventListener.java │ │ ├── CheckpointEventListener.java │ │ ├── EventListener.java │ │ ├── GatewayContext.java │ │ ├── RunScenarioTest.java │ │ ├── ScenarioSteps.java │ │ └── TransactionInvocation.java │ └── resources │ └── junit-platform.properties ├── mkdocs.yml ├── node ├── .gitignore ├── .npmignore ├── .npmrc ├── README.md ├── eslint.config.base.mjs ├── eslint.config.mjs ├── jest.config.js ├── package-lock.json ├── package.json ├── src │ ├── README.md │ ├── blockevents.test.ts │ ├── blockeventsbuilder.ts │ ├── blockeventsrequest.ts │ ├── chaincodeevent.ts │ ├── chaincodeevents.test.ts │ ├── chaincodeeventsbuilder.ts │ ├── chaincodeeventsrequest.ts │ ├── checkpointer.d.ts │ ├── checkpointers.test.ts │ ├── checkpointers.ts │ ├── client.test.ts │ ├── client.ts │ ├── commit.ts │ ├── commiterror.ts │ ├── commitstatuserror.ts │ ├── contract.ts │ ├── dependency.test.ts │ ├── endorseerror.ts │ ├── eventsbuilder.ts │ ├── filecheckpointer.ts │ ├── gateway.test.ts │ ├── gateway.ts │ ├── gatewayerror.ts │ ├── hash │ │ ├── hash.d.ts │ │ ├── hashes.test.ts │ │ └── hashes.ts │ ├── identity │ │ ├── ecdsa.ts │ │ ├── hsmsigner.test.ts │ │ ├── hsmsigner.ts │ │ ├── identity.d.ts │ │ ├── signer.d.ts │ │ ├── signers.test.ts │ │ └── signers.ts │ ├── index.ts │ ├── inmemorycheckpointer.ts │ ├── network.test.ts │ ├── network.ts │ ├── offlinesign.test.ts │ ├── proposal.test.ts │ ├── proposal.ts │ ├── proposalbuilder.ts │ ├── signable.d.ts │ ├── signingidentity.test.ts │ ├── signingidentity.ts │ ├── status.ts │ ├── submiterror.ts │ ├── submittedtransaction.ts │ ├── testutils.test.ts │ ├── transaction.test.ts │ ├── transaction.ts │ ├── transactioncontext.ts │ └── transactionparser.ts ├── test │ ├── cert.pem │ └── key.pem ├── tsconfig.build.json ├── tsconfig.json └── typedoc.json ├── pkg ├── .gitignore ├── client │ ├── README.md │ ├── blockevents.go │ ├── blockevents_test.go │ ├── blockeventsbuilder.go │ ├── blockeventscommon_test.go │ ├── blockeventsfiltered_test.go │ ├── blockeventswithprivatedata_test.go │ ├── chaincodeevents.go │ ├── chaincodeevents_test.go │ ├── chaincodeeventsbuilder.go │ ├── checkpointer_test.go │ ├── client.go │ ├── commit.go │ ├── context.go │ ├── contract.go │ ├── contract_test.go │ ├── credentials_test.go │ ├── errors.go │ ├── evaluate_test.go │ ├── eventsbuilder.go │ ├── example_contract_test.go │ ├── example_network_test.go │ ├── example_test.go │ ├── filecheckpointer.go │ ├── filecheckpointer_test.go │ ├── gateway.go │ ├── gateway_test.go │ ├── identity_test.go │ ├── inmemorycheckpointer.go │ ├── network.go │ ├── network_test.go │ ├── offlinesign_test.go │ ├── proposal.go │ ├── proposalbuilder.go │ ├── protobuf_test.go │ ├── sign_test.go │ ├── signingidentity.go │ ├── submit_test.go │ ├── transaction.go │ ├── transactioncontext.go │ └── transactionparser.go ├── hash │ ├── hash.go │ └── hash_test.go ├── identity │ ├── ecdsa.go │ ├── hsmsign.go │ ├── hsmsign_test.go │ ├── identity.go │ ├── identity_test.go │ ├── pem.go │ ├── pem_test.go │ ├── sign.go │ └── sign_test.go └── internal │ ├── staticcheck.conf │ └── test │ └── credentials.go ├── requirements.txt ├── scenario ├── features │ ├── blockevents.feature │ ├── chaincodeevents.feature │ ├── discovery.feature │ ├── endorsement.feature │ ├── endorsement_hsm.feature │ ├── errors.feature │ ├── offlinesign.feature │ ├── privatedata.feature │ ├── sbe.feature │ └── transactions.feature ├── fixtures │ ├── ca-client-config │ │ ├── .gitignore │ │ └── fabric-ca-client-config-template.yaml │ ├── chaincode │ │ ├── golang │ │ │ ├── basic │ │ │ │ ├── .gitignore │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── main.go │ │ │ └── private │ │ │ │ ├── .gitignore │ │ │ │ ├── collections_config.json │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── private.go │ │ └── node │ │ │ └── errors │ │ │ ├── .gitignore │ │ │ ├── index.js │ │ │ ├── lib │ │ │ └── errors-contract.js │ │ │ └── package.json │ ├── crypto-material │ │ ├── .gitignore │ │ ├── configtx.yaml │ │ ├── create_channel.sh │ │ └── crypto-config.yaml │ ├── docker-compose │ │ ├── .env │ │ ├── docker-compose-base.yaml │ │ ├── docker-compose-cli.yaml │ │ └── docker-compose-tls.yaml │ ├── generate-hsm-user.sh │ └── generate.sh ├── go │ ├── blockandprivatedataevents_test.go │ ├── blockevents_test.go │ ├── chaincodeevents_test.go │ ├── connection_test.go │ ├── fabric_test.go │ ├── filteredblockevents_test.go │ ├── godogs_test.go │ ├── scenario_test.go │ └── transaction_test.go └── node │ ├── .gitignore │ ├── eslint.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── baseeventlistener.ts │ ├── checkpointeventlistener.ts │ ├── customworld.ts │ ├── eventlistener.ts │ ├── fabric.ts │ ├── fabricski.ts │ ├── gatewaycontext.ts │ ├── scenario_steps.ts │ ├── transactioninvocation.ts │ └── utils.ts │ └── tsconfig.json └── scripts ├── shellcheck.sh └── update-versions.sh /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ubuntu", 3 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu", 4 | "features": { 5 | "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, 6 | "ghcr.io/devcontainers/features/go:1": { 7 | "version": "latest" 8 | }, 9 | "ghcr.io/devcontainers/features/java:1": { 10 | "version": "lts", 11 | "jdkDistro": "tem", 12 | "installMaven": true 13 | }, 14 | "ghcr.io/devcontainers/features/node:1": { 15 | "version": "lts" 16 | }, 17 | "ghcr.io/devcontainers/features/python:1": { 18 | "version": "latest", 19 | "installTools": false 20 | }, 21 | "ghcr.io/devcontainers-extra/features/apt-get-packages:1": { 22 | "packages": "shellcheck,softhsm2" 23 | } 24 | }, 25 | "customizations": { 26 | "vscode": { 27 | "settings": { 28 | "editor.defaultFormatter": "esbenp.prettier-vscode", 29 | "go.buildTags": "pkcs11" 30 | }, 31 | "extensions": [ 32 | "esbenp.prettier-vscode", 33 | "github.vscode-github-actions", 34 | "-ms-python.python", 35 | "-ms-python.vscode-pylance", 36 | "-ms-python.autopep8", 37 | "ms-vscode.makefile-tools", 38 | "timonwong.shellcheck" 39 | ] 40 | } 41 | }, 42 | "postCreateCommand": { 43 | "generate": ["make", "generate"] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | end_of_line = lf 8 | max_line_length = 120 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [Makefile] 13 | indent_style = tab 14 | 15 | [*.{yaml,yml}] 16 | indent_size = 2 17 | 18 | [*.html] 19 | indent_size = 2 20 | 21 | [*.md] 22 | indent_size = 2 23 | 24 | [*.{mj,cj,j,t}s] 25 | quote_type = single 26 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hyperledger/fabric-gateway-maintainers 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please provide a clear and concise description of the bug, including the following: 11 | 12 | - Steps to reproduce. 13 | - Expected behavior. 14 | - Programming language and version. 15 | - Client API version used. 16 | - Fabric version used. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Questions 4 | url: https://github.com/hyperledger/fabric-gateway/discussions 5 | about: Please use Discussions for questions. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an enhancement for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **As a** ...who (role)... 11 | **I want** ...what (brief description of new feature)... 12 | **So that** ...why (need satisfied by new feature)... 13 | 14 | Please describe the new feature as a user story using the template above (see issue #198 as an example). The "So that" statement provides useful context about the motivation for the enhancement, and helps in determining whether any changes successfully satisfy the requirement. 15 | 16 | Provide additional information below the user story, which can include: 17 | 18 | - More detailed description of the feature and any problem it solves. 19 | - Suggested solution. 20 | - Alternative solutions considered. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "devcontainers" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/scripts/maven_publish_release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | PUBLISH_PROFILE="${1:?}" 6 | 7 | POM_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout) 8 | PUBLISH_VERSION="${POM_VERSION%%-*}" 9 | 10 | mvn --batch-mode --no-transfer-progress versions:set -DnewVersion="${PUBLISH_VERSION}" 11 | mvn --batch-mode --no-transfer-progress --activate-profiles "release,${PUBLISH_PROFILE}" -DskipTests deploy 12 | -------------------------------------------------------------------------------- /.github/scripts/maven_publish_snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | POM_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout) 6 | GATEWAY_VERSION="${POM_VERSION%%-*}" 7 | PUBLISH_VERSION="${GATEWAY_VERSION}-SNAPSHOT" 8 | 9 | mvn --batch-mode --no-transfer-progress versions:set -DnewVersion="${PUBLISH_VERSION}" 10 | mvn --batch-mode --no-transfer-progress --activate-profiles release -DskipTests deploy 11 | -------------------------------------------------------------------------------- /.github/scripts/npm_publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | TAG="${1:?}" 6 | 7 | if [[ ${TAG} != latest ]]; then 8 | PACKAGE_VERSION=$(jq --raw-output .version package.json) 9 | TODAY=$(date -u '+%Y%m%d') 10 | TARGET_VERSION="${PACKAGE_VERSION}-dev.${TODAY}." 11 | INCREMENT=$(npm view --json | jq --raw-output '.versions[]' | awk -F . "/^${TARGET_VERSION}/"'{ lastVersion=$NF } \ 12 | END { sub(/".*/, "", lastVersion); print (lastVersion=="" ? "1" : lastVersion+1) }') 13 | PUBLISH_VERSION="${TARGET_VERSION}${INCREMENT}" 14 | npm --allow-same-version --no-git-tag-version version "${PUBLISH_VERSION}" 15 | fi 16 | 17 | npm publish --access public --tag "${TAG}" 18 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | name: Build documentation 2 | on: 3 | workflow_call: 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | node: 10 | runs-on: ubuntu-24.04 11 | name: Node documentation 12 | steps: 13 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 14 | - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 15 | with: 16 | node-version: "lts/*" 17 | - name: Generate documentation 18 | run: make generate-docs-node 19 | - name: Upload documentation 20 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 21 | with: 22 | name: node-doc 23 | path: node/apidocs/ 24 | 25 | java: 26 | runs-on: ubuntu-24.04 27 | name: Java documentation 28 | steps: 29 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 30 | - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 31 | with: 32 | java-version: 21 33 | distribution: temurin 34 | cache: maven 35 | - name: Generate JavaDoc 36 | run: make generate-docs-java 37 | - name: Upload JavaDoc 38 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 39 | with: 40 | name: java-doc 41 | path: java/target/reports/apidocs/ 42 | 43 | site: 44 | runs-on: ubuntu-24.04 45 | name: Documentation site 46 | steps: 47 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 48 | - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 49 | with: 50 | python-version: 3.x 51 | - name: Generate documentation site 52 | run: make generate-docs 53 | - name: Upload documentation site 54 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 55 | with: 56 | name: site-doc 57 | path: site/ 58 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | analyze: 21 | name: Analyze 22 | runs-on: ubuntu-latest 23 | permissions: 24 | actions: read 25 | contents: read 26 | security-events: write 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | language: 31 | - go 32 | - java 33 | - javascript 34 | include: 35 | - language: java 36 | working-directory: java 37 | - language: javascript 38 | working-directory: node 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@5f8171a638ada777af81d42b55959a643bb29017 # v3.28.12 44 | with: 45 | languages: ${{ matrix.language }} 46 | - name: Autobuild 47 | uses: github/codeql-action/autobuild@5f8171a638ada777af81d42b55959a643bb29017 # v3.28.12 48 | env: 49 | GOFLAGS: "-tags=pkcs11" 50 | with: 51 | working-directory: ${{ matrix.working-directory || '' }} 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@5f8171a638ada777af81d42b55959a643bb29017 # v3.28.12 54 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Pull request 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | uses: ./.github/workflows/test.yml 18 | 19 | scan: 20 | uses: ./.github/workflows/scan.yml 21 | 22 | pull-request: 23 | needs: build 24 | name: Pull request success 25 | runs-on: ubuntu-latest 26 | steps: 27 | - run: "true" 28 | -------------------------------------------------------------------------------- /.github/workflows/schedule.yml: -------------------------------------------------------------------------------- 1 | name: Scheduled build 2 | 3 | on: 4 | schedule: 5 | - cron: "32 23 * * 6" 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | main: 13 | uses: ./.github/workflows/test.yml 14 | -------------------------------------------------------------------------------- /.github/workflows/verify-versions.yml: -------------------------------------------------------------------------------- 1 | name: Verify versions 2 | 3 | on: 4 | workflow_call: 5 | 6 | permissions: 7 | contents: read 8 | 9 | env: 10 | GATEWAY_VERSION: 1.8.0 11 | 12 | jobs: 13 | go: 14 | if: startsWith(github.ref, 'refs/tags/v') 15 | runs-on: ubuntu-latest 16 | name: Verify Go version 17 | steps: 18 | - name: Check Go module version 19 | shell: bash 20 | run: | 21 | echo "Build ref: ${GITHUB_REF}" 22 | GO_GATEWAY_VERSION=${GITHUB_REF#refs/tags/v} 23 | echo "Expected version: ${GATEWAY_VERSION}" 24 | echo "Go module version (git tag): ${GO_GATEWAY_VERSION}" 25 | [ "${GO_GATEWAY_VERSION}" = "${GATEWAY_VERSION}" ] || exit 1 26 | 27 | node: 28 | runs-on: ubuntu-latest 29 | name: Verify Node version 30 | steps: 31 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 32 | - name: Check Node package version 33 | shell: bash 34 | working-directory: node 35 | run: | 36 | PACKAGE_GATEWAY_VERSION=$(jq --raw-output .version package.json) 37 | echo "Expected version: ${GATEWAY_VERSION}" 38 | echo "package.json version: ${PACKAGE_GATEWAY_VERSION}" 39 | [ "${PACKAGE_GATEWAY_VERSION}" = "${GATEWAY_VERSION}" ] || exit 1 40 | 41 | java: 42 | runs-on: ubuntu-latest 43 | name: Verify Java version 44 | steps: 45 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 46 | - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 47 | with: 48 | java-version: 21 49 | distribution: temurin 50 | cache: maven 51 | - name: Check Java artifact version 52 | shell: bash 53 | working-directory: java 54 | run: | 55 | POM_GATEWAY_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout) 56 | echo "Expected version: ${GATEWAY_VERSION}" 57 | echo "pom.xml version: ${POM_GATEWAY_VERSION}" 58 | [ "${POM_GATEWAY_VERSION%%-*}" = "${GATEWAY_VERSION}" ] || exit 1 59 | -------------------------------------------------------------------------------- /.github/workflows/vulnerability-scan.yml: -------------------------------------------------------------------------------- 1 | name: "Security vulnerability scan" 2 | 3 | on: 4 | schedule: 5 | - cron: "20 23 * * *" 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | latest: 13 | name: Scan ${{ github.ref_name }} 14 | uses: ./.github/workflows/scan.yml 15 | 16 | latest-release-version: 17 | name: Get latest release tag 18 | runs-on: ubuntu-latest 19 | outputs: 20 | tag_name: ${{ steps.tag-name.outputs.value }} 21 | steps: 22 | - id: tag-name 23 | run: echo "value=$(curl --location --silent --fail "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/latest" | jq --raw-output '.tag_name')" >> "${GITHUB_OUTPUT}" 24 | 25 | release: 26 | name: Scan ${{ needs.latest-release-version.outputs.tag_name }} 27 | needs: latest-release-version 28 | uses: ./.github/workflows/scan.yml 29 | with: 30 | ref: ${{ needs.latest-release-version.outputs.tag_name }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | bin/ 4 | cover.out 5 | fabric-protos/ 6 | .DS_Store 7 | *.iml 8 | softhsm2.conf 9 | .python-version 10 | .venv/ 11 | site/ 12 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | build-tags: 4 | - pkcs11 5 | linters: 6 | default: none 7 | enable: 8 | - cyclop 9 | - errcheck 10 | - errname 11 | - errorlint 12 | - gocognit 13 | - goheader 14 | - gosec 15 | - govet 16 | - ineffassign 17 | - misspell 18 | - nolintlint 19 | - perfsprint 20 | - protogetter 21 | - staticcheck 22 | - testifylint 23 | - unused 24 | - usetesting 25 | settings: 26 | cyclop: 27 | max-complexity: 10 28 | gocognit: 29 | min-complexity: 15 30 | goheader: 31 | values: 32 | const: 33 | COMPANY: IBM Corp. 34 | template: |- 35 | Copyright {{ COMPANY }} All Rights Reserved. 36 | SPDX-License-Identifier: Apache-2.0 37 | staticcheck: 38 | checks: 39 | - all 40 | exclusions: 41 | generated: strict 42 | warn-unused: true 43 | presets: 44 | - std-error-handling 45 | rules: 46 | - path: pkg/internal/test/ 47 | text: "^ST1000:" 48 | linters: 49 | - staticcheck 50 | - path: pkg/hash/hash.go 51 | text: "^ST1003:.* ALL_CAPS" 52 | linters: 53 | - staticcheck 54 | formatters: 55 | enable: 56 | - gofmt 57 | - goimports 58 | exclusions: 59 | generated: strict 60 | -------------------------------------------------------------------------------- /.mockery.yml: -------------------------------------------------------------------------------- 1 | structname: Mock{{.InterfaceName | firstUpper}} 2 | template: testify 3 | template-data: 4 | unroll-variadic: false 5 | packages: 6 | google.golang.org/grpc: 7 | config: 8 | dir: pkg/client 9 | pkgname: client 10 | interfaces: 11 | ClientConnInterface: {} 12 | ClientStream: {} 13 | -------------------------------------------------------------------------------- /.nancy-ignore: -------------------------------------------------------------------------------- 1 | CVE-2024-8421 # RedHat-specific duplicate of CVE-2023-39325 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes in each release are documented on the project's [GitHub releases](https://github.com/hyperledger/fabric-gateway/releases) page. 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | This project follows the [LF Decentralized Trust code of conduct](https://lf-decentralized-trust.github.io/governance/governing-documents/code-of-conduct). Please review these guidelines before participating. 4 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers 2 | 3 | ### Active Maintainers 4 | | Name | GitHub | email | 5 | |---|---|---| 6 | | Andrew Coleman | andrew-coleman | andrew_coleman@uk.ibm.com | 7 | | Mark Lewis | bestbeforetoday | Mark.S.Lewis@outlook.com | 8 | 9 | ### Retired Maintainers 10 | | Name | GitHub | email | 11 | |---|---|---| 12 | 13 | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | This document defines how security vulnerability reporting is handled for this project. The approach aligns with the [LF Decentralized Trust security policy](https://lf-decentralized-trust.github.io/governance/governing-documents/security). Please review that document to understand the basis of the security reporting for this project. Details specific to this repository are documented below. 4 | 5 | ## Supported versions 6 | 7 | The [latest release](https://github.com/hyperledger/fabric-gateway/releases/latest) version is supported with security updates. To address any security vulnerabilities found in previous releases, you should update to the latest release. 8 | 9 | ## Reporting a vulnerability 10 | 11 | Suspected security vulnerabilities in this project can be reported using the repository's [security advisories page](https://github.com/hyperledger/fabric-gateway/security/advisories). Guidance can be found in the GitHub documentation on [privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability). The maintainers will work with you to confirm the vulnerability, deliver a fix, and then release a security bulletin. 12 | 13 | ## Vulnerabilities in dependencies 14 | 15 | Dependencies are regularly scanned for published security vulnerabilities, and these are addressed as soon as practical. In general it should not be necessary to report vulnerabilities in project dependencies. 16 | -------------------------------------------------------------------------------- /docs/community.md: -------------------------------------------------------------------------------- 1 | # Community 2 | 3 | For community discussion related to the Fabric Gateway client API, look at: 4 | 5 | - The GitHub repository [discussions](https://github.com/hyperledger/fabric-gateway/discussions). 6 | - The `#fabric-client-apis` channel on Hyperledger [Discord](https://discord.com/channels/905194001349627914/943089887589048350) ([invite link](https://discord.gg/hyperledger)). 7 | - The Hyperledger Fabric [mailing list](https://lists.lfdecentralizedtrust.org/g/fabric). 8 | -------------------------------------------------------------------------------- /docs/compatibility.md: -------------------------------------------------------------------------------- 1 | # Compatibility 2 | 3 | Each minor release version of Fabric Gateway client API targets the current supported versions of Go, and the current long-term support (LTS) releases of Node and Java. A specific minimum version of Hyperledger Fabric for the Gateway peer is also required for full functionality. 4 | 5 | The following table shows versions of Fabric, programming language runtimes, and other dependencies that are explicitly tested and that are supported for use with the latest version of the Fabric Gateway client API. 6 | 7 | | | Tested | Supported | 8 | | ------------ | ------------ | ------------- | 9 | | **Fabric** | 2.5 | 2.4.4+ | 10 | | **Go** | 1.23, 1.24 | 1.23, 1.24 | 11 | | **Node** | 18, 20, 22 | 18, 20, 22 | 12 | | **Java** | 11, 17, 21 | 8, 11, 17, 21 | 13 | | **Platform** | Ubuntu 24.04 | | 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyperledger/fabric-gateway 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/cucumber/godog v0.15.0 7 | github.com/google/go-cmp v0.6.0 8 | github.com/hyperledger/fabric-protos-go-apiv2 v0.3.6 9 | github.com/miekg/pkcs11 v1.1.1 10 | github.com/spf13/pflag v1.0.6 11 | github.com/stretchr/testify v1.10.0 12 | golang.org/x/crypto v0.36.0 13 | google.golang.org/grpc v1.70.0 14 | google.golang.org/protobuf v1.36.5 15 | ) 16 | 17 | require ( 18 | github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect 19 | github.com/cucumber/messages/go/v21 v21.0.1 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/gofrs/uuid v4.4.0+incompatible // indirect 22 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 23 | github.com/hashicorp/go-memdb v1.3.4 // indirect 24 | github.com/hashicorp/golang-lru v1.0.2 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | github.com/stretchr/objx v0.5.2 // indirect 27 | golang.org/x/net v0.38.0 // indirect 28 | golang.org/x/sys v0.31.0 // indirect 29 | golang.org/x/text v0.23.0 // indirect 30 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect 31 | gopkg.in/yaml.v3 v3.0.1 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .idea/ 3 | *.iml 4 | .project 5 | .vscode/ 6 | .settings/ 7 | target/ 8 | -------------------------------------------------------------------------------- /java/.mvn/maven.config: -------------------------------------------------------------------------------- 1 | --no-transfer-progress 2 | -------------------------------------------------------------------------------- /java/dependency-suppression.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | ^pkg:maven/org\.hyperledger\.fabric/fabric\-protos@.*$ 9 | CVE-2022-31121 10 | 11 | 12 | 16 | ^pkg:maven/org\.hyperledger\.fabric/fabric\-protos@.*$ 17 | CVE-2022-36023 18 | 19 | 20 | 24 | ^pkg:maven/org\.hyperledger\.fabric/fabric-protos@.*$ 25 | CVE-2024-45244 26 | 27 | 28 | -------------------------------------------------------------------------------- /java/license-header.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright $YEAR IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | -------------------------------------------------------------------------------- /java/pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | Custom PMD ruleset for Fabric Gateway client API. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/BlockAndPrivateDataEventsBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.ByteString; 10 | import java.util.Objects; 11 | import org.hyperledger.fabric.protos.common.Envelope; 12 | 13 | final class BlockAndPrivateDataEventsBuilder implements BlockAndPrivateDataEventsRequest.Builder { 14 | private final GatewayClient client; 15 | private final SigningIdentity signingIdentity; 16 | private final BlockEventsEnvelopeBuilder envelopeBuilder; 17 | 18 | BlockAndPrivateDataEventsBuilder( 19 | final GatewayClient client, 20 | final SigningIdentity signingIdentity, 21 | final String channelName, 22 | final ByteString tlsCertificateHash) { 23 | Objects.requireNonNull(channelName, "channel name"); 24 | 25 | this.client = client; 26 | this.signingIdentity = signingIdentity; 27 | this.envelopeBuilder = new BlockEventsEnvelopeBuilder(signingIdentity, channelName, tlsCertificateHash); 28 | } 29 | 30 | @Override 31 | public BlockAndPrivateDataEventsBuilder startBlock(final long blockNumber) { 32 | envelopeBuilder.startBlock(blockNumber); 33 | return this; 34 | } 35 | 36 | @Override 37 | public BlockAndPrivateDataEventsBuilder checkpoint(final Checkpoint checkpoint) { 38 | checkpoint.getBlockNumber().ifPresent(envelopeBuilder::startBlock); 39 | return this; 40 | } 41 | 42 | @Override 43 | public BlockAndPrivateDataEventsRequest build() { 44 | Envelope request = envelopeBuilder.build(); 45 | return new BlockAndPrivateDataEventsRequestImpl(client, signingIdentity, request); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/BlockAndPrivateDataEventsRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import org.hyperledger.fabric.protos.peer.BlockAndPrivateData; 10 | 11 | /** 12 | * A Fabric Gateway call to obtain block and private data events. Supports off-line signing flow using 13 | * {@link Gateway#newSignedBlockAndPrivateDataEventsRequest(byte[], byte[])}. 14 | */ 15 | public interface BlockAndPrivateDataEventsRequest extends EventsRequest { 16 | /** 17 | * Builder used to create a new block and private data events request. The default behavior is to read events from 18 | * the next committed block. 19 | */ 20 | interface Builder extends EventsBuilder { 21 | @Override 22 | Builder startBlock(long blockNumber); 23 | 24 | @Override 25 | Builder checkpoint(Checkpoint checkpoint); 26 | 27 | @Override 28 | BlockAndPrivateDataEventsRequest build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/BlockAndPrivateDataEventsRequestImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.CallOptions; 10 | import java.util.NoSuchElementException; 11 | import java.util.function.UnaryOperator; 12 | import org.hyperledger.fabric.protos.common.Envelope; 13 | import org.hyperledger.fabric.protos.peer.BlockAndPrivateData; 14 | import org.hyperledger.fabric.protos.peer.DeliverResponse; 15 | 16 | final class BlockAndPrivateDataEventsRequestImpl extends SignableBlockEventsRequest 17 | implements BlockAndPrivateDataEventsRequest { 18 | private final GatewayClient client; 19 | 20 | BlockAndPrivateDataEventsRequestImpl( 21 | final GatewayClient client, final SigningIdentity signingIdentity, final Envelope request) { 22 | super(signingIdentity, request); 23 | this.client = client; 24 | } 25 | 26 | @Override 27 | public CloseableIterator getEvents(final UnaryOperator options) { 28 | Envelope request = getSignedRequest(); 29 | CloseableIterator responseIter = client.blockAndPrivateDataEvents(request, options); 30 | 31 | return new MappingCloseableIterator<>(responseIter, response -> { 32 | DeliverResponse.TypeCase responseType = response.getTypeCase(); 33 | if (responseType == DeliverResponse.TypeCase.STATUS) { 34 | throw new NoSuchElementException("Unexpected status response: " + response.getStatus()); 35 | } 36 | 37 | return response.getBlockAndPrivateData(); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/BlockEventsBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.ByteString; 10 | import java.util.Objects; 11 | import org.hyperledger.fabric.protos.common.Envelope; 12 | 13 | final class BlockEventsBuilder implements BlockEventsRequest.Builder { 14 | private final GatewayClient client; 15 | private final SigningIdentity signingIdentity; 16 | private final BlockEventsEnvelopeBuilder envelopeBuilder; 17 | 18 | BlockEventsBuilder( 19 | final GatewayClient client, 20 | final SigningIdentity signingIdentity, 21 | final String channelName, 22 | final ByteString tlsCertificateHash) { 23 | Objects.requireNonNull(channelName, "channel name"); 24 | 25 | this.client = client; 26 | this.signingIdentity = signingIdentity; 27 | this.envelopeBuilder = new BlockEventsEnvelopeBuilder(signingIdentity, channelName, tlsCertificateHash); 28 | } 29 | 30 | @Override 31 | public BlockEventsBuilder startBlock(final long blockNumber) { 32 | envelopeBuilder.startBlock(blockNumber); 33 | return this; 34 | } 35 | 36 | @Override 37 | public BlockEventsBuilder checkpoint(final Checkpoint checkpoint) { 38 | checkpoint.getBlockNumber().ifPresent(envelopeBuilder::startBlock); 39 | return this; 40 | } 41 | 42 | @Override 43 | public BlockEventsRequest build() { 44 | Envelope request = envelopeBuilder.build(); 45 | return new BlockEventsRequestImpl(client, signingIdentity, request); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/BlockEventsRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import org.hyperledger.fabric.protos.common.Block; 10 | 11 | /** 12 | * A Fabric Gateway call to obtain block events. Supports off-line signing flow using 13 | * {@link Gateway#newSignedBlockEventsRequest(byte[], byte[])}. 14 | */ 15 | public interface BlockEventsRequest extends EventsRequest { 16 | /** 17 | * Builder used to create a new block events request. The default behavior is to read events from the next 18 | * committed block. 19 | */ 20 | interface Builder extends EventsBuilder { 21 | @Override 22 | Builder startBlock(long blockNumber); 23 | 24 | @Override 25 | Builder checkpoint(Checkpoint checkpoint); 26 | 27 | @Override 28 | BlockEventsRequest build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/BlockEventsRequestImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.CallOptions; 10 | import java.util.NoSuchElementException; 11 | import java.util.function.UnaryOperator; 12 | import org.hyperledger.fabric.protos.common.Block; 13 | import org.hyperledger.fabric.protos.common.Envelope; 14 | import org.hyperledger.fabric.protos.peer.DeliverResponse; 15 | 16 | final class BlockEventsRequestImpl extends SignableBlockEventsRequest implements BlockEventsRequest { 17 | private final GatewayClient client; 18 | 19 | BlockEventsRequestImpl(final GatewayClient client, final SigningIdentity signingIdentity, final Envelope request) { 20 | super(signingIdentity, request); 21 | this.client = client; 22 | } 23 | 24 | @Override 25 | public CloseableIterator getEvents(final UnaryOperator options) { 26 | Envelope request = getSignedRequest(); 27 | CloseableIterator responseIter = client.blockEvents(request, options); 28 | 29 | return new MappingCloseableIterator<>(responseIter, response -> { 30 | DeliverResponse.TypeCase responseType = response.getTypeCase(); 31 | if (responseType == DeliverResponse.TypeCase.STATUS) { 32 | throw new NoSuchElementException("Unexpected status response: " + response.getStatus()); 33 | } 34 | 35 | return response.getBlock(); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/Builder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | /** 10 | * A builder used to create new object instances from configuration state. 11 | * @param The type of object built. 12 | */ 13 | public interface Builder { 14 | /** 15 | * Build an instance. 16 | * @return A built instance. 17 | */ 18 | T build(); 19 | } 20 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/CallOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.CallOptions; 10 | import io.grpc.Deadline; 11 | import java.util.Objects; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.function.UnaryOperator; 14 | 15 | /** 16 | * Options defining runtime behavior of a gRPC service invocation. 17 | * @deprecated Use gRPC {@link io.grpc.CallOptions} instead. 18 | */ 19 | @Deprecated 20 | public final class CallOption { 21 | private final UnaryOperator operator; 22 | 23 | private CallOption(final UnaryOperator operator) { 24 | this.operator = operator; 25 | } 26 | 27 | /** 28 | * An absolute deadline. 29 | * @param deadline the deadline. 30 | * @return a call option. 31 | */ 32 | public static CallOption deadline(final Deadline deadline) { 33 | Objects.requireNonNull(deadline, "deadline"); 34 | return new CallOption(options -> options.withDeadline(deadline)); 35 | } 36 | 37 | /** 38 | * A deadline that is after the given duration from when the call is initiated. 39 | * @param duration a time duration. 40 | * @param unit units for the time duration. 41 | * @return a call option. 42 | */ 43 | public static CallOption deadlineAfter(final long duration, final TimeUnit unit) { 44 | Objects.requireNonNull(unit, "unit"); 45 | return new CallOption(options -> options.withDeadlineAfter(duration, unit)); 46 | } 47 | 48 | CallOptions apply(final CallOptions options) { 49 | return operator.apply(options); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/ChaincodeEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | /** 10 | * Chaincode event emitted by a transaction function. 11 | */ 12 | public interface ChaincodeEvent { 13 | /** 14 | * Block number that included this chaincode event. 15 | *

Note that the block number is an unsigned 64-bit integer, with the sign bit used to hold the top bit of 16 | * the number.

17 | * @return A block number. 18 | */ 19 | long getBlockNumber(); 20 | 21 | /** 22 | * Transaction that emitted this chaincode event. 23 | * @return Transaction ID. 24 | */ 25 | String getTransactionId(); 26 | 27 | /** 28 | * Chaincode that emitted this event. 29 | * @return Chaincode name. 30 | */ 31 | String getChaincodeName(); 32 | 33 | /** 34 | * Name of the emitted event. 35 | * @return Event name. 36 | */ 37 | String getEventName(); 38 | 39 | /** 40 | * Application defined payload data associated with this event. 41 | * @return Event payload. 42 | */ 43 | byte[] getPayload(); 44 | } 45 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/ChaincodeEventIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.util.Collections; 10 | import java.util.Iterator; 11 | import org.hyperledger.fabric.protos.gateway.ChaincodeEventsResponse; 12 | 13 | final class ChaincodeEventIterator implements CloseableIterator { 14 | private final CloseableIterator responseIter; 15 | private Iterator eventIter = Collections.emptyIterator(); 16 | private long blockNumber; 17 | 18 | ChaincodeEventIterator(final CloseableIterator responseIter) { 19 | this.responseIter = responseIter; 20 | } 21 | 22 | @Override 23 | public boolean hasNext() { 24 | return eventIter.hasNext() || responseIter.hasNext(); 25 | } 26 | 27 | @Override 28 | public ChaincodeEvent next() { 29 | while (!eventIter.hasNext()) { 30 | ChaincodeEventsResponse response = responseIter.next(); 31 | eventIter = response.getEventsList().iterator(); 32 | blockNumber = response.getBlockNumber(); 33 | } 34 | 35 | return new ChaincodeEventImpl(blockNumber, eventIter.next()); 36 | } 37 | 38 | @Override 39 | public void close() { 40 | responseIter.close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/ChaincodeEventsRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | /** 10 | * A Fabric Gateway call to obtain chaincode events. Supports off-line signing flow using 11 | * {@link Gateway#newSignedChaincodeEventsRequest(byte[], byte[])}. 12 | */ 13 | public interface ChaincodeEventsRequest extends EventsRequest { 14 | /** 15 | * Builder used to create a new chaincode events request. The default behavior is to read events from the next 16 | * committed block. 17 | */ 18 | interface Builder extends EventsBuilder { 19 | @Override 20 | Builder startBlock(long blockNumber); 21 | 22 | @Override 23 | Builder checkpoint(Checkpoint checkpoint); 24 | 25 | @Override 26 | ChaincodeEventsRequest build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/ChaincodeEventsRequestImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.ByteString; 10 | import io.grpc.CallOptions; 11 | import java.util.function.UnaryOperator; 12 | import org.hyperledger.fabric.protos.gateway.ChaincodeEventsResponse; 13 | import org.hyperledger.fabric.protos.gateway.SignedChaincodeEventsRequest; 14 | 15 | final class ChaincodeEventsRequestImpl implements ChaincodeEventsRequest { 16 | private final GatewayClient client; 17 | private final SigningIdentity signingIdentity; 18 | private SignedChaincodeEventsRequest signedRequest; 19 | 20 | ChaincodeEventsRequestImpl( 21 | final GatewayClient client, 22 | final SigningIdentity signingIdentity, 23 | final SignedChaincodeEventsRequest signedRequest) { 24 | this.client = client; 25 | this.signingIdentity = signingIdentity; 26 | this.signedRequest = signedRequest; 27 | } 28 | 29 | @Override 30 | public CloseableIterator getEvents(final UnaryOperator options) { 31 | sign(); 32 | CloseableIterator responseIter = client.chaincodeEvents(signedRequest, options); 33 | return new ChaincodeEventIterator(responseIter); 34 | } 35 | 36 | @Override 37 | public byte[] getBytes() { 38 | return signedRequest.toByteArray(); 39 | } 40 | 41 | @Override 42 | public byte[] getDigest() { 43 | byte[] message = signedRequest.getRequest().toByteArray(); 44 | return signingIdentity.hash(message); 45 | } 46 | 47 | void setSignature(final byte[] signature) { 48 | signedRequest = signedRequest.toBuilder() 49 | .setSignature(ByteString.copyFrom(signature)) 50 | .build(); 51 | } 52 | 53 | private void sign() { 54 | if (isSigned()) { 55 | return; 56 | } 57 | 58 | byte[] digest = getDigest(); 59 | byte[] signature = signingIdentity.sign(digest); 60 | setSignature(signature); 61 | } 62 | 63 | private boolean isSigned() { 64 | return !signedRequest.getSignature().isEmpty(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/Checkpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.util.Optional; 10 | import java.util.OptionalLong; 11 | 12 | /** 13 | * Checkpoint provides the current position for event processing. 14 | */ 15 | public interface Checkpoint { 16 | /** 17 | * The block number in which the next event is expected. 18 | * @return A ledger block number. 19 | */ 20 | OptionalLong getBlockNumber(); 21 | 22 | /** 23 | * Transaction Id of the last successfully processed event within the current block. 24 | * @return A transaction Id. 25 | */ 26 | Optional getTransactionId(); 27 | } 28 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/Checkpointer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Checkpointer allows update of a checkpoint position after events are successfully processed. 13 | */ 14 | public interface Checkpointer extends Checkpoint { 15 | /** 16 | * Checkpoint a successfully processed block. 17 | *

Note that the block number is an unsigned 64-bit integer, with the sign bit used to hold the top bit of 18 | * the number.

19 | * @param blockNumber a ledger block number. 20 | * @throws IOException if an I/O error occurs. 21 | */ 22 | void checkpointBlock(long blockNumber) throws IOException; 23 | 24 | /** 25 | * Checkpoint a transaction within a block. 26 | * @param blockNumber a ledger block number. 27 | * @param transactionId transaction id within the block. 28 | * @throws IOException if an I/O error occurs. 29 | */ 30 | void checkpointTransaction(long blockNumber, String transactionId) throws IOException; 31 | 32 | /** 33 | * Checkpoint a chaincode event. 34 | * @param event a chaincode event. 35 | * @throws IOException if an I/O error occurs. 36 | */ 37 | void checkpointChaincodeEvent(ChaincodeEvent event) throws IOException; 38 | } 39 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/CloseableIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.util.Iterator; 10 | 11 | /** 12 | * An iterator that can be closed when the consumer does not want to read any more elements, freeing up resources that 13 | * may be held by the iterator. 14 | *

Note that iteration may throw {@link GatewayRuntimeException} if the gRPC connection fails.

15 | * @param The type of elements returned by this iterator. 16 | */ 17 | public interface CloseableIterator extends Iterator, AutoCloseable { 18 | @Override 19 | void close(); 20 | } 21 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/Commit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.CallOptions; 10 | import java.util.function.UnaryOperator; 11 | 12 | /** 13 | * Allows access to information about a transaction that is committed to the ledger. 14 | */ 15 | public interface Commit extends Signable { 16 | /** 17 | * Get the transaction ID. 18 | * @return A transaction ID. 19 | */ 20 | String getTransactionId(); 21 | 22 | /** 23 | * Get the status of the committed transaction. If the transaction has not yet committed, this method blocks until 24 | * the commit occurs. 25 | * @return Transaction commit status. 26 | * @throws CommitStatusException if the gRPC service invocation fails. 27 | */ 28 | default Status getStatus() throws CommitStatusException { 29 | return getStatus(GatewayUtils.asCallOptions()); 30 | } 31 | 32 | /** 33 | * Get the status of the committed transaction. If the transaction has not yet committed, this method blocks until 34 | * the commit occurs. 35 | * @param options Function that transforms call options. 36 | * @return Transaction commit status. 37 | * @throws CommitStatusException if the gRPC service invocation fails. 38 | */ 39 | Status getStatus(UnaryOperator options) throws CommitStatusException; 40 | 41 | /** 42 | * Get the status of the committed transaction. If the transaction has not yet committed, this method blocks until 43 | * the commit occurs. 44 | * @param options Call options. 45 | * @return Transaction commit status. 46 | * @throws CommitStatusException if the gRPC service invocation fails. 47 | * @deprecated Replaced by {@link #getStatus(UnaryOperator)}. 48 | */ 49 | @Deprecated 50 | default Status getStatus(final CallOption... options) throws CommitStatusException { 51 | return getStatus(GatewayUtils.asCallOptions(options)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/CommitException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import org.hyperledger.fabric.protos.peer.TxValidationCode; 10 | 11 | /** 12 | * Thrown when a transaction fails to commit successfully. 13 | */ 14 | public class CommitException extends Exception { 15 | private static final long serialVersionUID = 1L; 16 | 17 | private final transient Status status; 18 | 19 | private static String message(final Status status) { 20 | TxValidationCode code = status.getCode(); 21 | return "Commit of transaction " + status.getTransactionId() + " failed with status code " + code.getNumber() 22 | + " (" + code.name() + ")"; 23 | } 24 | 25 | /** 26 | * Constructs a new commit exception for the specified transaction. 27 | * @param status Transaction commit status. 28 | */ 29 | CommitException(final Status status) { 30 | super(message(status)); 31 | this.status = status; 32 | } 33 | 34 | /** 35 | * Get the ID of the transaction. 36 | * @return transaction ID. 37 | */ 38 | public String getTransactionId() { 39 | return status.getTransactionId(); 40 | } 41 | 42 | /** 43 | * Get the transaction status code. 44 | * @return transaction status code. 45 | */ 46 | public TxValidationCode getCode() { 47 | return status.getCode(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/CommitStatusException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.StatusRuntimeException; 10 | 11 | /** 12 | * Thrown when a failure occurs obtaining the commit status of a transaction. 13 | */ 14 | public class CommitStatusException extends TransactionException { 15 | private static final long serialVersionUID = 1L; 16 | 17 | /** 18 | * Constructs a new exception with the specified cause. 19 | * @param transactionId a transaction ID. 20 | * @param cause the cause. 21 | */ 22 | public CommitStatusException(final String transactionId, final StatusRuntimeException cause) { 23 | super(transactionId, cause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/EndorseException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.StatusRuntimeException; 10 | 11 | /** 12 | * Thrown when a failure occurs endorsing a transaction proposal. 13 | */ 14 | public class EndorseException extends TransactionException { 15 | private static final long serialVersionUID = 1L; 16 | 17 | /** 18 | * Constructs a new exception with the specified cause. 19 | * @param transactionId a transaction ID. 20 | * @param cause the cause. 21 | */ 22 | public EndorseException(final String transactionId, final StatusRuntimeException cause) { 23 | super(transactionId, cause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/EventsBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | /** 10 | * Builder used to create a new events request. 11 | * 12 | *

If both a start block and checkpoint are specified, and the checkpoint has a valid position set, the 13 | * checkpoint position is used and the specified start block is ignored. If the checkpoint is unset then the start block 14 | * is used.

15 | * 16 | *

If no start position is specified, eventing begins from the next committed block.

17 | * 18 | * @param Event type returned by the request. 19 | */ 20 | public interface EventsBuilder extends Builder> { 21 | /** 22 | * Specify the block number at which to start reading events. 23 | *

Note that the block number is an unsigned 64-bit integer, with the sign bit used to hold the top bit of 24 | * the number.

25 | * @param blockNumber a ledger block number. 26 | * @return This builder. 27 | */ 28 | EventsBuilder startBlock(long blockNumber); 29 | 30 | /** 31 | * Reads events starting at the checkpoint position. 32 | * @param checkpoint a checkpoint position. 33 | * @return This builder. 34 | */ 35 | EventsBuilder checkpoint(Checkpoint checkpoint); 36 | } 37 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/EventsRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.CallOptions; 10 | import java.util.function.UnaryOperator; 11 | 12 | /** 13 | * A Fabric Gateway call to obtain events. 14 | * @param The type of events obtained by this request. 15 | */ 16 | public interface EventsRequest extends Signable { 17 | /** 18 | * Get events. The Java gRPC implementation may not begin reading events until the first use of the returned 19 | * iterator. 20 | *

Note that the returned iterator may throw {@link GatewayRuntimeException} during iteration if a gRPC 21 | * connection error occurs.

22 | * @return Ordered sequence of events. 23 | */ 24 | default CloseableIterator getEvents() { 25 | return getEvents(GatewayUtils.asCallOptions()); 26 | } 27 | 28 | /** 29 | * Get events. The Java gRPC implementation may not begin reading events until the first use of the returned 30 | * iterator. 31 | *

Note that the returned iterator may throw {@link GatewayRuntimeException} during iteration if a gRPC 32 | * connection error occurs.

33 | * @param options Function that transforms call options. 34 | * @return Ordered sequence of events. 35 | */ 36 | CloseableIterator getEvents(UnaryOperator options); 37 | 38 | /** 39 | * Get events. The Java gRPC implementation may not begin reading events until the first use of the returned 40 | * iterator. 41 | *

Note that the returned iterator may throw {@link GatewayRuntimeException} during iteration if a gRPC 42 | * connection error occurs.

43 | * @param options Call options. 44 | * @return Ordered sequence of events. 45 | * @deprecated Replaced by {@link #getEvents(UnaryOperator)}. 46 | */ 47 | @Deprecated 48 | default CloseableIterator getEvents(final CallOption... options) { 49 | return getEvents(GatewayUtils.asCallOptions(options)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/FilteredBlockEventsBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.ByteString; 10 | import java.util.Objects; 11 | import org.hyperledger.fabric.protos.common.Envelope; 12 | 13 | final class FilteredBlockEventsBuilder implements FilteredBlockEventsRequest.Builder { 14 | private final GatewayClient client; 15 | private final SigningIdentity signingIdentity; 16 | private final BlockEventsEnvelopeBuilder envelopeBuilder; 17 | 18 | FilteredBlockEventsBuilder( 19 | final GatewayClient client, 20 | final SigningIdentity signingIdentity, 21 | final String channelName, 22 | final ByteString tlsCertificateHash) { 23 | Objects.requireNonNull(channelName, "channel name"); 24 | 25 | this.client = client; 26 | this.signingIdentity = signingIdentity; 27 | this.envelopeBuilder = new BlockEventsEnvelopeBuilder(signingIdentity, channelName, tlsCertificateHash); 28 | } 29 | 30 | @Override 31 | public FilteredBlockEventsBuilder startBlock(final long blockNumber) { 32 | envelopeBuilder.startBlock(blockNumber); 33 | return this; 34 | } 35 | 36 | @Override 37 | public FilteredBlockEventsBuilder checkpoint(final Checkpoint checkpoint) { 38 | checkpoint.getBlockNumber().ifPresent(envelopeBuilder::startBlock); 39 | return this; 40 | } 41 | 42 | @Override 43 | public FilteredBlockEventsRequest build() { 44 | Envelope request = envelopeBuilder.build(); 45 | return new FilteredBlockEventsRequestImpl(client, signingIdentity, request); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/FilteredBlockEventsRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import org.hyperledger.fabric.protos.peer.FilteredBlock; 10 | 11 | /** 12 | * A Fabric Gateway call to obtain filtered block events. Supports off-line signing flow using 13 | * {@link Gateway#newSignedFilteredBlockEventsRequest(byte[], byte[])}. 14 | */ 15 | public interface FilteredBlockEventsRequest extends EventsRequest { 16 | /** 17 | * Builder used to create a new filtered block events request. The default behavior is to read events from the next 18 | * committed block. 19 | */ 20 | interface Builder extends EventsBuilder { 21 | @Override 22 | Builder startBlock(long blockNumber); 23 | 24 | @Override 25 | Builder checkpoint(Checkpoint checkpoint); 26 | 27 | @Override 28 | FilteredBlockEventsRequest build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/FilteredBlockEventsRequestImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.CallOptions; 10 | import java.util.NoSuchElementException; 11 | import java.util.function.UnaryOperator; 12 | import org.hyperledger.fabric.protos.common.Envelope; 13 | import org.hyperledger.fabric.protos.peer.DeliverResponse; 14 | import org.hyperledger.fabric.protos.peer.FilteredBlock; 15 | 16 | final class FilteredBlockEventsRequestImpl extends SignableBlockEventsRequest implements FilteredBlockEventsRequest { 17 | private final GatewayClient client; 18 | 19 | FilteredBlockEventsRequestImpl( 20 | final GatewayClient client, final SigningIdentity signingIdentity, final Envelope request) { 21 | super(signingIdentity, request); 22 | this.client = client; 23 | } 24 | 25 | @Override 26 | public CloseableIterator getEvents(final UnaryOperator options) { 27 | Envelope request = getSignedRequest(); 28 | CloseableIterator responseIter = client.filteredBlockEvents(request, options); 29 | 30 | return new MappingCloseableIterator<>(responseIter, response -> { 31 | DeliverResponse.TypeCase responseType = response.getTypeCase(); 32 | if (responseType == DeliverResponse.TypeCase.STATUS) { 33 | throw new NoSuchElementException("Unexpected status response: " + response.getStatus()); 34 | } 35 | 36 | return response.getFilteredBlock(); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/GrpcStackTracePrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.io.CharArrayWriter; 10 | import java.io.PrintStream; 11 | import java.io.PrintWriter; 12 | import java.util.List; 13 | import java.util.function.Consumer; 14 | import org.hyperledger.fabric.protos.gateway.ErrorDetail; 15 | 16 | class GrpcStackTracePrinter { 17 | private final Consumer printStackTraceFn; 18 | private final GrpcStatus grpcStatus; 19 | 20 | public GrpcStackTracePrinter(final Consumer printStackTraceFn, final GrpcStatus grpcStatus) { 21 | this.printStackTraceFn = printStackTraceFn; 22 | this.grpcStatus = grpcStatus; 23 | } 24 | 25 | public void printStackTrace(final PrintStream out) { 26 | PrintWriter writer = new PrintWriter(out); 27 | printStackTrace(writer); 28 | writer.flush(); 29 | } 30 | 31 | public void printStackTrace(final PrintWriter out) { 32 | CharArrayWriter message = new CharArrayWriter(); 33 | 34 | try (PrintWriter printer = new PrintWriter(message)) { 35 | printStackTraceFn.accept(printer); 36 | } 37 | 38 | List details = grpcStatus.getDetails(); 39 | if (!details.isEmpty()) { 40 | message.append("Error details:\n"); 41 | for (ErrorDetail detail : details) { 42 | message.append(" address: ") 43 | .append(detail.getAddress()) 44 | .append("; mspId: ") 45 | .append(detail.getMspId()) 46 | .append("; message: ") 47 | .append(detail.getMessage()) 48 | .append('\n'); 49 | } 50 | } 51 | 52 | out.print(message); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/GrpcStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.InvalidProtocolBufferException; 10 | import io.grpc.protobuf.StatusProto; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.stream.Collectors; 14 | import org.hyperledger.fabric.protos.gateway.ErrorDetail; 15 | 16 | final class GrpcStatus { 17 | private final io.grpc.Status status; 18 | private final io.grpc.Metadata trailers; 19 | 20 | GrpcStatus(final io.grpc.Status status, final io.grpc.Metadata trailers) { 21 | this.status = status; 22 | this.trailers = trailers; 23 | } 24 | 25 | public io.grpc.Status getStatus() { 26 | return status; 27 | } 28 | 29 | public List getDetails() { 30 | return StatusProto.fromStatusAndTrailers(status, trailers).getDetailsList().stream() 31 | .map(any -> { 32 | try { 33 | return any.unpack(ErrorDetail.class); 34 | } catch (InvalidProtocolBufferException e) { 35 | return null; 36 | } 37 | }) 38 | .filter(Objects::nonNull) 39 | .collect(Collectors.toList()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/InMemoryCheckpointer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.util.Optional; 10 | import java.util.OptionalLong; 11 | 12 | /** 13 | * A non-persistent Checkpointer implementation. 14 | * It can be used to checkpoint progress after successfully processing events, allowing eventing to be resumed from this point. 15 | */ 16 | public final class InMemoryCheckpointer implements Checkpointer { 17 | 18 | private OptionalLong blockNumber = OptionalLong.empty(); 19 | private String transactionId; 20 | 21 | @Override 22 | public void checkpointBlock(final long blockNumber) { 23 | checkpointTransaction(blockNumber + 1, null); 24 | } 25 | 26 | @Override 27 | public void checkpointTransaction(final long blockNumber, final String transactionId) { 28 | this.blockNumber = OptionalLong.of(blockNumber); 29 | this.transactionId = transactionId; 30 | } 31 | 32 | @Override 33 | public void checkpointChaincodeEvent(final ChaincodeEvent event) { 34 | checkpointTransaction(event.getBlockNumber(), event.getTransactionId()); 35 | } 36 | 37 | @Override 38 | public OptionalLong getBlockNumber() { 39 | return blockNumber; 40 | } 41 | 42 | @Override 43 | public Optional getTransactionId() { 44 | return Optional.ofNullable(transactionId); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/MappingCloseableIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.util.function.Function; 10 | 11 | final class MappingCloseableIterator implements CloseableIterator { 12 | private final CloseableIterator iterator; 13 | private final Function mapper; 14 | 15 | MappingCloseableIterator(final CloseableIterator iterator, final Function mapper) { 16 | this.iterator = iterator; 17 | this.mapper = mapper; 18 | } 19 | 20 | @Override 21 | public void close() { 22 | iterator.close(); 23 | } 24 | 25 | @Override 26 | public boolean hasNext() { 27 | return iterator.hasNext(); 28 | } 29 | 30 | @Override 31 | public R next() { 32 | T value = iterator.next(); 33 | return mapper.apply(value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/Signable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | /** 10 | * A Fabric Gateway call that can be explicitly signed. Supports off-line signing flow. 11 | */ 12 | public interface Signable { 13 | /** 14 | * Get the serialized message bytes. 15 | * Serialized bytes can be used to recreate the object using methods on {@link Gateway}. 16 | * @return A serialized signable object. 17 | */ 18 | byte[] getBytes(); 19 | 20 | /** 21 | * Get the digest of the signable object. This is used to generate a digital signature. 22 | * @return A hash of the signable object. 23 | */ 24 | byte[] getDigest(); 25 | } 26 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/SignableBlockEventsRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.ByteString; 10 | import org.hyperledger.fabric.protos.common.Envelope; 11 | 12 | class SignableBlockEventsRequest implements Signable { 13 | private final SigningIdentity signingIdentity; 14 | private Envelope request; 15 | 16 | SignableBlockEventsRequest(final SigningIdentity signingIdentity, final Envelope request) { 17 | this.signingIdentity = signingIdentity; 18 | this.request = request; 19 | } 20 | 21 | protected Envelope getSignedRequest() { 22 | if (!isSigned()) { 23 | byte[] digest = getDigest(); 24 | byte[] signature = signingIdentity.sign(digest); 25 | setSignature(signature); 26 | } 27 | 28 | return request; 29 | } 30 | 31 | @Override 32 | public byte[] getBytes() { 33 | return request.toByteArray(); 34 | } 35 | 36 | @Override 37 | public byte[] getDigest() { 38 | byte[] message = request.getPayload().toByteArray(); 39 | return signingIdentity.hash(message); 40 | } 41 | 42 | void setSignature(final byte[] signature) { 43 | request = 44 | request.toBuilder().setSignature(ByteString.copyFrom(signature)).build(); 45 | } 46 | 47 | private boolean isSigned() { 48 | return !request.getSignature().isEmpty(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/SigningIdentity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.security.GeneralSecurityException; 10 | import java.security.ProviderException; 11 | import java.util.function.Function; 12 | import org.hyperledger.fabric.client.identity.Identity; 13 | import org.hyperledger.fabric.client.identity.Signer; 14 | import org.hyperledger.fabric.protos.msp.SerializedIdentity; 15 | 16 | final class SigningIdentity { 17 | private final Identity identity; 18 | private final Function hash; 19 | private final Signer signer; 20 | private final SerializedIdentity creator; 21 | 22 | SigningIdentity(final Identity identity, final Function hash, final Signer signer) { 23 | this.identity = identity; 24 | this.hash = hash; 25 | this.signer = signer; 26 | 27 | GatewayUtils.requireNonNullArgument(this.identity, "No identity supplied"); 28 | GatewayUtils.requireNonNullArgument(this.hash, "No hash implementation supplied"); 29 | GatewayUtils.requireNonNullArgument(this.signer, "No signing implementation supplied"); 30 | 31 | this.creator = GatewayUtils.newSerializedIdentity(identity); 32 | } 33 | 34 | public Identity getIdentity() { 35 | return identity; 36 | } 37 | 38 | public byte[] hash(final byte[] message) { 39 | return hash.apply(message); 40 | } 41 | 42 | public byte[] sign(final byte[] digest) { 43 | try { 44 | return signer.sign(digest); 45 | } catch (GeneralSecurityException e) { 46 | throw new ProviderException(e); 47 | } 48 | } 49 | 50 | public byte[] getCreator() { 51 | return creator.toByteArray(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/StartPositionBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import org.hyperledger.fabric.protos.orderer.SeekNextCommit; 10 | import org.hyperledger.fabric.protos.orderer.SeekPosition; 11 | import org.hyperledger.fabric.protos.orderer.SeekSpecified; 12 | 13 | final class StartPositionBuilder implements Builder { 14 | private final SeekPosition.Builder builder = 15 | SeekPosition.newBuilder().setNextCommit(SeekNextCommit.getDefaultInstance()); 16 | 17 | public StartPositionBuilder startBlock(final long blockNumber) { 18 | SeekSpecified specified = 19 | SeekSpecified.newBuilder().setNumber(blockNumber).build(); 20 | builder.setSpecified(specified); 21 | return this; 22 | } 23 | 24 | @Override 25 | public SeekPosition build() { 26 | return builder.build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/Status.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import org.hyperledger.fabric.protos.peer.TxValidationCode; 10 | 11 | /** 12 | * Information about a transaction that is committed to the ledger. 13 | */ 14 | public interface Status { 15 | /** 16 | * Get the transaction ID. 17 | * @return A transaction ID. 18 | */ 19 | String getTransactionId(); 20 | 21 | /** 22 | * Get the block number in which the transaction committed. 23 | *

Note that the block number is an unsigned 64-bit integer, with the sign bit used to hold the top bit of 24 | * the number.

25 | * @return A block number. 26 | */ 27 | long getBlockNumber(); 28 | 29 | /** 30 | * Get the committed transaction status code. 31 | * @return Transaction status code. 32 | */ 33 | TxValidationCode getCode(); 34 | 35 | /** 36 | * Check whether the transaction committed successfully. 37 | * @return {@code true} if the transaction committed successfully; otherwise {@code false}. 38 | */ 39 | boolean isSuccessful(); 40 | } 41 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/StatusImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import org.hyperledger.fabric.protos.gateway.CommitStatusResponse; 10 | import org.hyperledger.fabric.protos.peer.TxValidationCode; 11 | 12 | /** 13 | * Information about a transaction that is committed to the ledger. 14 | */ 15 | final class StatusImpl implements Status { 16 | private final String transactionId; 17 | private final long blockNumber; 18 | private final TxValidationCode code; 19 | 20 | StatusImpl(final String transactionId, final CommitStatusResponse response) { 21 | this.transactionId = transactionId; 22 | this.blockNumber = response.getBlockNumber(); 23 | this.code = response.getResult(); 24 | } 25 | 26 | @Override 27 | public String getTransactionId() { 28 | return transactionId; 29 | } 30 | 31 | @Override 32 | public long getBlockNumber() { 33 | return blockNumber; 34 | } 35 | 36 | @Override 37 | public TxValidationCode getCode() { 38 | return code; 39 | } 40 | 41 | @Override 42 | public boolean isSuccessful() { 43 | return code == TxValidationCode.VALID; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return GatewayUtils.toString( 49 | this, 50 | "transactionId: " + transactionId, 51 | "code: " + code.getNumber() + " (" + code.name() + ")", 52 | "blockNumber: " + blockNumber); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/SubmitException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.StatusRuntimeException; 10 | 11 | /** 12 | * Thrown when a failure occurs submitting an endorsed transaction to the orderer. 13 | */ 14 | public class SubmitException extends TransactionException { 15 | private static final long serialVersionUID = 1L; 16 | 17 | /** 18 | * Constructs a new exception with the specified cause. 19 | * @param transactionId a transaction ID. 20 | * @param cause the cause. 21 | */ 22 | public SubmitException(final String transactionId, final StatusRuntimeException cause) { 23 | super(transactionId, cause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/SubmittedTransaction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | /** 10 | * Allows access to the transaction result and its commit status on the ledger. 11 | */ 12 | public interface SubmittedTransaction extends Commit { 13 | /** 14 | * Get the transaction result. This is obtained during the endorsement process when the transaction proposal is 15 | * run on endorsing peers and so is available immediately. The transaction might subsequently fail to commit 16 | * successfully. 17 | * @return Transaction result. 18 | */ 19 | byte[] getResult(); 20 | } 21 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/SubmittedTransactionImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.ByteString; 10 | import org.hyperledger.fabric.protos.gateway.SignedCommitStatusRequest; 11 | 12 | final class SubmittedTransactionImpl extends CommitImpl implements SubmittedTransaction { 13 | private final ByteString result; 14 | 15 | SubmittedTransactionImpl( 16 | final GatewayClient client, 17 | final SigningIdentity signingIdentity, 18 | final String transactionId, 19 | final SignedCommitStatusRequest signedRequest, 20 | final ByteString result) { 21 | super(client, signingIdentity, transactionId, signedRequest); 22 | this.result = result; 23 | } 24 | 25 | @Override 26 | public byte[] getResult() { 27 | return result.toByteArray(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/TransactionContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import com.google.protobuf.ByteString; 10 | import java.nio.charset.StandardCharsets; 11 | import java.security.SecureRandom; 12 | import org.bouncycastle.util.encoders.Hex; 13 | import org.hyperledger.fabric.protos.common.SignatureHeader; 14 | 15 | final class TransactionContext { 16 | private static final int NONCE_LENGTH = 24; 17 | private static final SecureRandom RANDOM = new SecureRandom(); 18 | 19 | private final SigningIdentity signingIdentity; 20 | private final byte[] nonce; 21 | private final String transactionId; 22 | private final SignatureHeader signatureHeader; 23 | 24 | TransactionContext(final SigningIdentity signingIdentity) { 25 | this.signingIdentity = signingIdentity; 26 | nonce = newNonce(); 27 | 28 | transactionId = newTransactionId(); 29 | signatureHeader = newSignatureHeader(); 30 | } 31 | 32 | private static byte[] newNonce() { 33 | byte[] values = new byte[NONCE_LENGTH]; 34 | RANDOM.nextBytes(values); 35 | return values; 36 | } 37 | 38 | private String newTransactionId() { 39 | byte[] saltedCreator = GatewayUtils.concat(nonce, signingIdentity.getCreator()); 40 | byte[] rawTransactionId = Hash.SHA256.apply(saltedCreator); 41 | byte[] hexTransactionId = Hex.encode(rawTransactionId); 42 | return new String(hexTransactionId, StandardCharsets.UTF_8); 43 | } 44 | 45 | private SignatureHeader newSignatureHeader() { 46 | return SignatureHeader.newBuilder() 47 | .setCreator(ByteString.copyFrom(signingIdentity.getCreator())) 48 | .setNonce(ByteString.copyFrom(nonce)) 49 | .build(); 50 | } 51 | 52 | public String getTransactionId() { 53 | return transactionId; 54 | } 55 | 56 | public SignatureHeader getSignatureHeader() { 57 | return signatureHeader; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/TransactionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.StatusRuntimeException; 10 | 11 | /** 12 | * Thrown when a failure occurs invoking a transaction. 13 | */ 14 | public class TransactionException extends GatewayException { 15 | private static final long serialVersionUID = 1L; 16 | 17 | private final String transactionId; 18 | 19 | /** 20 | * Constructs a new exception with the specified cause. 21 | * @param transactionId a transaction ID. 22 | * @param cause the cause. 23 | */ 24 | public TransactionException(final String transactionId, final StatusRuntimeException cause) { 25 | super(cause); 26 | this.transactionId = transactionId; 27 | } 28 | 29 | /** 30 | * The ID of the transaction. 31 | * @return a transaction ID. 32 | */ 33 | public String getTransactionId() { 34 | return transactionId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/identity/ECPrivateKeySigner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client.identity; 8 | 9 | import java.math.BigInteger; 10 | import java.security.GeneralSecurityException; 11 | import java.security.interfaces.ECPrivateKey; 12 | import org.bouncycastle.asn1.ASN1Integer; 13 | 14 | final class ECPrivateKeySigner implements Signer { 15 | private static final String ALGORITHM_NAME = "NONEwithECDSA"; 16 | 17 | private final Signer signer; 18 | private final BigInteger curveN; 19 | private final BigInteger halfCurveN; 20 | 21 | ECPrivateKeySigner(final ECPrivateKey privateKey) { 22 | signer = new PrivateKeySigner(privateKey, ALGORITHM_NAME); 23 | curveN = privateKey.getParams().getOrder(); 24 | halfCurveN = curveN.divide(BigInteger.valueOf(2)); 25 | } 26 | 27 | @Override 28 | public byte[] sign(final byte[] digest) throws GeneralSecurityException { 29 | byte[] rawSignature = signer.sign(digest); 30 | ECSignature signature = ECSignature.fromBytes(rawSignature); 31 | signature = preventMalleability(signature); 32 | return signature.getBytes(); 33 | } 34 | 35 | private ECSignature preventMalleability(final ECSignature signature) { 36 | BigInteger s = signature.getS().getValue(); 37 | if (s.compareTo(halfCurveN) > 0) { 38 | s = curveN.subtract(s); 39 | return new ECSignature(signature.getR(), new ASN1Integer(s)); 40 | } 41 | return signature; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/identity/Identity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client.identity; 8 | 9 | /** 10 | * Represents a client identity used to interact with a Fabric network. The identity consists of an identifier for the 11 | * organization to which the identity belongs, and implementation-specific credentials describing the identity. 12 | */ 13 | public interface Identity { 14 | /** 15 | * Member services provider to which this identity is associated. 16 | * @return A member services provider identifier. 17 | */ 18 | String getMspId(); 19 | 20 | /** 21 | * Implementation-specific credentials. 22 | * @return Credential data. 23 | */ 24 | byte[] getCredentials(); 25 | } 26 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/identity/PrivateKeySigner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client.identity; 8 | 9 | import java.security.GeneralSecurityException; 10 | import java.security.PrivateKey; 11 | import java.security.Provider; 12 | import java.security.Signature; 13 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 14 | 15 | final class PrivateKeySigner implements Signer { 16 | private static final Provider PROVIDER = new BouncyCastleProvider(); 17 | 18 | private final PrivateKey privateKey; 19 | private final String algorithm; 20 | 21 | PrivateKeySigner(final PrivateKey privateKey, final String algorithm) { 22 | this.privateKey = privateKey; 23 | this.algorithm = algorithm; 24 | } 25 | 26 | @Override 27 | public byte[] sign(final byte[] digest) throws GeneralSecurityException { 28 | Signature signer = Signature.getInstance(algorithm, PROVIDER); 29 | signer.initSign(privateKey); 30 | signer.update(digest); 31 | return signer.sign(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/identity/Signer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client.identity; 8 | 9 | import java.security.GeneralSecurityException; 10 | 11 | /** 12 | * A signing implementation used to generate digital signatures. Standard implementations can be obtained using factory 13 | * methods on the {@link Signers} class. 14 | */ 15 | @FunctionalInterface 16 | public interface Signer { 17 | /** 18 | * Signs the supplied message digest. The digest is typically a hash of the message. 19 | * @param digest A message digest. 20 | * @return A digital signature. 21 | * @throws GeneralSecurityException if a signing error occurs. 22 | */ 23 | byte[] sign(byte[] digest) throws GeneralSecurityException; 24 | } 25 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/identity/Signers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client.identity; 8 | 9 | import java.security.PrivateKey; 10 | import java.security.interfaces.ECPrivateKey; 11 | 12 | /** 13 | * Factory methods to create standard signing implementations. 14 | */ 15 | public final class Signers { 16 | private static final String ED25519_ALGORITHM = "Ed25519"; 17 | 18 | /** 19 | * Create a new signer that uses the supplied private key for signing. The {@link Identities} class provides static 20 | * methods to create a {@code PrivateKey} object from PEM-format data. 21 | * 22 | *

Currently supported private key types are:

23 | *
    24 | *
  • ECDSA.
  • 25 | *
  • Ed25519.
  • 26 | *
27 | * 28 | *

Note that the Sign implementations have different expectations on the input data supplied to them.

29 | * 30 | *

The ECDSA signers operate on a pre-computed message digest, and should be combined with an appropriate hash 31 | * algorithm. P-256 is typically used with a SHA-256 hash, and P-384 is typically used with a SHA-384 hash.

32 | * 33 | *

The Ed25519 signer operates on the full message content, and should be combined with a 34 | * {@link org.hyperledger.fabric.client.Hash#NONE NONE} (or no-op) hash implementation to ensure the complete 35 | * message is passed to the signer.

36 | * @param privateKey A private key. 37 | * @return A signer implementation. 38 | */ 39 | public static Signer newPrivateKeySigner(final PrivateKey privateKey) { 40 | if (privateKey instanceof ECPrivateKey) { 41 | return new ECPrivateKeySigner((ECPrivateKey) privateKey); 42 | } 43 | 44 | if (ED25519_ALGORITHM.equals(privateKey.getAlgorithm())) { 45 | return new PrivateKeySigner(privateKey, ED25519_ALGORITHM); 46 | } 47 | 48 | throw new IllegalArgumentException("Unsupported private key type: " 49 | + privateKey.getClass().getTypeName() + " (" + privateKey.getAlgorithm() + ")"); 50 | } 51 | 52 | // Private constructor to prevent instantiation 53 | private Signers() {} 54 | } 55 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/identity/X509Identity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client.identity; 8 | 9 | import java.nio.charset.StandardCharsets; 10 | import java.security.cert.X509Certificate; 11 | import java.util.Arrays; 12 | import java.util.Objects; 13 | 14 | /** 15 | * A client identity described by an X.509 certificate. The {@link Identities} class provides static methods to create 16 | * an {@code X509Certificate} object from PEM-format data. 17 | */ 18 | public final class X509Identity implements Identity { 19 | private final String mspId; 20 | private final X509Certificate certificate; 21 | private final byte[] credentials; 22 | 23 | /** 24 | * Constructor. 25 | * @param mspId A membership service provider identifier. 26 | * @param certificate An X.509 certificate. 27 | */ 28 | public X509Identity(final String mspId, final X509Certificate certificate) { 29 | this.mspId = mspId; 30 | this.certificate = certificate; 31 | credentials = Identities.toPemString(certificate).getBytes(StandardCharsets.UTF_8); 32 | } 33 | 34 | /** 35 | * Get the certificate for this identity. 36 | * @return An X.509 certificate. 37 | */ 38 | public X509Certificate getCertificate() { 39 | return certificate; 40 | } 41 | 42 | @Override 43 | public String getMspId() { 44 | return mspId; 45 | } 46 | 47 | @Override 48 | public byte[] getCredentials() { 49 | return credentials.clone(); 50 | } 51 | 52 | @Override 53 | public boolean equals(final Object other) { 54 | if (this == other) { 55 | return true; 56 | } 57 | if (!(other instanceof X509Identity)) { 58 | return false; 59 | } 60 | 61 | X509Identity that = (X509Identity) other; 62 | return Objects.equals(this.mspId, that.mspId) && Arrays.equals(this.credentials, that.credentials); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(mspId, Arrays.hashCode(credentials)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/identity/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | *

Provides classes and interfaces for describing client identity and for creating digital signatures on behalf of 9 | * client identities.

10 | */ 11 | package org.hyperledger.fabric.client.identity; 12 | -------------------------------------------------------------------------------- /java/src/main/java/org/hyperledger/fabric/client/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | *

This package provides the set of interfaces that enable a Java application to 9 | * interact with a Fabric blockchain network. It provides a simple API to 10 | * submit transactions to a ledger or query the contents of a ledger with minimal code. 11 | * The Gateway SDK implements the Fabric programming model as described in the 12 | * Running a Fabric Application 13 | * tutorial of the Fabric documentation.

14 | * 15 | *

The entry point into this package is {@link org.hyperledger.fabric.client.Gateway Gateway}.

16 | * 17 | * @see Running a Fabric Application 18 | */ 19 | package org.hyperledger.fabric.client; 20 | -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSans-Bold.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSans-BoldOblique.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSans-BoldOblique.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSans-Oblique.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSans-Oblique.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSans.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono-Bold.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono-BoldOblique.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono-BoldOblique.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono-Oblique.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono-Oblique.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSansMono.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSerif-Bold.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSerif-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSerif-BoldItalic.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSerif-Italic.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/DejaVuLGCSerif.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger/fabric-gateway/6b09d8b135b7f111a3a0e6f8e2ba08059589b62c/java/src/main/javadoc/resources/fonts/DejaVuLGCSerif.woff2 -------------------------------------------------------------------------------- /java/src/main/javadoc/resources/fonts/dejavu.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'DejaVu Sans Mono'; 3 | src: url('DejaVuLGCSansMono.woff2') format('woff2'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | @font-face { 8 | font-family: 'DejaVu Sans Mono'; 9 | src: url('DejaVuLGCSansMono-Oblique.woff2') format('woff2'); 10 | font-weight: normal; 11 | font-style: italic; 12 | } 13 | @font-face { 14 | font-family: 'DejaVu Sans Mono'; 15 | src: url('DejaVuLGCSansMono-Bold.woff2') format('woff2'); 16 | font-weight: bold; 17 | font-style: normal; 18 | } 19 | @font-face { 20 | font-family: 'DejaVu Sans Mono'; 21 | src: url('DejaVuLGCSansMono-BoldOblique.woff2') format('woff2'); 22 | font-weight: bold; 23 | font-style: italic; 24 | } 25 | 26 | @font-face { 27 | font-family: 'DejaVu Sans'; 28 | src: url('DejaVuLGCSans.woff2') format('woff2'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | @font-face { 33 | font-family: 'DejaVu Sans'; 34 | src: url('DejaVuLGCSans-Oblique.woff2') format('woff2'); 35 | font-weight: normal; 36 | font-style: italic; 37 | } 38 | @font-face { 39 | font-family: 'DejaVu Sans'; 40 | src: url('DejaVuLGCSans-Bold.woff2') format('woff2'); 41 | font-weight: bold; 42 | font-style: normal; 43 | } 44 | @font-face { 45 | font-family: 'DejaVu Sans'; 46 | src: url('DejaVuLGCSans-BoldOblique.woff2') format('woff2'); 47 | font-weight: bold; 48 | font-style: italic; 49 | } 50 | 51 | @font-face { 52 | font-family: 'DejaVu Serif'; 53 | src: url('DejaVuLGCSerif.woff2') format('woff2'); 54 | font-weight: normal; 55 | font-style: normal; 56 | } 57 | @font-face { 58 | font-family: 'DejaVu Serif'; 59 | src: url('DejaVuLGCSerif-Italic.woff2') format('woff2'); 60 | font-weight: normal; 61 | font-style: italic; 62 | } 63 | @font-face { 64 | font-family: 'DejaVu Serif'; 65 | src: url('DejaVuLGCSerif-Bold.woff2') format('woff2'); 66 | font-weight: bold; 67 | font-style: normal; 68 | } 69 | @font-face { 70 | font-family: 'DejaVu Serif'; 71 | src: url('DejaVuLGCSerif-BoldItalic.woff2') format('woff2'); 72 | font-weight: bold; 73 | font-style: italic; 74 | } 75 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/BlockEventsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import static org.mockito.Mockito.any; 10 | import static org.mockito.Mockito.doReturn; 11 | import static org.mockito.Mockito.doThrow; 12 | 13 | import io.grpc.CallOptions; 14 | import java.util.function.UnaryOperator; 15 | import java.util.stream.Stream; 16 | import org.hyperledger.fabric.protos.common.Block; 17 | import org.hyperledger.fabric.protos.common.BlockHeader; 18 | import org.hyperledger.fabric.protos.common.Envelope; 19 | import org.hyperledger.fabric.protos.peer.DeliverResponse; 20 | 21 | public final class BlockEventsTest extends CommonBlockEventsTest { 22 | @Override 23 | protected void setEventsOptions(final Gateway.Builder builder, final UnaryOperator options) { 24 | builder.blockEventsOptions(options); 25 | } 26 | 27 | @Override 28 | protected DeliverResponse newDeliverResponse(final long blockNumber) { 29 | return DeliverResponse.newBuilder() 30 | .setBlock(Block.newBuilder().setHeader(BlockHeader.newBuilder().setNumber(blockNumber))) 31 | .build(); 32 | } 33 | 34 | @Override 35 | protected void stubDoThrow(final Throwable... t) { 36 | doThrow(t).when(stub).blockEvents(any()); 37 | } 38 | 39 | @Override 40 | protected CloseableIterator getEvents() { 41 | return network.getBlockEvents(); 42 | } 43 | 44 | @Override 45 | protected CloseableIterator getEvents(final UnaryOperator options) { 46 | return network.getBlockEvents(options); 47 | } 48 | 49 | @Override 50 | protected Stream captureEvents() { 51 | return mocker.captureBlockEvents(); 52 | } 53 | 54 | @Override 55 | protected EventsBuilder newEventsRequest() { 56 | return network.newBlockEventsRequest(); 57 | } 58 | 59 | @Override 60 | protected void stubDoReturn(final Stream responses) { 61 | doReturn(responses).when(stub).blockEvents(any()); 62 | } 63 | 64 | @Override 65 | protected Block extractEvent(final DeliverResponse response) { 66 | return response.getBlock(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/DeliverServiceStub.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import java.util.stream.Stream; 10 | import org.hyperledger.fabric.protos.common.Envelope; 11 | import org.hyperledger.fabric.protos.peer.DeliverResponse; 12 | 13 | /** 14 | * Simplified stub implementation for Deliver gRPC service, to be used as a spy by unit tests. 15 | */ 16 | public class DeliverServiceStub { 17 | public Stream blockEvents(final Stream requests) { 18 | return Stream.empty(); 19 | } 20 | 21 | public Stream filteredBlockEvents(final Stream requests) { 22 | return Stream.empty(); 23 | } 24 | 25 | public Stream blockAndPrivateDataEvents(final Stream requests) { 26 | return Stream.empty(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/FileCheckpointerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | 11 | import java.io.IOException; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import org.junit.jupiter.api.Test; 15 | 16 | public class FileCheckpointerTest extends CommonCheckpointerTest { 17 | private FileCheckpointer checkpointer; 18 | private static final TestUtils testUtils = TestUtils.getInstance(); 19 | 20 | @Override 21 | protected FileCheckpointer getCheckpointerInstance() throws IOException { 22 | Path file = testUtils.createTempFile(".json"); 23 | checkpointer = new FileCheckpointer(file); 24 | return checkpointer; 25 | } 26 | 27 | @Override 28 | protected void tearDown() throws IOException { 29 | checkpointer.close(); 30 | } 31 | 32 | @Test 33 | void state_is_persisted() throws IOException { 34 | Path file = testUtils.createTempFile(".json"); 35 | try (FileCheckpointer checkpointer = new FileCheckpointer(file)) { 36 | checkpointer.checkpointTransaction(1, "TRANSACTION_ID"); 37 | } 38 | checkpointer = new FileCheckpointer(file); 39 | assertCheckpoint(checkpointer, 1, "TRANSACTION_ID"); 40 | } 41 | 42 | @Test 43 | void partial_state_is_persisted() throws IOException { 44 | Path file = testUtils.createTempFile(".json"); 45 | try (FileCheckpointer checkpointer = new FileCheckpointer(file)) { 46 | checkpointer.checkpointBlock(1); 47 | } 48 | checkpointer = new FileCheckpointer(file); 49 | assertCheckpoint(checkpointer, 2); 50 | } 51 | 52 | @Test 53 | void block_number_zero_is_persisted_correctly() throws IOException { 54 | getCheckpointerInstance(); 55 | checkpointer.checkpointBlock(0); 56 | assertCheckpoint(this.checkpointer, 1); 57 | } 58 | 59 | @Test 60 | void throws_on_unwritable_file_location() { 61 | Path badPath = Paths.get("").resolve("/missing_directory/checkpoint.json"); 62 | assertThrows(IOException.class, () -> new FileCheckpointer(badPath)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/GatewayExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.StatusRuntimeException; 10 | 11 | final class GatewayExceptionTest extends CommonGatewayExceptionTest { 12 | protected Exception newInstance(final StatusRuntimeException e) { 13 | return new GatewayException(e); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/GatewayRuntimeExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.StatusRuntimeException; 10 | 11 | final class GatewayRuntimeExceptionTest extends CommonGatewayExceptionTest { 12 | protected Exception newInstance(final StatusRuntimeException e) { 13 | return new GatewayRuntimeException(e); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/HashTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | import java.security.Security; 13 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 14 | import org.junit.jupiter.api.AfterAll; 15 | import org.junit.jupiter.api.BeforeAll; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.params.ParameterizedTest; 18 | import org.junit.jupiter.params.provider.EnumSource; 19 | 20 | public final class HashTest { 21 | @BeforeAll 22 | static void beforeAll() { 23 | // Required from some Java 1.8 distributions that don't include SHA3 message digests 24 | Security.addProvider(new BouncyCastleProvider()); 25 | } 26 | 27 | @AfterAll 28 | static void afterAll() { 29 | Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); 30 | } 31 | 32 | @ParameterizedTest 33 | @EnumSource(Hash.class) 34 | void identical_messages_have_identical_hash(Hash hash) { 35 | byte[] message = "MESSAGE".getBytes(StandardCharsets.UTF_8); 36 | 37 | byte[] hash1 = hash.apply(message); 38 | byte[] hash2 = hash.apply(message); 39 | 40 | assertThat(hash1).isEqualTo(hash2); 41 | } 42 | 43 | @ParameterizedTest 44 | @EnumSource(Hash.class) 45 | void different_messages_have_different_hash(Hash hash) { 46 | byte[] foo = "foo".getBytes(StandardCharsets.UTF_8); 47 | byte[] bar = "bar".getBytes(StandardCharsets.UTF_8); 48 | 49 | byte[] fooHash = hash.apply(foo); 50 | byte[] barHash = hash.apply(bar); 51 | 52 | assertThat(fooHash).isNotEqualTo(barHash); 53 | } 54 | 55 | @Test 56 | void NONE_returns_input() { 57 | byte[] message = "MESSAGE".getBytes(StandardCharsets.UTF_8); 58 | byte[] result = Hash.NONE.apply(message); 59 | assertThat(result).isEqualTo(message); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/InMemoryCheckpointerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | public class InMemoryCheckpointerTest extends CommonCheckpointerTest { 10 | 11 | @Override 12 | protected InMemoryCheckpointer getCheckpointerInstance() { 13 | return new InMemoryCheckpointer(); 14 | } 15 | 16 | @Override 17 | protected void tearDown() {} 18 | } 19 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/MockDeliverService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.stub.ServerCallStreamObserver; 10 | import io.grpc.stub.StreamObserver; 11 | import org.hyperledger.fabric.protos.common.Envelope; 12 | import org.hyperledger.fabric.protos.peer.DeliverGrpc; 13 | import org.hyperledger.fabric.protos.peer.DeliverResponse; 14 | 15 | /** 16 | * Mock Deliver gRPC service that acts as an adapter for a stub implementation. 17 | */ 18 | public final class MockDeliverService extends DeliverGrpc.DeliverImplBase { 19 | private static final TestUtils testUtils = TestUtils.getInstance(); 20 | 21 | private final DeliverServiceStub stub; 22 | 23 | public MockDeliverService(final DeliverServiceStub stub) { 24 | this.stub = stub; 25 | } 26 | 27 | @Override 28 | public StreamObserver deliver(final StreamObserver responseObserver) { 29 | return testUtils.invokeStubDuplexCall( 30 | stub::blockEvents, (ServerCallStreamObserver) responseObserver, 1); 31 | } 32 | 33 | @Override 34 | public StreamObserver deliverFiltered(final StreamObserver responseObserver) { 35 | return testUtils.invokeStubDuplexCall( 36 | stub::filteredBlockEvents, (ServerCallStreamObserver) responseObserver, 1); 37 | } 38 | 39 | @Override 40 | public StreamObserver deliverWithPrivateData(final StreamObserver responseObserver) { 41 | return testUtils.invokeStubDuplexCall( 42 | stub::blockAndPrivateDataEvents, (ServerCallStreamObserver) responseObserver, 1); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/NetworkTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 11 | 12 | import io.grpc.ManagedChannel; 13 | import io.grpc.ManagedChannelBuilder; 14 | import java.util.concurrent.TimeUnit; 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | 19 | public final class NetworkTest { 20 | private static final TestUtils testUtils = TestUtils.getInstance(); 21 | 22 | private Gateway gateway; 23 | private Network network; 24 | private ManagedChannel channel; 25 | 26 | @BeforeEach 27 | void beforeEach() { 28 | channel = ManagedChannelBuilder.forAddress("example.org", 1337) 29 | .usePlaintext() 30 | .build(); 31 | gateway = testUtils.newGatewayBuilder().connection(channel).connect(); 32 | network = gateway.getNetwork("ch1"); 33 | } 34 | 35 | @AfterEach 36 | void afterEach() { 37 | gateway.close(); 38 | testUtils.shutdownChannel(channel, 5, TimeUnit.SECONDS); 39 | } 40 | 41 | @Test 42 | void getContract_using_only_chaincode_name_returns_correctly_named_Contract() { 43 | Contract contract = network.getContract("CHAINCODE_NAME"); 44 | assertThat(contract.getChaincodeName()).isEqualTo("CHAINCODE_NAME"); 45 | assertThat(contract.getContractName()).isEmpty(); 46 | } 47 | 48 | @Test 49 | void getContract_using_contract_name_returns_correctly_named_Contract() { 50 | Contract contract = network.getContract("CHAINCODE_NAME", "CONTRACT"); 51 | assertThat(contract.getChaincodeName()).isEqualTo("CHAINCODE_NAME"); 52 | assertThat(contract.getContractName()).get().isEqualTo("CONTRACT"); 53 | } 54 | 55 | @Test 56 | void getContract_throws_NullPointerException_on_null_chaincode_name() { 57 | assertThatThrownBy(() -> network.getContract(null)) 58 | .isInstanceOf(NullPointerException.class) 59 | .hasMessageContaining("chaincode name"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /java/src/test/java/org/hyperledger/fabric/client/WrappedManagedChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.hyperledger.fabric.client; 8 | 9 | import io.grpc.CallOptions; 10 | import io.grpc.ClientCall; 11 | import io.grpc.ManagedChannel; 12 | import io.grpc.MethodDescriptor; 13 | import java.util.Objects; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * Wrapper for an existing managed channel to allow mocking of final channel implementation classes. 18 | */ 19 | public class WrappedManagedChannel extends ManagedChannel { 20 | private final ManagedChannel channel; 21 | 22 | public WrappedManagedChannel(final ManagedChannel channel) { 23 | Objects.requireNonNull(channel); 24 | this.channel = channel; 25 | } 26 | 27 | @Override 28 | public ManagedChannel shutdown() { 29 | return channel.shutdown(); 30 | } 31 | 32 | @Override 33 | public boolean isShutdown() { 34 | return channel.isShutdown(); 35 | } 36 | 37 | @Override 38 | public boolean isTerminated() { 39 | return channel.isTerminated(); 40 | } 41 | 42 | @Override 43 | public ManagedChannel shutdownNow() { 44 | return channel.shutdownNow(); 45 | } 46 | 47 | @Override 48 | public boolean awaitTermination(final long l, final TimeUnit timeUnit) throws InterruptedException { 49 | return channel.awaitTermination(l, timeUnit); 50 | } 51 | 52 | @Override 53 | public ClientCall newCall( 54 | final MethodDescriptor methodDescriptor, final CallOptions callOptions) { 55 | return channel.newCall(methodDescriptor, callOptions); 56 | } 57 | 58 | @Override 59 | public String authority() { 60 | return channel.authority(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /java/src/test/java/scenario/BasicEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package scenario; 8 | 9 | import io.grpc.Status; 10 | import java.util.Objects; 11 | import java.util.concurrent.BlockingQueue; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Executors; 14 | import java.util.concurrent.SynchronousQueue; 15 | import java.util.concurrent.TimeUnit; 16 | import org.hyperledger.fabric.client.CloseableIterator; 17 | import org.hyperledger.fabric.client.GatewayRuntimeException; 18 | 19 | public final class BasicEventListener implements EventListener { 20 | private final BlockingQueue eventQueue = new SynchronousQueue<>(); 21 | private final ExecutorService executor = Executors.newSingleThreadExecutor(); 22 | private final CloseableIterator iterator; 23 | 24 | public BasicEventListener(final CloseableIterator iterator) { 25 | this.iterator = iterator; 26 | 27 | // Start reading events immediately as Java gRPC implementation may not invoke the gRPC service until the first 28 | // read attempt occurs. 29 | executor.execute(this::readEvents); 30 | } 31 | 32 | private void readEvents() { 33 | try { 34 | iterator.forEachRemaining(event -> { 35 | try { 36 | eventQueue.put(event); 37 | } catch (InterruptedException e) { 38 | iterator.close(); 39 | Thread.currentThread().interrupt(); 40 | } 41 | }); 42 | } catch (GatewayRuntimeException e) { 43 | if (e.getStatus().getCode() != Status.Code.CANCELLED) { 44 | throw e; 45 | } 46 | } 47 | } 48 | 49 | public T next() throws InterruptedException { 50 | T event = eventQueue.poll(30, TimeUnit.SECONDS); 51 | Objects.requireNonNull(event, "timeout waiting for event"); 52 | return event; 53 | } 54 | 55 | public void close() { 56 | executor.shutdownNow(); 57 | iterator.close(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /java/src/test/java/scenario/CheckpointEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package scenario; 8 | 9 | import java.io.IOException; 10 | import org.hyperledger.fabric.client.CloseableIterator; 11 | 12 | public final class CheckpointEventListener implements EventListener { 13 | private final EventListener eventListener; 14 | private final CheckpointCall checkpoint; 15 | 16 | @FunctionalInterface 17 | public interface CheckpointCall { 18 | void accept(T event) throws IOException; 19 | } 20 | 21 | CheckpointEventListener(final CloseableIterator iterator, final CheckpointCall checkpoint) { 22 | eventListener = new BasicEventListener<>(iterator); 23 | this.checkpoint = checkpoint; 24 | } 25 | 26 | @Override 27 | public T next() throws InterruptedException, IOException { 28 | T event = eventListener.next(); 29 | checkpoint.accept(event); 30 | return event; 31 | } 32 | 33 | @Override 34 | public void close() { 35 | eventListener.close(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java/src/test/java/scenario/EventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package scenario; 8 | 9 | import java.io.IOException; 10 | 11 | public interface EventListener { 12 | T next() throws InterruptedException, IOException; 13 | 14 | void close(); 15 | } 16 | -------------------------------------------------------------------------------- /java/src/test/java/scenario/RunScenarioTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package scenario; 8 | 9 | import static io.cucumber.junit.platform.engine.Constants.FILTER_TAGS_PROPERTY_NAME; 10 | import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; 11 | import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; 12 | 13 | import io.cucumber.java.AfterAll; 14 | import io.cucumber.java.BeforeAll; 15 | import org.junit.platform.suite.api.ConfigurationParameter; 16 | import org.junit.platform.suite.api.IncludeEngines; 17 | import org.junit.platform.suite.api.SelectDirectories; 18 | import org.junit.platform.suite.api.Suite; 19 | 20 | @Suite 21 | @IncludeEngines("cucumber") 22 | @SelectDirectories("../scenario/features") 23 | @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") 24 | @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "scenario") 25 | @ConfigurationParameter(key = FILTER_TAGS_PROPERTY_NAME, value = "not @hsm") 26 | public class RunScenarioTest { 27 | @BeforeAll 28 | public static void startFabric() throws Exception { 29 | System.err.println("Starting Fabric"); 30 | ScenarioSteps.startFabric(); 31 | } 32 | 33 | @AfterAll 34 | public static void stopFabric() throws Exception { 35 | ScenarioSteps.stopFabric(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.displayname.generator.default = org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores 2 | cucumber.publish.quiet=true 3 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Fabric Gateway 2 | site_description: Go, Node and Java client API for Hyperledger Fabric 3 | repo_url: https://github.com/hyperledger/fabric-gateway/ 4 | theme: 5 | name: material 6 | logo: assets/Hyperledger_Fabric_White.svg 7 | favicon: assets/Hyperledger_Fabric_Icon.svg 8 | palette: 9 | # Palette toggle for automatic mode 10 | - media: "(prefers-color-scheme)" 11 | toggle: 12 | icon: material/brightness-auto 13 | name: Switch to light mode 14 | # Palette toggle for light mode 15 | - media: "(prefers-color-scheme: light)" 16 | scheme: default 17 | toggle: 18 | icon: material/brightness-7 19 | name: Switch to dark mode 20 | # Palette toggle for dark mode 21 | - media: "(prefers-color-scheme: dark)" 22 | scheme: slate 23 | toggle: 24 | icon: material/brightness-4 25 | name: Switch to system preference 26 | extra: 27 | social: 28 | - icon: octicons/comment-discussion-16 29 | link: https://github.com/hyperledger/fabric-gateway/discussions 30 | name: GitHub discussions 31 | - icon: fontawesome/brands/discord 32 | link: https://discord.gg/hyperledger 33 | name: Hyperledger Discord 34 | - icon: fontawesome/regular/envelope 35 | link: https://lists.lfdecentralizedtrust.org/g/fabric 36 | name: Fabric mailing list 37 | nav: 38 | - index.md 39 | - migration.md 40 | - compatibility.md 41 | - community.md 42 | -------------------------------------------------------------------------------- /node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | *.tgz 5 | apidocs/ 6 | *.cdx.json 7 | -------------------------------------------------------------------------------- /node/.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude everything except specific inclusions below 2 | **/* 3 | 4 | !dist/**/* 5 | -------------------------------------------------------------------------------- /node/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /node/README.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Fabric Gateway Client API for Node 2 | 3 | 4 | The Fabric Gateway client API allows applications to interact with a Hyperledger Fabric blockchain network. It implements the Fabric programming model, providing a simple API to submit transactions to a ledger or query the contents of a ledger with minimal code. 5 | 6 | ## How to use 7 | 8 | Samples showing how to create client applications that connect to and interact with a Hyperledger Fabric network, are available in the [fabric-samples](https://github.com/hyperledger/fabric-samples) repository: 9 | 10 | - [asset-transfer-basic](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic) for examples of transaction submit and evaluate. 11 | - [asset-transfer-events](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-events) for examples of chaincode eventing. 12 | - [off_chain_data](https://github.com/hyperledger/fabric-samples/tree/main/off_chain_data) for examples of block eventing. 13 | 14 | ## API documentation 15 | 16 | The Gateway client API documentation for Node is available here: 17 | 18 | - https://hyperledger.github.io/fabric-gateway/main/api/node/ 19 | 20 | ## Installation 21 | 22 | Add a dependency to your project's `package.json` file with the command: 23 | 24 | ```sh 25 | npm install @hyperledger/fabric-gateway 26 | ``` 27 | 28 | ## Compatibility 29 | 30 | This API requires Fabric v2.4 (or later) with a Gateway enabled Peer. Additional compatibility information is available in the documentation: 31 | 32 | - https://hyperledger.github.io/fabric-gateway/ 33 | -------------------------------------------------------------------------------- /node/eslint.config.base.mjs: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import prettier from 'eslint-config-prettier'; 3 | import tseslint from 'typescript-eslint'; 4 | 5 | export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, prettier, { 6 | languageOptions: { 7 | ecmaVersion: 2023, 8 | sourceType: 'module', 9 | parserOptions: { 10 | project: 'tsconfig.json', 11 | tsconfigRootDir: import.meta.dirname, 12 | }, 13 | }, 14 | rules: { 15 | complexity: ['error', 10], 16 | '@typescript-eslint/explicit-function-return-type': [ 17 | 'error', 18 | { 19 | allowExpressions: true, 20 | }, 21 | ], 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /node/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import jest from 'eslint-plugin-jest'; 2 | import tseslint from 'typescript-eslint'; 3 | import base from './eslint.config.base.mjs'; 4 | import { FlatCompat } from '@eslint/eslintrc'; 5 | 6 | const compat = new FlatCompat({ baseDirectory: import.meta.dirname }); 7 | 8 | export default tseslint.config(...base, jest.configs['flat/recommended'], ...compat.plugins('eslint-plugin-tsdoc'), { 9 | rules: { 10 | 'tsdoc/syntax': ['error'], 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /node/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | collectCoverage: true, 6 | collectCoverageFrom: ['**/*.[jt]s?(x)', '!**/*.d.ts'], 7 | coverageProvider: 'v8', 8 | testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], 9 | verbose: true, 10 | workerThreads: true, 11 | }; 12 | -------------------------------------------------------------------------------- /node/src/chaincodeevent.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { gateway, peer } from '@hyperledger/fabric-protos'; 8 | import { CloseableAsyncIterable } from './client'; 9 | 10 | /** 11 | * Chaincode event emitted by a transaction function. 12 | */ 13 | export interface ChaincodeEvent { 14 | /** 15 | * Block number that included this chaincode event. 16 | */ 17 | blockNumber: bigint; 18 | 19 | /** 20 | * Transaction that emitted this chaincode event. 21 | */ 22 | transactionId: string; 23 | 24 | /** 25 | * Chaincode that emitted this event. 26 | */ 27 | chaincodeName: string; 28 | 29 | /** 30 | * Name of the emitted event. 31 | */ 32 | eventName: string; 33 | 34 | /** 35 | * Application defined payload data associated with this event. 36 | */ 37 | payload: Uint8Array; 38 | } 39 | 40 | // @ts-expect-error Polyfill for Symbol.dispose if not present 41 | Symbol.dispose ??= Symbol('Symbol.dispose'); 42 | 43 | export function newChaincodeEvents( 44 | responses: CloseableAsyncIterable, 45 | ): CloseableAsyncIterable { 46 | return { 47 | [Symbol.asyncIterator]: async function* () { 48 | for await (const response of responses) { 49 | const blockNumber = BigInt(response.getBlockNumber()); 50 | const events = response.getEventsList(); 51 | for (const event of events) { 52 | yield newChaincodeEvent(blockNumber, event); 53 | } 54 | } 55 | }, 56 | close: () => { 57 | responses.close(); 58 | }, 59 | [Symbol.dispose]: () => { 60 | responses.close(); 61 | }, 62 | }; 63 | } 64 | 65 | function newChaincodeEvent(blockNumber: bigint, event: peer.ChaincodeEvent): ChaincodeEvent { 66 | return { 67 | blockNumber, 68 | chaincodeName: event.getChaincodeId(), 69 | eventName: event.getEventName(), 70 | transactionId: event.getTxId(), 71 | payload: event.getPayload_asU8(), 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /node/src/checkpointer.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { ChaincodeEvent } from './chaincodeevent'; 8 | 9 | /** 10 | * Used to get the checkpointed state. 11 | */ 12 | export interface Checkpoint { 13 | /** 14 | * Get the checkpointed block number, or undefined if there is no previously saved state. 15 | */ 16 | getBlockNumber(): bigint | undefined; 17 | 18 | /** 19 | * Get the last processed transaction ID within the current block. 20 | */ 21 | getTransactionId(): string | undefined; 22 | } 23 | 24 | export interface Checkpointer extends Checkpoint { 25 | /** 26 | * To checkpoint block. 27 | */ 28 | checkpointBlock(blockNumber: bigint): Promise; 29 | 30 | /** 31 | *To checkpoint transaction within the current block. 32 | */ 33 | checkpointTransaction(blockNumber: bigint, transactionId: string): Promise; 34 | 35 | /** 36 | * To checkpoint chaincode event. 37 | */ 38 | checkpointChaincodeEvent(event: ChaincodeEvent): Promise; 39 | } 40 | -------------------------------------------------------------------------------- /node/src/checkpointers.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { Checkpointer } from './checkpointer'; 8 | import { FileCheckPointer } from './filecheckpointer'; 9 | import { InMemoryCheckPointer } from './inmemorycheckpointer'; 10 | 11 | /** 12 | * Create a checkpointer that uses the specified file to store persistent state. 13 | * @param path - Path to a file holding persistent checkpoint state. 14 | */ 15 | export async function file(path: string): Promise { 16 | const filecheckpointer = new FileCheckPointer(path); 17 | await filecheckpointer.init(); 18 | return filecheckpointer; 19 | } 20 | 21 | /** 22 | * Create a checkpointer that stores its state in memory only. 23 | */ 24 | export function inMemory(): Checkpointer { 25 | return new InMemoryCheckPointer(); 26 | } 27 | -------------------------------------------------------------------------------- /node/src/commiterror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { peer } from '@hyperledger/fabric-protos'; 8 | import { Status, StatusNames } from './status'; 9 | 10 | /** 11 | * CommitError is thrown to indicate that a transaction committed with an unsuccessful status code. 12 | */ 13 | export class CommitError extends Error { 14 | /** 15 | * Transaction validation status code. The value corresponds to one of the values enumerated by {@link StatusCode}. 16 | */ 17 | code: peer.TxValidationCodeMap[keyof peer.TxValidationCodeMap]; 18 | 19 | /** 20 | * The ID of the transaction. 21 | */ 22 | transactionId: string; 23 | 24 | constructor( 25 | properties: Readonly<{ 26 | code: peer.TxValidationCodeMap[keyof peer.TxValidationCodeMap]; 27 | transactionId: string; 28 | message?: string; 29 | }>, 30 | ) { 31 | super(properties.message); 32 | 33 | this.name = CommitError.name; 34 | this.code = properties.code; 35 | this.transactionId = properties.transactionId; 36 | } 37 | } 38 | 39 | export function newCommitError(status: Status): CommitError { 40 | return new CommitError({ 41 | message: `Transaction ${status.transactionId} failed to commit with status code ${String(status.code)} (${StatusNames[status.code]})`, 42 | code: status.code, 43 | transactionId: status.transactionId, 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /node/src/commitstatuserror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { ServiceError } from '@grpc/grpc-js'; 8 | import { ErrorDetail, GatewayError } from './gatewayerror'; 9 | 10 | /** 11 | * CommitStatusError is thrown when a failure occurs obtaining the commit status of a transaction. 12 | */ 13 | export class CommitStatusError extends GatewayError { 14 | /** 15 | * The ID of the transaction. 16 | */ 17 | transactionId: string; 18 | 19 | constructor( 20 | properties: Readonly<{ 21 | code: number; 22 | details: ErrorDetail[]; 23 | cause: ServiceError; 24 | transactionId: string; 25 | message?: string; 26 | }>, 27 | ) { 28 | super(properties); 29 | 30 | this.name = CommitStatusError.name; 31 | this.transactionId = properties.transactionId; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /node/src/dependency.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { generateKeyPairSync } from 'node:crypto'; 8 | import { dirname, sep as pathSeparator } from 'node:path'; 9 | import type { signers as SignersType } from '.'; 10 | 11 | function isLoaded(moduleName: string): boolean { 12 | const moduleFile = require.resolve(moduleName); 13 | const moduleDir = dirname(moduleFile) + pathSeparator; 14 | return !!Object.values(require.cache).find((m) => m?.filename.startsWith(moduleDir)); 15 | } 16 | 17 | describe('optional pkcs11js dependency', () => { 18 | it('not loaded when accessing private key signer', () => { 19 | jest.resetModules(); 20 | expect(isLoaded('pkcs11js')).toBe(false); 21 | 22 | const { privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' }); 23 | // eslint-disable-next-line @typescript-eslint/no-require-imports 24 | const { signers } = require('.') as { signers: typeof SignersType }; 25 | signers.newPrivateKeySigner(privateKey); 26 | 27 | expect(isLoaded('pkcs11js')).toBe(false); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /node/src/endorseerror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { ServiceError } from '@grpc/grpc-js'; 8 | import { ErrorDetail, GatewayError } from './gatewayerror'; 9 | 10 | /** 11 | * EndorseError is thrown when a failure occurs endorsing a transaction proposal. 12 | */ 13 | export class EndorseError extends GatewayError { 14 | /** 15 | * The ID of the transaction. 16 | */ 17 | transactionId: string; 18 | 19 | constructor( 20 | properties: Readonly<{ 21 | code: number; 22 | details: ErrorDetail[]; 23 | cause: ServiceError; 24 | transactionId: string; 25 | message?: string; 26 | }>, 27 | ) { 28 | super(properties); 29 | 30 | this.name = EndorseError.name; 31 | this.transactionId = properties.transactionId; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /node/src/eventsbuilder.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { orderer } from '@hyperledger/fabric-protos'; 8 | import { Checkpoint } from './checkpointer'; 9 | 10 | /** 11 | * Options used when requesting events. 12 | * 13 | * If both a start block and checkpoint are specified, and the checkpoint has a valid position set, the checkpoint 14 | * position is used and the specified start block is ignored. If the checkpoint is unset then the start block is used. 15 | * 16 | * If no start position is specified, eventing begins from the next committed block. 17 | */ 18 | export interface EventsOptions { 19 | /** 20 | * Block number at which to start reading events. 21 | */ 22 | startBlock?: bigint; 23 | 24 | /** 25 | * Used to get checkpointed state. 26 | */ 27 | checkpoint?: Checkpoint; 28 | } 29 | 30 | export class EventsBuilder { 31 | readonly #options: Readonly; 32 | 33 | constructor(options: Readonly) { 34 | this.#options = options; 35 | } 36 | 37 | getStartPosition(): orderer.SeekPosition { 38 | const result = new orderer.SeekPosition(); 39 | const startBlock = this.#options.checkpoint?.getBlockNumber() ?? this.#options.startBlock; 40 | 41 | if (startBlock != undefined) { 42 | const specified = new orderer.SeekSpecified(); 43 | 44 | specified.setNumber(Number(startBlock)); 45 | result.setSpecified(specified); 46 | 47 | return result; 48 | } 49 | 50 | result.setNextCommit(new orderer.SeekNextCommit()); 51 | return result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /node/src/hash/hash.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * A hashing implementation used to generate a digest from a supplied message. 9 | */ 10 | export type Hash = (message: Uint8Array) => Uint8Array; 11 | -------------------------------------------------------------------------------- /node/src/hash/hashes.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import * as hashes from './hashes'; 8 | 9 | describe('hashes', () => { 10 | Object.entries(hashes).forEach(([name, hash]) => { 11 | // eslint-disable-next-line jest/valid-title 12 | describe(name, () => { 13 | it('Hashes of identical data are identical', () => { 14 | const message = Buffer.from('foobar'); 15 | 16 | const hash1 = hash(message); 17 | const hash2 = hash(message); 18 | 19 | expect(hash1).toEqual(hash2); 20 | }); 21 | 22 | it('Hashes of different data are not identical', () => { 23 | const foo = Buffer.from('foo'); 24 | const bar = Buffer.from('bar'); 25 | 26 | const fooHash = hash(foo); 27 | const barHash = hash(bar); 28 | 29 | expect(fooHash).not.toEqual(barHash); 30 | }); 31 | }); 32 | }); 33 | 34 | it('none returns input', () => { 35 | const message = Buffer.from('foobar'); 36 | const hash = hashes.none(message); 37 | expect(hash).toEqual(message); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /node/src/hash/hashes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { createHash } from 'node:crypto'; 8 | import { Hash } from './hash'; 9 | 10 | /** 11 | * Returns the input message unchanged. This can be used if the signing implementation requires the full message bytes, 12 | * not just a pre-generated digest, such as Ed25519. 13 | */ 14 | export const none: Hash = (message) => message; 15 | 16 | /** 17 | * SHA256 hash the supplied message bytes to create a digest for signing. 18 | */ 19 | export const sha256: Hash = (message) => digest('sha256', message); 20 | 21 | /** 22 | * SHA384 hash the supplied message bytes to create a digest for signing. 23 | */ 24 | export const sha384: Hash = (message) => digest('sha384', message); 25 | 26 | /** 27 | * SHA3-256 hash the supplied message bytes to create a digest for signing. 28 | */ 29 | export const sha3_256: Hash = (message) => digest('sha3-256', message); 30 | 31 | /** 32 | * SHA3-384 hash the supplied message bytes to create a digest for signing. 33 | */ 34 | export const sha3_384: Hash = (message) => digest('sha3-384', message); 35 | 36 | function digest(algorithm: string, message: Uint8Array): Uint8Array { 37 | return createHash(algorithm).update(message).digest(); 38 | } 39 | -------------------------------------------------------------------------------- /node/src/identity/ecdsa.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { CurveFn } from '@noble/curves/abstract/weierstrass'; 8 | import { p256 } from '@noble/curves/p256'; 9 | import { p384 } from '@noble/curves/p384'; 10 | import { KeyObject } from 'node:crypto'; 11 | import { Signer } from './signer'; 12 | 13 | const namedCurves: Record = { 14 | 'P-256': p256, 15 | 'P-384': p384, 16 | }; 17 | 18 | export function newECPrivateKeySigner(key: KeyObject): Signer { 19 | const { crv, d } = key.export({ format: 'jwk' }); 20 | if (!crv) { 21 | throw new Error('Missing EC curve name'); 22 | } 23 | if (!d) { 24 | throw new Error('Missing EC private key value'); 25 | } 26 | 27 | const curve = getCurve(crv); 28 | const privateKey = Buffer.from(d, 'base64url'); 29 | 30 | return (digest) => { 31 | const signature = curve.sign(digest, privateKey, { lowS: true }); 32 | return Promise.resolve(signature.toDERRawBytes()); 33 | }; 34 | } 35 | 36 | function getCurve(name: string): CurveFn { 37 | const curve = namedCurves[name]; 38 | if (!curve) { 39 | throw new Error(`Unsupported curve: ${name}`); 40 | } 41 | 42 | return curve; 43 | } 44 | -------------------------------------------------------------------------------- /node/src/identity/identity.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * Represents a client identity used to interact with a Fabric network. The identity consists of an identifier for the 9 | * organization to which the identity belongs, and implementation-specific credentials describing the identity. 10 | */ 11 | export interface Identity { 12 | /** 13 | * Member services provider to which this identity is associated. 14 | */ 15 | mspId: string; 16 | 17 | /** 18 | * Implementation-specific credentials. For an identity described by a X.509 certificate, the credentials are the 19 | * PEM-encoded certificate. 20 | */ 21 | credentials: Uint8Array; 22 | } 23 | -------------------------------------------------------------------------------- /node/src/identity/signer.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * A signing implementation used to generate digital signatures from a supplied message digest. Standard 9 | * implementations can be obtained using {@link signers} factory methods. 10 | */ 11 | export type Signer = (digest: Uint8Array) => Promise; 12 | -------------------------------------------------------------------------------- /node/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | export { type BlockEventsOptions } from './blockeventsbuilder'; 8 | export { 9 | type BlockAndPrivateDataEventsRequest, 10 | type BlockEventsRequest, 11 | type FilteredBlockEventsRequest, 12 | } from './blockeventsrequest'; 13 | export { type ChaincodeEvent } from './chaincodeevent'; 14 | export { type ChaincodeEventsOptions } from './chaincodeeventsbuilder'; 15 | export { type ChaincodeEventsRequest } from './chaincodeeventsrequest'; 16 | export { type Checkpoint, type Checkpointer } from './checkpointer'; 17 | export * as checkpointers from './checkpointers'; 18 | export { type CloseableAsyncIterable } from './client'; 19 | export { type Commit } from './commit'; 20 | export { CommitError } from './commiterror'; 21 | export { CommitStatusError } from './commitstatuserror'; 22 | export { type Contract } from './contract'; 23 | export { EndorseError } from './endorseerror'; 24 | export { type EventsOptions } from './eventsbuilder'; 25 | export { type ConnectOptions, type Gateway, type GrpcClient, connect } from './gateway'; 26 | export { type ErrorDetail, GatewayError } from './gatewayerror'; 27 | export { type Hash } from './hash/hash'; 28 | export * as hash from './hash/hashes'; 29 | export { type HSMSigner, type HSMSignerFactory, type HSMSignerOptions } from './identity/hsmsigner'; 30 | export { type Identity } from './identity/identity'; 31 | export { type Signer } from './identity/signer'; 32 | export * as signers from './identity/signers'; 33 | export { type Network } from './network'; 34 | export { type Proposal } from './proposal'; 35 | export { type ProposalOptions } from './proposalbuilder'; 36 | export { type Signable } from './signable'; 37 | export { type Status, StatusCode } from './status'; 38 | export { type SubmitError } from './submiterror'; 39 | export { type SubmittedTransaction } from './submittedtransaction'; 40 | export { type Transaction } from './transaction'; 41 | -------------------------------------------------------------------------------- /node/src/inmemorycheckpointer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { Checkpointer } from './checkpointer'; 8 | import { ChaincodeEvent } from './chaincodeevent'; 9 | 10 | /** 11 | * In-memory checkpointer class used to persist checkpointer state in memory. 12 | */ 13 | 14 | export class InMemoryCheckPointer implements Checkpointer { 15 | #blockNumber?: bigint; 16 | #transactionId?: string; 17 | 18 | checkpointBlock(blockNumber: bigint): Promise { 19 | this.#blockNumber = blockNumber + BigInt(1); 20 | this.#transactionId = undefined; 21 | return Promise.resolve(); 22 | } 23 | 24 | checkpointTransaction(blockNumber: bigint, transactionId: string): Promise { 25 | this.#blockNumber = blockNumber; 26 | this.#transactionId = transactionId; 27 | return Promise.resolve(); 28 | } 29 | 30 | async checkpointChaincodeEvent(event: ChaincodeEvent): Promise { 31 | await this.checkpointTransaction(event.blockNumber, event.transactionId); 32 | } 33 | 34 | getBlockNumber(): bigint | undefined { 35 | return this.#blockNumber; 36 | } 37 | 38 | getTransactionId(): string | undefined { 39 | return this.#transactionId; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /node/src/network.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import * as grpc from '@grpc/grpc-js'; 8 | import { connect, ConnectOptions } from './gateway'; 9 | import { Identity } from './identity/identity'; 10 | import { Network } from './network'; 11 | 12 | describe('Network', () => { 13 | let network: Network; 14 | let client: grpc.Client; 15 | 16 | beforeEach(() => { 17 | const identity: Identity = { 18 | mspId: 'MSP_ID', 19 | credentials: Buffer.from('CERTIFICATE'), 20 | }; 21 | client = new grpc.Client('example.org:1337', grpc.credentials.createInsecure()); 22 | const options: ConnectOptions = { 23 | identity, 24 | client, 25 | }; 26 | 27 | const gateway = connect(options); 28 | network = gateway.getNetwork('CHANNEL_NAME'); 29 | }); 30 | 31 | describe('getContract', () => { 32 | it('returns correctly named default contract', () => { 33 | const contract = network.getContract('CHAINCODE_NAME'); 34 | 35 | expect(contract.getChaincodeName()).toBe('CHAINCODE_NAME'); 36 | expect(contract.getContractName()).toBeUndefined(); 37 | }); 38 | 39 | it('returns correctly named non-default contract', () => { 40 | const contract = network.getContract('CHAINCODE_NAME', 'CONTRACT_NAME'); 41 | 42 | expect(contract.getChaincodeName()).toBe('CHAINCODE_NAME'); 43 | expect(contract.getContractName()).toBe('CONTRACT_NAME'); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /node/src/signable.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * A call that can be explicitly signed. Supports off-line signing flow. 9 | */ 10 | export interface Signable { 11 | /** 12 | * Get the serialized bytes of the signable object. 13 | * Serialized bytes can be used to recreate the object using methods on {@link Gateway}. 14 | */ 15 | getBytes(): Uint8Array; 16 | 17 | /** 18 | * Get the digest of the signable object. This is used to generate a digital signature. 19 | */ 20 | getDigest(): Uint8Array; 21 | } 22 | -------------------------------------------------------------------------------- /node/src/signingidentity.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { msp } from '@hyperledger/fabric-protos'; 8 | import { ConnectOptions } from './gateway'; 9 | import { Hash } from './hash/hash'; 10 | import { sha256 } from './hash/hashes'; 11 | import { Identity } from './identity/identity'; 12 | import { Signer } from './identity/signer'; 13 | 14 | export const undefinedSignerMessage = 'No signing implementation'; 15 | 16 | const undefinedSigner: Signer = () => { 17 | return Promise.reject(new Error(undefinedSignerMessage)); 18 | }; 19 | 20 | type SigningIdentityOptions = Pick; 21 | 22 | export class SigningIdentity { 23 | readonly #identity: Identity; 24 | readonly #creator: Uint8Array; 25 | readonly #hash: Hash; 26 | readonly #sign: Signer; 27 | 28 | constructor(options: Readonly) { 29 | this.#identity = { 30 | mspId: options.identity.mspId, 31 | credentials: new Uint8Array(options.identity.credentials), 32 | }; 33 | 34 | const serializedIdentity = new msp.SerializedIdentity(); 35 | serializedIdentity.setMspid(options.identity.mspId); 36 | serializedIdentity.setIdBytes(options.identity.credentials); 37 | this.#creator = serializedIdentity.serializeBinary(); 38 | 39 | this.#hash = options.hash ?? sha256; 40 | this.#sign = options.signer ?? undefinedSigner; 41 | } 42 | 43 | getIdentity(): Identity { 44 | return { 45 | mspId: this.#identity.mspId, 46 | credentials: new Uint8Array(this.#identity.credentials), 47 | }; 48 | } 49 | 50 | getCreator(): Uint8Array { 51 | return new Uint8Array(this.#creator); 52 | } 53 | 54 | hash(message: Uint8Array): Uint8Array { 55 | return this.#hash(message); 56 | } 57 | 58 | async sign(digest: Uint8Array): Promise { 59 | return this.#sign(digest); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /node/src/status.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { peer } from '@hyperledger/fabric-protos'; 8 | 9 | /** 10 | * Enumeration of transaction status codes. 11 | */ 12 | export const StatusCode = Object.freeze(peer.TxValidationCode) as { 13 | [P in keyof typeof peer.TxValidationCode]: (typeof peer.TxValidationCode)[P]; 14 | }; 15 | 16 | export const StatusNames = Object.freeze( 17 | Object.fromEntries( 18 | Object.entries(StatusCode) 19 | .filter(([_, code]) => typeof code === 'number') // eslint-disable-line @typescript-eslint/no-unused-vars 20 | .map(([name, code]) => [code, name]), 21 | ) as { [P in keyof typeof StatusCode as (typeof StatusCode)[P]]: P }, 22 | ); 23 | 24 | /** 25 | * Status of a transaction that is committed to the ledger. 26 | */ 27 | export interface Status { 28 | /** 29 | * Block number in which the transaction committed. 30 | */ 31 | blockNumber: bigint; 32 | 33 | /** 34 | * Transaction validation status code. The value corresponds to one of the values enumerated by {@link StatusCode}. 35 | */ 36 | code: peer.TxValidationCodeMap[keyof peer.TxValidationCodeMap]; 37 | 38 | /** 39 | * `true` if the transaction committed successfully; otherwise `false`. 40 | */ 41 | successful: boolean; 42 | 43 | /** 44 | * The ID of the transaction. 45 | */ 46 | transactionId: string; 47 | } 48 | -------------------------------------------------------------------------------- /node/src/submiterror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { ServiceError } from '@grpc/grpc-js'; 8 | import { ErrorDetail, GatewayError } from './gatewayerror'; 9 | 10 | /** 11 | * SubmitError is thrown when a failure occurs submitting an endorsed transaction to the orderer. 12 | */ 13 | export class SubmitError extends GatewayError { 14 | /** 15 | * The ID of the transaction. 16 | */ 17 | transactionId: string; 18 | 19 | constructor( 20 | properties: Readonly<{ 21 | code: number; 22 | details: ErrorDetail[]; 23 | cause: ServiceError; 24 | transactionId: string; 25 | message?: string; 26 | }>, 27 | ) { 28 | super(properties); 29 | 30 | this.name = SubmitError.name; 31 | this.transactionId = properties.transactionId; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /node/src/submittedtransaction.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { Commit, CommitImpl, CommitImplOptions } from './commit'; 8 | 9 | /** 10 | * Allows access to the transaction result and its commit status on the ledger. 11 | */ 12 | export interface SubmittedTransaction extends Commit { 13 | /** 14 | * Get the transaction result. This is obtained during the endorsement process when the transaction proposal is 15 | * run on endorsing peers and so is available immediately. The transaction might subsequently fail to commit 16 | * successfully. 17 | */ 18 | getResult(): Uint8Array; 19 | } 20 | 21 | export interface SubmittedTransactionImplOptions extends CommitImplOptions { 22 | result: Uint8Array; 23 | } 24 | 25 | export class SubmittedTransactionImpl extends CommitImpl { 26 | #result: Uint8Array; 27 | 28 | constructor(options: Readonly) { 29 | super(options); 30 | this.#result = options.result; 31 | } 32 | 33 | getResult(): Uint8Array { 34 | return this.#result; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /node/src/transactioncontext.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { common } from '@hyperledger/fabric-protos'; 8 | import { randomBytes } from 'node:crypto'; 9 | import { sha256 } from './hash/hashes'; 10 | import { SigningIdentity } from './signingidentity'; 11 | 12 | export class TransactionContext { 13 | readonly #transactionId: string; 14 | readonly #signatureHeader: common.SignatureHeader; 15 | 16 | constructor(signingIdentity: SigningIdentity) { 17 | const nonce = randomBytes(24); 18 | const creator = signingIdentity.getCreator(); 19 | 20 | const saltedCreator = Buffer.concat([nonce, creator]); 21 | const rawTransactionId = sha256(saltedCreator); 22 | this.#transactionId = Buffer.from(rawTransactionId).toString('hex'); 23 | 24 | this.#signatureHeader = new common.SignatureHeader(); 25 | this.#signatureHeader.setCreator(creator); 26 | this.#signatureHeader.setNonce(nonce); 27 | } 28 | 29 | getTransactionId(): string { 30 | return this.#transactionId; 31 | } 32 | 33 | getSignatureHeader(): common.SignatureHeader { 34 | return this.#signatureHeader; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /node/src/transactionparser.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { common, peer } from '@hyperledger/fabric-protos'; 8 | import { inspect } from 'node:util'; 9 | import { assertDefined } from './gateway'; 10 | 11 | export function parseTransactionEnvelope(envelope: common.Envelope): { 12 | channelName: string; 13 | result: Uint8Array; 14 | } { 15 | const payload = common.Payload.deserializeBinary(envelope.getPayload_asU8()); 16 | const header = assertDefined(payload.getHeader(), 'Missing header'); 17 | 18 | return { 19 | channelName: parseChannelNameFromHeader(header), 20 | result: parseResultFromPayload(payload), 21 | }; 22 | } 23 | 24 | function parseChannelNameFromHeader(header: common.Header): string { 25 | const channelHeader = common.ChannelHeader.deserializeBinary(header.getChannelHeader_asU8()); 26 | return channelHeader.getChannelId(); 27 | } 28 | 29 | function parseResultFromPayload(payload: common.Payload): Uint8Array { 30 | const transaction = peer.Transaction.deserializeBinary(payload.getData_asU8()); 31 | 32 | const errors: unknown[] = []; 33 | 34 | for (const transactionAction of transaction.getActionsList()) { 35 | try { 36 | return parseResultFromTransactionAction(transactionAction); 37 | } catch (err) { 38 | errors.push(err); 39 | } 40 | } 41 | 42 | throw Object.assign(new Error(`No proposal response found: ${inspect(errors)}`), { 43 | suppressed: errors, 44 | }); 45 | } 46 | 47 | function parseResultFromTransactionAction(transactionAction: peer.TransactionAction): Uint8Array { 48 | const actionPayload = peer.ChaincodeActionPayload.deserializeBinary(transactionAction.getPayload_asU8()); 49 | const endorsedAction = assertDefined(actionPayload.getAction(), 'Missing endorsed action'); 50 | const responsePayload = peer.ProposalResponsePayload.deserializeBinary( 51 | endorsedAction.getProposalResponsePayload_asU8(), 52 | ); 53 | const chaincodeAction = peer.ChaincodeAction.deserializeBinary(responsePayload.getExtension_asU8()); 54 | const chaincodeResponse = assertDefined(chaincodeAction.getResponse(), 'Missing chaincode response'); 55 | return chaincodeResponse.getPayload_asU8(); 56 | } 57 | -------------------------------------------------------------------------------- /node/test/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGDCCAb+gAwIBAgIQSabzkXOpBfe51cMWawS1VzAKBggqhkjOPQQDAjBzMQsw 3 | CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy 4 | YW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu 5 | b3JnMS5leGFtcGxlLmNvbTAeFw0yMDExMDIxNTA4MDBaFw0zMDEwMzExNTA4MDBa 6 | MFsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T 7 | YW4gRnJhbmNpc2NvMR8wHQYDVQQDDBZVc2VyMUBvcmcxLmV4YW1wbGUuY29tMFkw 8 | EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMCWZFcZ77bX+j9mLgGqODcQ+jFLutayX 9 | tMKhb5F08xtP/Tbs3GMyBGsy21a2JFDKPq29adrla8i3emxzd8VpmaNNMEswDgYD 10 | VR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwKwYDVR0jBCQwIoAg+Swjy+a4gUpo 11 | B7dg2zuF50AcessIGV8+iYHs7acNObcwCgYIKoZIzj0EAwIDRwAwRAIgJxvf+LbL 12 | juOQMYcwsHyIrJHiEXP0JbbkNFTDHjRjeJMCICQvYYrNmvrHZHxD4e1bGZw/keKp 13 | l6Fst12H7Fu9xiVW 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /node/test/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgwi4f7UCEV3q/0oSW 3 | g5V/2xFEnBrmU3DgXhZGdWvmX+OhRANCAAQwJZkVxnvttf6P2YuAao4NxD6MUu61 4 | rJe0wqFvkXTzG0/9NuzcYzIEazLbVrYkUMo+rb1p2uVryLd6bHN3xWmZ 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /node/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "exclude": [ 5 | "**/*.test.*", 6 | "**/*.spec.*" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@tsconfig/node20/tsconfig.json", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "declarationMap": true, 7 | "erasableSyntaxOnly": true, 8 | "isolatedModules": true, 9 | "sourceMap": true, 10 | "outDir": "dist", 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noImplicitReturns": true, 14 | "noUncheckedIndexedAccess": true, 15 | "forceConsistentCasingInFileNames": true 16 | }, 17 | "include": ["src/"] 18 | } 19 | -------------------------------------------------------------------------------- /node/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "cleanOutputDir": true, 3 | "entryPoints": ["src/index.ts"], 4 | "out": "apidocs", 5 | "readme": "src/README.md", 6 | "treatWarningsAsErrors": true 7 | } 8 | -------------------------------------------------------------------------------- /pkg/.gitignore: -------------------------------------------------------------------------------- 1 | mocks_test.go 2 | -------------------------------------------------------------------------------- /pkg/client/README.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Fabric Gateway Client API for Go 2 | 3 | The Fabric Gateway client API allows applications to interact with a Hyperledger Fabric blockchain network. It implements the Fabric programming model, providing a simple API to submit transactions to a ledger or query the contents of a ledger with minimal code. 4 | 5 | ## How to use 6 | 7 | Samples showing how to create client applications that connect to and interact with a Hyperledger Fabric network, are available in the [fabric-samples](https://github.com/hyperledger/fabric-samples) repository: 8 | 9 | - [asset-transfer-basic](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic) for examples of transaction submit and evaluate. 10 | - [asset-transfer-events](https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-events) for examples of chaincode eventing. 11 | - [off_chain_data](https://github.com/hyperledger/fabric-samples/tree/main/off_chain_data) for examples of block eventing. 12 | 13 | ## API documentation 14 | 15 | The Gateway client API documentation for Go is available here: 16 | 17 | - https://pkg.go.dev/github.com/hyperledger/fabric-gateway/pkg/client 18 | 19 | ## Installation 20 | 21 | Add a package dependency to your project with the command: 22 | 23 | ```sh 24 | go get github.com/hyperledger/fabric-gateway 25 | ``` 26 | 27 | ## Compatibility 28 | 29 | This API requires Fabric v2.4 (or later) with a Gateway enabled Peer. Additional compatibility information is available in the documentation: 30 | 31 | - https://hyperledger.github.io/fabric-gateway/ 32 | -------------------------------------------------------------------------------- /pkg/client/chaincodeeventsbuilder.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hyperledger/fabric-protos-go-apiv2/gateway" 10 | "google.golang.org/protobuf/proto" 11 | ) 12 | 13 | type chaincodeEventsBuilder struct { 14 | eventsBuilder 15 | chaincodeName string 16 | } 17 | 18 | func (builder *chaincodeEventsBuilder) build() (*ChaincodeEventsRequest, error) { 19 | signedRequest, err := builder.newSignedChaincodeEventsRequestProto() 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | result := &ChaincodeEventsRequest{ 25 | client: builder.client, 26 | signingID: builder.signingID, 27 | signedRequest: signedRequest, 28 | } 29 | return result, nil 30 | } 31 | 32 | func (builder *chaincodeEventsBuilder) newSignedChaincodeEventsRequestProto() (*gateway.SignedChaincodeEventsRequest, error) { 33 | request, err := builder.newChaincodeEventsRequestProto() 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | requestBytes, err := proto.Marshal(request) 39 | if err != nil { 40 | return nil, fmt.Errorf("failed to serialize chaincode events request: %w", err) 41 | } 42 | 43 | signedRequest := &gateway.SignedChaincodeEventsRequest{ 44 | Request: requestBytes, 45 | } 46 | return signedRequest, nil 47 | } 48 | 49 | func (builder *chaincodeEventsBuilder) newChaincodeEventsRequestProto() (*gateway.ChaincodeEventsRequest, error) { 50 | creator, err := builder.signingID.Creator() 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to serialize identity: %w", err) 53 | } 54 | 55 | request := &gateway.ChaincodeEventsRequest{ 56 | ChannelId: builder.channelName, 57 | Identity: creator, 58 | ChaincodeId: builder.chaincodeName, 59 | StartPosition: builder.getStartPosition(), 60 | AfterTransactionId: builder.afterTransactionID, 61 | } 62 | return request, nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/client/context.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import "context" 7 | 8 | type contextWithCancel func(parent context.Context) (context.Context, context.CancelFunc) 9 | 10 | type contextFactory struct { 11 | ctx context.Context 12 | evaluate contextWithCancel 13 | endorse contextWithCancel 14 | submit contextWithCancel 15 | commitStatus contextWithCancel 16 | } 17 | 18 | func (factory *contextFactory) getOrDefault(supplier contextWithCancel) (context.Context, context.CancelFunc) { 19 | if supplier != nil { 20 | return supplier(factory.ctx) 21 | } 22 | return context.WithCancel(factory.ctx) 23 | } 24 | 25 | func (factory *contextFactory) Evaluate() (context.Context, context.CancelFunc) { 26 | return factory.getOrDefault(factory.evaluate) 27 | } 28 | 29 | func (factory *contextFactory) Endorse() (context.Context, context.CancelFunc) { 30 | return factory.getOrDefault(factory.endorse) 31 | } 32 | 33 | func (factory *contextFactory) Submit() (context.Context, context.CancelFunc) { 34 | return factory.getOrDefault(factory.submit) 35 | } 36 | 37 | func (factory *contextFactory) CommitStatus() (context.Context, context.CancelFunc) { 38 | return factory.getOrDefault(factory.commitStatus) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/client/contract_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func AssertNewTestContract(t *testing.T, chaincodeName string, options ...ConnectOption) *Contract { 11 | network := AssertNewTestNetwork(t, "network", options...) 12 | return network.GetContract(chaincodeName) 13 | } 14 | 15 | func AssertNewTestContractWithName(t *testing.T, chaincodeName string, contractName string, options ...ConnectOption) *Contract { 16 | network := AssertNewTestNetwork(t, "network", options...) 17 | return network.GetContractWithName(chaincodeName, contractName) 18 | } 19 | 20 | func bytesAsStrings(bytes [][]byte) []string { 21 | results := make([]string, 0, len(bytes)) 22 | 23 | for _, v := range bytes { 24 | results = append(results, string(v)) 25 | } 26 | 27 | return results 28 | } 29 | -------------------------------------------------------------------------------- /pkg/client/credentials_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "github.com/hyperledger/fabric-gateway/pkg/identity" 8 | "github.com/hyperledger/fabric-gateway/pkg/internal/test" 9 | ) 10 | 11 | var TestCredentials *signingIdentity 12 | 13 | func init() { 14 | privateKey, err := test.NewECDSAPrivateKey() 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | certificate, err := test.NewCertificate(privateKey) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | id, err := identity.NewX509Identity("mspID", certificate) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | sign, err := identity.NewPrivateKeySign(privateKey) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | TestCredentials = newSigningIdentity(id) 35 | TestCredentials.sign = sign 36 | } 37 | -------------------------------------------------------------------------------- /pkg/client/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | type grpcError struct { 14 | error 15 | } 16 | 17 | func (e *grpcError) GRPCStatus() *status.Status { 18 | return status.Convert(e.error) 19 | } 20 | 21 | func (e *grpcError) Unwrap() error { 22 | return e.error 23 | } 24 | 25 | func newTransactionError(err error, transactionID string) *TransactionError { 26 | if err == nil { 27 | return nil 28 | } 29 | 30 | return &TransactionError{ 31 | grpcError: &grpcError{err}, 32 | TransactionID: transactionID, 33 | } 34 | } 35 | 36 | // TransactionError represents an error invoking a transaction. This is a gRPC [status] error. 37 | type TransactionError struct { 38 | *grpcError 39 | TransactionID string 40 | } 41 | 42 | // EndorseError represents a failure endorsing a transaction proposal. 43 | type EndorseError struct { 44 | *TransactionError 45 | } 46 | 47 | // SubmitError represents a failure submitting an endorsed transaction to the orderer. 48 | type SubmitError struct { 49 | *TransactionError 50 | } 51 | 52 | // CommitStatusError represents a failure obtaining the commit status of a transaction. 53 | type CommitStatusError struct { 54 | *TransactionError 55 | } 56 | 57 | func newCommitError(transactionID string, code peer.TxValidationCode) error { 58 | return &CommitError{ 59 | message: fmt.Sprintf("transaction %s failed to commit with status code %d (%s)", transactionID, int32(code), peer.TxValidationCode_name[int32(code)]), 60 | TransactionID: transactionID, 61 | Code: code, 62 | } 63 | } 64 | 65 | // CommitError represents a transaction that fails to commit successfully. 66 | type CommitError struct { 67 | message string 68 | TransactionID string 69 | Code peer.TxValidationCode 70 | } 71 | 72 | func (e *CommitError) Error() string { 73 | return e.message 74 | } 75 | -------------------------------------------------------------------------------- /pkg/client/inmemorycheckpointer.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | // InMemoryCheckpointer is a non-persistent [Checkpoint] implementation. It can be used to checkpoint progress after 7 | // successfully processing events, allowing eventing to be resumed from this point. 8 | type InMemoryCheckpointer struct { 9 | blockNumber uint64 10 | transactionID string 11 | } 12 | 13 | // CheckpointBlock records a successfully processed block. 14 | func (c *InMemoryCheckpointer) CheckpointBlock(blockNumber uint64) { 15 | c.CheckpointTransaction(blockNumber+1, "") 16 | } 17 | 18 | // CheckpointTransaction records a successfully processed transaction within a given block. 19 | func (c *InMemoryCheckpointer) CheckpointTransaction(blockNumber uint64, transactionID string) { 20 | c.blockNumber = blockNumber 21 | c.transactionID = transactionID 22 | } 23 | 24 | // CheckpointChaincodeEvent records a successfully processed chaincode event. 25 | func (c *InMemoryCheckpointer) CheckpointChaincodeEvent(event *ChaincodeEvent) { 26 | c.CheckpointTransaction(event.BlockNumber, event.TransactionID) 27 | } 28 | 29 | // BlockNumber in which the next event is expected. 30 | func (c *InMemoryCheckpointer) BlockNumber() uint64 { 31 | return c.blockNumber 32 | } 33 | 34 | // TransactionID of the last successfully processed event within the current block. 35 | func (c *InMemoryCheckpointer) TransactionID() string { 36 | return c.transactionID 37 | } 38 | -------------------------------------------------------------------------------- /pkg/client/network_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func AssertNewTestNetwork(t *testing.T, networkName string, options ...ConnectOption) *Network { 13 | gateway := AssertNewTestGateway(t, options...) 14 | return gateway.GetNetwork(networkName) 15 | } 16 | 17 | func TestNetwork(t *testing.T) { 18 | t.Run("GetContract returns correctly named Contract", func(t *testing.T) { 19 | chaincodeName := "chaincode" 20 | network := AssertNewTestNetwork(t, "network") 21 | 22 | contract := network.GetContract(chaincodeName) 23 | 24 | require.NotNil(t, contract) 25 | require.Equal(t, chaincodeName, contract.ChaincodeName(), "chaincode name") 26 | require.Empty(t, contract.ContractName(), "contract name") 27 | }) 28 | 29 | t.Run("GetContractWithName returns correctly named Contract", func(t *testing.T) { 30 | chaincodeName := "chaincode" 31 | contractName := "contract" 32 | network := AssertNewTestNetwork(t, "network") 33 | 34 | contract := network.GetContractWithName(chaincodeName, contractName) 35 | 36 | require.NotNil(t, contract) 37 | require.Equal(t, chaincodeName, contract.ChaincodeName(), "chaincode name") 38 | require.Equal(t, contractName, contract.ContractName(), "contract name") 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/client/signingidentity.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "errors" 8 | 9 | "github.com/hyperledger/fabric-gateway/pkg/hash" 10 | "github.com/hyperledger/fabric-gateway/pkg/identity" 11 | "github.com/hyperledger/fabric-protos-go-apiv2/msp" 12 | "google.golang.org/protobuf/proto" 13 | ) 14 | 15 | type signingIdentity struct { 16 | id identity.Identity 17 | sign identity.Sign 18 | hash hash.Hash 19 | } 20 | 21 | func newSigningIdentity(id identity.Identity) *signingIdentity { 22 | return &signingIdentity{ 23 | id: id, 24 | sign: func(digest []byte) ([]byte, error) { 25 | return nil, errors.New("no sign implementation supplied") 26 | }, 27 | hash: hash.SHA256, 28 | } 29 | } 30 | 31 | func (signingID *signingIdentity) Identity() identity.Identity { 32 | return signingID.id 33 | } 34 | 35 | func (signingID *signingIdentity) Hash(message []byte) []byte { 36 | return signingID.hash(message) 37 | } 38 | 39 | func (signingID *signingIdentity) Sign(digest []byte) ([]byte, error) { 40 | return signingID.sign(digest) 41 | } 42 | 43 | func (signingID *signingIdentity) Creator() ([]byte, error) { 44 | serializedIdentity := &msp.SerializedIdentity{ 45 | Mspid: signingID.id.MspID(), 46 | IdBytes: signingID.id.Credentials(), 47 | } 48 | return proto.Marshal(serializedIdentity) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/client/transactioncontext.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "crypto/rand" 8 | "encoding/hex" 9 | 10 | "github.com/hyperledger/fabric-gateway/pkg/hash" 11 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 12 | ) 13 | 14 | type transactionContext struct { 15 | TransactionID string 16 | SignatureHeader *common.SignatureHeader 17 | } 18 | 19 | func newTransactionContext(signingIdentity *signingIdentity) (*transactionContext, error) { 20 | nonce := make([]byte, 24) 21 | if _, err := rand.Read(nonce); err != nil { 22 | return nil, err 23 | } 24 | 25 | creator, err := signingIdentity.Creator() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | saltedCreator := append(nonce, creator...) 31 | rawTransactionID := hash.SHA256(saltedCreator) 32 | transactionID := hex.EncodeToString(rawTransactionID) 33 | 34 | signatureHeader := &common.SignatureHeader{ 35 | Creator: creator, 36 | Nonce: nonce, 37 | } 38 | 39 | transactionCtx := &transactionContext{ 40 | TransactionID: transactionID, 41 | SignatureHeader: signatureHeader, 42 | } 43 | return transactionCtx, nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/hash/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package hash provides hash implementations used for digital signature of messages sent to a Fabric network. 5 | package hash 6 | 7 | import ( 8 | "crypto/sha256" 9 | "crypto/sha512" 10 | gohash "hash" 11 | 12 | "golang.org/x/crypto/sha3" 13 | ) 14 | 15 | // Hash function generates a digest for the supplied message. 16 | type Hash = func(message []byte) []byte 17 | 18 | // NONE returns the input message unchanged. This can be used if the signing implementation requires the full message 19 | // bytes, not just a pre-generated digest, such as Ed25519. 20 | func NONE(message []byte) []byte { 21 | return message 22 | } 23 | 24 | // SHA256 hash the supplied message bytes to create a digest for signing. 25 | func SHA256(message []byte) []byte { 26 | return digest(sha256.New(), message) 27 | } 28 | 29 | // SHA384 hash the supplied message bytes to create a digest for signing. 30 | func SHA384(message []byte) []byte { 31 | return digest(sha512.New384(), message) 32 | } 33 | 34 | // SHA3_256 hash the supplied message bytes to create a digest for signing. 35 | func SHA3_256(message []byte) []byte { 36 | return digest(sha3.New256(), message) 37 | } 38 | 39 | // SHA3_384 hash the supplied message bytes to create a digest for signing. 40 | func SHA3_384(message []byte) []byte { 41 | return digest(sha3.New384(), message) 42 | } 43 | 44 | func digest(hasher gohash.Hash, message []byte) []byte { 45 | hasher.Write(message) 46 | return hasher.Sum(nil) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/hash/hash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package hash 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestHash(t *testing.T) { 13 | for testName, testCase := range map[string]struct { 14 | hash Hash 15 | }{ 16 | "NONE": {hash: NONE}, 17 | "SHA256": {hash: SHA256}, 18 | "SHA384": {hash: SHA384}, 19 | "SHA3_256": {hash: SHA3_256}, 20 | "SHA3_384": {hash: SHA3_384}, 21 | } { 22 | t.Run(testName, func(t *testing.T) { 23 | t.Run("Hashes of identical data are identical", func(t *testing.T) { 24 | message := []byte("foobar") 25 | 26 | hash1 := testCase.hash(message) 27 | hash2 := testCase.hash(message) 28 | 29 | require.Equal(t, hash1, hash2) 30 | }) 31 | 32 | t.Run("Hashes of different data are not identical", func(t *testing.T) { 33 | foo := []byte("foo") 34 | bar := []byte("bar") 35 | 36 | fooHash := testCase.hash(foo) 37 | barHash := testCase.hash(bar) 38 | 39 | require.NotEqual(t, fooHash, barHash) 40 | }) 41 | }) 42 | } 43 | 44 | t.Run("NONE returns input", func(t *testing.T) { 45 | message := []byte("foobar") 46 | result := NONE(message) 47 | require.Equal(t, message, result) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/identity/ecdsa.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package identity 5 | 6 | import ( 7 | "crypto/ecdsa" 8 | "crypto/rand" 9 | "encoding/asn1" 10 | "math/big" 11 | ) 12 | 13 | func ecdsaPrivateKeySign(privateKey *ecdsa.PrivateKey) Sign { 14 | n := privateKey.Params().Params().N 15 | 16 | return func(digest []byte) ([]byte, error) { 17 | r, s, err := ecdsa.Sign(rand.Reader, privateKey, digest) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | s = canonicalECDSASignatureSValue(s, n) 23 | 24 | return asn1ECDSASignature(r, s) 25 | } 26 | } 27 | 28 | func canonicalECDSASignatureSValue(s *big.Int, curveN *big.Int) *big.Int { 29 | halfOrder := new(big.Int).Rsh(curveN, 1) 30 | if s.Cmp(halfOrder) <= 0 { 31 | return s 32 | } 33 | 34 | // Set s to N - s so it is in the lower part of signature space, less or equal to half order 35 | return new(big.Int).Sub(curveN, s) 36 | } 37 | 38 | type ecdsaSignature struct { 39 | R, S *big.Int 40 | } 41 | 42 | func asn1ECDSASignature(r, s *big.Int) ([]byte, error) { 43 | return asn1.Marshal(ecdsaSignature{ 44 | R: r, 45 | S: s, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/identity/identity.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package identity defines a client identity and signing implementation used to interact with a Fabric network. 5 | // 6 | // This package provides utilities to aid creation of client identities and accompanying signing implementations from 7 | // various types of credentials. 8 | package identity 9 | 10 | import ( 11 | "crypto/x509" 12 | ) 13 | 14 | // Identity represents a client identity used to interact with a Fabric network. 15 | type Identity interface { 16 | MspID() string // ID of the Membership Service Provider to which this identity belongs. 17 | Credentials() []byte // Implementation-specific credentials. 18 | } 19 | 20 | // X509Identity represents a client identity backed by an X.509 certificate. 21 | type X509Identity struct { 22 | mspID string 23 | certificate []byte 24 | } 25 | 26 | // MspID returns the ID of the Membership Service Provider to which this identity belongs. 27 | func (id *X509Identity) MspID() string { 28 | return id.mspID 29 | } 30 | 31 | // Credentials as an X.509 certificate in PEM encoded ASN.1 DER format. 32 | func (id *X509Identity) Credentials() []byte { 33 | return id.certificate 34 | } 35 | 36 | // NewX509Identity creates a new Identity from an X.509 certificate. 37 | func NewX509Identity(mspID string, certificate *x509.Certificate) (*X509Identity, error) { 38 | certificatePEM, err := CertificateToPEM(certificate) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | identity := &X509Identity{ 44 | mspID: mspID, 45 | certificate: certificatePEM, 46 | } 47 | return identity, nil 48 | } 49 | -------------------------------------------------------------------------------- /pkg/identity/identity_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package identity 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/hyperledger/fabric-gateway/pkg/internal/test" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestIdentity(t *testing.T) { 14 | const mspID = "mspID" 15 | 16 | privateKey, err := test.NewECDSAPrivateKey() 17 | require.NoError(t, err) 18 | 19 | certificate, err := test.NewCertificate(privateKey) 20 | require.NoError(t, err) 21 | 22 | t.Run("NewX509Identity", func(t *testing.T) { 23 | identity, err := NewX509Identity(mspID, certificate) 24 | require.NoError(t, err) 25 | 26 | require.Equal(t, mspID, identity.MspID()) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/identity/pem.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package identity 5 | 6 | import ( 7 | "bytes" 8 | "crypto" 9 | "crypto/x509" 10 | "encoding/pem" 11 | "errors" 12 | ) 13 | 14 | // CertificateToPEM converts an X.509 certificate to PEM encoded ASN.1 DER data. 15 | func CertificateToPEM(certificate *x509.Certificate) ([]byte, error) { 16 | block := &pem.Block{ 17 | Type: "CERTIFICATE", 18 | Bytes: certificate.Raw, 19 | } 20 | return pemEncode(block) 21 | } 22 | 23 | // CertificateFromPEM creates an X.509 certificate from PEM encoded data. 24 | func CertificateFromPEM(certificatePEM []byte) (*x509.Certificate, error) { 25 | block, _ := pem.Decode(certificatePEM) 26 | if block == nil { 27 | return nil, errors.New("failed to parse certificate PEM") 28 | } 29 | 30 | return x509.ParseCertificate(block.Bytes) 31 | } 32 | 33 | // PrivateKeyFromPEM creates a private key from PEM encoded data. 34 | func PrivateKeyFromPEM(privateKeyPEM []byte) (crypto.PrivateKey, error) { 35 | block, _ := pem.Decode(privateKeyPEM) 36 | if block == nil { 37 | return nil, errors.New("failed to parse private key PEM") 38 | } 39 | 40 | privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return privateKey, nil 46 | } 47 | 48 | // PrivateKeyToPEM converts a private key to PEM encoded PKCS #8 data. 49 | func PrivateKeyToPEM(privateKey crypto.PrivateKey) ([]byte, error) { 50 | privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | block := &pem.Block{ 56 | Type: "PRIVATE KEY", 57 | Bytes: privateKeyBytes, 58 | } 59 | return pemEncode(block) 60 | } 61 | 62 | func pemEncode(block *pem.Block) ([]byte, error) { 63 | var buffer bytes.Buffer 64 | if err := pem.Encode(&buffer, block); err != nil { 65 | return nil, err 66 | } 67 | 68 | return buffer.Bytes(), nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/identity/sign.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package identity 5 | 6 | import ( 7 | "crypto" 8 | "crypto/ecdsa" 9 | "crypto/ed25519" 10 | "fmt" 11 | ) 12 | 13 | // Sign function generates a digital signature of the supplied digest. 14 | type Sign = func(digest []byte) ([]byte, error) 15 | 16 | // NewPrivateKeySign returns a Sign function that uses the supplied private key. 17 | // 18 | // Currently supported private key types are: 19 | // - ECDSA. 20 | // - Ed25519. 21 | // 22 | // Note that the Sign implementations have different expectations on the input data supplied to them. 23 | // 24 | // The ECDSA signers operate on a pre-computed message digest, and should be combined with an appropriate hash 25 | // algorithm. P-256 is typically used with a SHA-256 hash, and P-384 is typically used with a SHA-384 hash. 26 | // 27 | // The Ed25519 signer operates on the full message content, and should be combined with a NONE (or no-op) hash 28 | // implementation to ensure the complete message is passed to the signer. 29 | func NewPrivateKeySign(privateKey crypto.PrivateKey) (Sign, error) { 30 | switch key := privateKey.(type) { 31 | case *ecdsa.PrivateKey: 32 | return ecdsaPrivateKeySign(key), nil 33 | case ed25519.PrivateKey: 34 | return ed25519PrivateKeySign(key), nil 35 | default: 36 | return nil, fmt.Errorf("unsupported key type: %T", privateKey) 37 | } 38 | } 39 | 40 | func ed25519PrivateKeySign(privateKey ed25519.PrivateKey) Sign { 41 | return func(message []byte) ([]byte, error) { 42 | signature := ed25519.Sign(privateKey, message) 43 | return signature, nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/internal/staticcheck.conf: -------------------------------------------------------------------------------- 1 | checks = ["inherit", "-ST1000"] 2 | -------------------------------------------------------------------------------- /pkg/internal/test/credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package test 5 | 6 | import ( 7 | "crypto" 8 | "crypto/ecdsa" 9 | "crypto/ed25519" 10 | "crypto/elliptic" 11 | "crypto/rand" 12 | "crypto/x509" 13 | "crypto/x509/pkix" 14 | "fmt" 15 | "math/big" 16 | "time" 17 | ) 18 | 19 | // NewECDSAPrivateKey generates a new private key for testing 20 | func NewECDSAPrivateKey() (*ecdsa.PrivateKey, error) { 21 | return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 22 | } 23 | 24 | // NewEd25519KeyPair generates a new public and private key pair for testing 25 | func NewEd25519KeyPair() (ed25519.PublicKey, ed25519.PrivateKey, error) { 26 | return ed25519.GenerateKey(rand.Reader) 27 | } 28 | 29 | // NewCertificate generates a new certificate from a private key for testing 30 | func NewCertificate(privateKey crypto.PrivateKey) (*x509.Certificate, error) { 31 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 32 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 33 | if err != nil { 34 | return nil, fmt.Errorf("failed to generate serial number: %w", err) 35 | } 36 | 37 | notBefore := time.Now() 38 | notAfter := notBefore.Add(time.Hour * 24) 39 | 40 | template := x509.Certificate{ 41 | SerialNumber: serialNumber, 42 | Subject: pkix.Name{ 43 | Organization: []string{"Test"}, 44 | }, 45 | NotBefore: notBefore, 46 | NotAfter: notAfter, 47 | 48 | KeyUsage: x509.KeyUsageDigitalSignature, 49 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 50 | BasicConstraintsValid: true, 51 | 52 | DNSNames: []string{"test.example.org"}, 53 | } 54 | 55 | publicKey := privateKey.(crypto.Signer).Public() 56 | certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey) 57 | if err != nil { 58 | return nil, fmt.Errorf("failed to generate certificate: %w", err) 59 | } 60 | 61 | return x509.ParseCertificate(certificateBytes) 62 | } 63 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs-material 2 | -------------------------------------------------------------------------------- /scenario/features/discovery.feature: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 IBM All Rights Reserved. 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | Feature: Discovery 7 | Background: 8 | Given I have deployed a Fabric network 9 | And I have created and joined all channels 10 | And I deploy golang chaincode named basic at version 1.0.0 for all organizations on channel mychannel with endorsement policy AND("Org1MSP.member", "Org2MSP.member", "Org3MSP.member") 11 | And I create a gateway named mygateway for user User1 in MSP Org1MSP 12 | And I connect the gateway to peer0.org1.example.com 13 | And I use the mychannel network 14 | And I use the basic contract 15 | 16 | Scenario: Submit fails with insufficient endorsers 17 | When I stop the peer named peer0.org3.example.com 18 | And I prepare to submit an echo transaction 19 | And I set the transaction arguments to ["conga"] 20 | Then the transaction invocation should fail 21 | 22 | Scenario: Submit succeeds with sufficient endorsers 23 | When I prepare to submit an echo transaction 24 | And I set the transaction arguments to ["conga"] 25 | And I invoke the transaction 26 | Then the response should be "conga" 27 | 28 | -------------------------------------------------------------------------------- /scenario/features/offlinesign.feature: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 IBM All Rights Reserved. 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | Feature: Off-line signing 7 | Background: 8 | Given I have deployed a Fabric network 9 | And I have created and joined all channels 10 | And I deploy golang chaincode named basic at version 1.0.0 for all organizations on channel mychannel with endorsement policy AND("Org1MSP.member","Org2MSP.member") 11 | And I create a gateway named mygateway without signer for user User1 in MSP Org1MSP 12 | And I connect the gateway to peer0.org1.example.com 13 | And I use the mychannel network 14 | And I use the basic contract 15 | 16 | Scenario: Evaluate fails without signing 17 | When I prepare to evaluate an echo transaction 18 | And I set the transaction arguments to ["conga"] 19 | Then the transaction invocation should fail 20 | 21 | Scenario: Evaluate succeeds with off-line signing 22 | When I prepare to evaluate an echo transaction 23 | And I set the transaction arguments to ["conga"] 24 | And I do off-line signing as user User1 in MSP Org1MSP 25 | And I invoke the transaction 26 | Then the response should be "conga" 27 | 28 | Scenario: Submit fails without signing 29 | When I prepare to submit an echo transaction 30 | And I set the transaction arguments to ["conga"] 31 | Then the transaction invocation should fail 32 | 33 | Scenario: Submit succeeds with off-line signing 34 | When I prepare to submit an echo transaction 35 | And I set the transaction arguments to ["conga"] 36 | And I do off-line signing as user User1 in MSP Org1MSP 37 | And I invoke the transaction 38 | Then the response should be "conga" 39 | -------------------------------------------------------------------------------- /scenario/fixtures/ca-client-config/.gitignore: -------------------------------------------------------------------------------- 1 | fabric-ca-client-config.yaml 2 | -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/golang/basic/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/golang/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyperledger/fabric-gateway/scenario/fixtures/chaincode/golang/basic 2 | 3 | go 1.23.0 4 | 5 | require github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 6 | 7 | require ( 8 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 9 | github.com/go-openapi/jsonreference v0.21.0 // indirect 10 | github.com/go-openapi/spec v0.21.0 // indirect 11 | github.com/go-openapi/swag v0.23.0 // indirect 12 | github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 // indirect 13 | github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect 14 | github.com/josharian/intern v1.0.0 // indirect 15 | github.com/mailru/easyjson v0.7.7 // indirect 16 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 17 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 18 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 19 | golang.org/x/net v0.38.0 // indirect 20 | golang.org/x/sys v0.31.0 // indirect 21 | golang.org/x/text v0.23.0 // indirect 22 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect 23 | google.golang.org/grpc v1.67.0 // indirect 24 | google.golang.org/protobuf v1.36.1 // indirect 25 | gopkg.in/yaml.v3 v3.0.1 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/golang/private/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/golang/private/collections_config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "SharedCollection", 4 | "policy": "OR('Org1MSP.member', 'Org3MSP.member')", 5 | "requiredPeerCount": 1, 6 | "maxPeerCount": 1, 7 | "blockToLive":1000000, 8 | "memberOnlyRead": true, 9 | "memberOnlyWrite": true 10 | }, 11 | { 12 | "name": "Org1Collection", 13 | "policy": "OR('Org1MSP.member')", 14 | "requiredPeerCount": 0, 15 | "maxPeerCount": 1, 16 | "blockToLive":3, 17 | "memberOnlyRead": true, 18 | "memberOnlyWrite": false, 19 | "endorsementPolicy": { 20 | "signaturePolicy": "OR('Org1MSP.member')" 21 | } 22 | }, 23 | { 24 | "name": "Org3Collection", 25 | "policy": "OR('Org3MSP.member')", 26 | "requiredPeerCount": 0, 27 | "maxPeerCount": 1, 28 | "blockToLive":3, 29 | "memberOnlyRead": false, 30 | "memberOnlyWrite": false, 31 | "endorsementPolicy": { 32 | "signaturePolicy": "OR('Org3MSP.member')" 33 | } 34 | } 35 | ] -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/golang/private/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyperledger/fabric-gateway/scenario/fixtures/chaincode/golang/private 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 7 | github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 8 | ) 9 | 10 | require ( 11 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 12 | github.com/go-openapi/jsonreference v0.21.0 // indirect 13 | github.com/go-openapi/spec v0.21.0 // indirect 14 | github.com/go-openapi/swag v0.23.0 // indirect 15 | github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect 16 | github.com/josharian/intern v1.0.0 // indirect 17 | github.com/mailru/easyjson v0.7.7 // indirect 18 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 19 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 20 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 21 | golang.org/x/net v0.38.0 // indirect 22 | golang.org/x/sys v0.31.0 // indirect 23 | golang.org/x/text v0.23.0 // indirect 24 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect 25 | google.golang.org/grpc v1.67.0 // indirect 26 | google.golang.org/protobuf v1.36.1 // indirect 27 | gopkg.in/yaml.v3 v3.0.1 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/node/errors/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/node/errors/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const ErrorsContract = require('./lib/errors-contract'); 8 | 9 | module.exports.ErrorsContract = ErrorsContract; 10 | module.exports.contracts = [ ErrorsContract ]; 11 | -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/node/errors/lib/errors-contract.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const { Contract } = require('fabric-contract-api'); 8 | 9 | class ErrorsContract extends Contract { 10 | 11 | async exists(ctx, id) { 12 | const buffer = await ctx.stub.getState(id); 13 | return (!!buffer && buffer.length > 0); 14 | } 15 | 16 | async crash(ctx) { 17 | console.log('CRASHING........'); 18 | process.exit(99); 19 | } 20 | 21 | async nondet(ctx) { 22 | const rd = Math.random() * 100000; 23 | console.log('Running nondet', rd); 24 | 25 | await ctx.stub.putState('random', Buffer.from(rd.toString())); 26 | } 27 | 28 | async orgsFail(ctx, orgsJSON) { 29 | let orgs = JSON.parse(orgsJSON); 30 | const peerOrg = await ctx.stub.getMspID(); 31 | if (orgs.includes(peerOrg)) { 32 | throw new Error(peerOrg + ' refuses to endorse this'); 33 | } 34 | await ctx.stub.putState('mykey', 'myvalue'); 35 | } 36 | 37 | async longRunning(ctx, repeat) { 38 | for (let i = 0; i < repeat; i++) { 39 | const res = await ctx.stub.getStateByRange(null, null); 40 | console.log(res); 41 | } 42 | } 43 | } 44 | 45 | module.exports = ErrorsContract; 46 | -------------------------------------------------------------------------------- /scenario/fixtures/chaincode/node/errors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "errors", 3 | "version": "0.0.1", 4 | "description": "My Smart Contract", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo 'No tests implemented' && exit 1", 8 | "start": "fabric-chaincode-node start" 9 | }, 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "fabric-contract-api": "~2.5", 13 | "fabric-shim": "~2.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scenario/fixtures/crypto-material/.gitignore: -------------------------------------------------------------------------------- 1 | crypto-config/ 2 | core.yaml 3 | genesis.block 4 | mychannel.block 5 | channel.tx 6 | Org1MSPanchors.tx 7 | Org2MSPanchors.tx 8 | Org3MSPanchors.tx 9 | hsm/ 10 | -------------------------------------------------------------------------------- /scenario/fixtures/crypto-material/create_channel.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" 6 | 7 | clean_config() { 8 | rm -rf /etc/hyperledger/config/channel.tx \ 9 | /etc/hyperledger/config/core.yaml \ 10 | /etc/hyperledger/config/genesis.block \ 11 | /etc/hyperledger/config/mychannel.block \ 12 | /etc/hyperledger/config/crypto-config 13 | } 14 | 15 | create_channel_config() { 16 | cryptogen generate --config=/etc/hyperledger/config/crypto-config.yaml \ 17 | --output=/etc/hyperledger/config/crypto-config 18 | configtxgen -configPath /etc/hyperledger/config \ 19 | -profile ThreeOrgsApplicationGenesis \ 20 | -outputBlock /etc/hyperledger/config/mychannel.block \ 21 | -channelID mychannel 22 | } 23 | 24 | rename_secret_key() { 25 | local KEY 26 | local KEY_DIR 27 | 28 | find "${BASEDIR}/crypto-config" -type f -name '*_sk' -print | while read -r KEY; do 29 | KEY_DIR=$(dirname "${KEY}") 30 | mv "${KEY}" "${KEY_DIR}/key.pem" 31 | chmod 644 "${KEY_DIR}/key.pem" 32 | done 33 | } 34 | 35 | clean_config 36 | create_channel_config 37 | rename_secret_key 38 | 39 | cp "${FABRIC_CFG_PATH}/core.yaml" /etc/hyperledger/config 40 | -------------------------------------------------------------------------------- /scenario/fixtures/docker-compose/.env: -------------------------------------------------------------------------------- 1 | # Environment variables will be overridden if set in user environment or Makefile. 2 | # Typically we'll want to test with the latest released orderer and tools images from dockerhub, but latest unreleased peer image that has been pulled and tagged 3 | FABRIC_VERSION=2.5 4 | CA_VERSION=1.5 5 | DOCKER_DEBUG=info:dockercontroller,gateway=debug 6 | DOCKER_SOCK=/var/run/docker.sock 7 | -------------------------------------------------------------------------------- /scenario/fixtures/docker-compose/docker-compose-cli.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | 15 | version: "3.8" 16 | 17 | services: 18 | clinopeer: 19 | container_name: cli 20 | image: hyperledger/fabric-tools:${FABRIC_VERSION} 21 | tty: true 22 | environment: 23 | # LOGGING SETTINGS 24 | - FABRIC_LOGGING_SPEC=${DOCKER_DEBUG} 25 | command: /bin/bash 26 | volumes: 27 | - ../crypto-material:/etc/hyperledger/config/ 28 | -------------------------------------------------------------------------------- /scenario/fixtures/generate-hsm-user.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | # Required environment variables 6 | : "${SOFTHSM2_CONF:?}" 7 | 8 | # define the CA setup 9 | CA_URL="127.0.0.1:7054" 10 | 11 | # try to locate the Soft HSM library 12 | POSSIBLE_LIB_LOC=( 13 | '/usr/lib/softhsm/libsofthsm2.so' 14 | '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so' 15 | '/usr/local/lib/softhsm/libsofthsm2.so' 16 | '/usr/lib/libacsp-pkcs11.so' 17 | '/opt/homebrew/lib/softhsm/libsofthsm2.so' 18 | ) 19 | for TEST_LIB in "${POSSIBLE_LIB_LOC[@]}"; do 20 | if [ -f "${TEST_LIB}" ]; then 21 | HSM2_LIB="${TEST_LIB}" 22 | break 23 | fi 24 | done 25 | [ -z "${HSM2_LIB}" ] && echo No SoftHSM PKCS11 Library found, ensure you have installed softhsm2 && exit 1 26 | 27 | # Update the client config file to point to the softhsm pkcs11 library 28 | CLIENT_CONFIG=./ca-client-config/fabric-ca-client-config.yaml 29 | sed "s+REPLACE_ME_HSMLIB+${HSM2_LIB}+g" < ca-client-config/fabric-ca-client-config-template.yaml > "${CLIENT_CONFIG}" 30 | 31 | # create the users, remove any existing users 32 | CRYPTO_PATH="${PWD}/crypto-material/hsm" 33 | [ -d "${CRYPTO_PATH}" ] && rm -fr "${CRYPTO_PATH}" 34 | 35 | CA_ADMIN='admin' 36 | CA_ADMIN_PW='adminpw' 37 | # user passed in as parameter 38 | HSM_USER="$1" 39 | 40 | fabric-ca-client enroll \ 41 | -c "${CLIENT_CONFIG}" \ 42 | -u "http://${CA_ADMIN}:${CA_ADMIN_PW}@${CA_URL}" \ 43 | --mspdir "${CRYPTO_PATH}/${CA_ADMIN}" \ 44 | --csr.hosts example.com 45 | 46 | ! fabric-ca-client register \ 47 | -c "${CLIENT_CONFIG}" \ 48 | --mspdir "${CRYPTO_PATH}/${CA_ADMIN}" \ 49 | --id.name "${HSM_USER}" \ 50 | --id.secret "${HSM_USER}" \ 51 | --id.type client \ 52 | --caname ca-org1 \ 53 | --id.maxenrollments 0 \ 54 | -m example.com \ 55 | -u "http://${CA_URL}" && echo 'user probably already registered, continuing' 56 | 57 | fabric-ca-client enroll \ 58 | -c "${CLIENT_CONFIG}" \ 59 | -u "http://${HSM_USER}:${HSM_USER}@${CA_URL}" \ 60 | --mspdir "${CRYPTO_PATH}/${HSM_USER}" \ 61 | --csr.hosts example.com 62 | -------------------------------------------------------------------------------- /scenario/fixtures/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" 6 | 7 | cd "${BASEDIR}/docker-compose" 8 | 9 | docker compose --file docker-compose-cli.yaml run --rm clinopeer /etc/hyperledger/config/create_channel.sh 10 | -------------------------------------------------------------------------------- /scenario/go/blockevents_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package scenario 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "time" 10 | 11 | "github.com/hyperledger/fabric-gateway/pkg/client" 12 | "github.com/hyperledger/fabric-protos-go-apiv2/common" 13 | ) 14 | 15 | type BlockEventListener struct { 16 | ctx context.Context 17 | cancel context.CancelFunc 18 | events <-chan *common.Block 19 | } 20 | 21 | func NewBlockEventListener(parentCtx context.Context, network *client.Network, options ...client.BlockEventsOption) (*BlockEventListener, error) { 22 | ctx, cancel := context.WithCancel(parentCtx) 23 | 24 | events, err := network.BlockEvents(ctx, options...) 25 | if err != nil { 26 | cancel() 27 | return nil, err 28 | } 29 | 30 | listener := &BlockEventListener{ 31 | ctx: ctx, 32 | cancel: cancel, 33 | events: events, 34 | } 35 | return listener, nil 36 | } 37 | 38 | func (listener *BlockEventListener) Event() (*common.Block, error) { 39 | select { 40 | case event, ok := <-listener.events: 41 | if !ok { 42 | return nil, errors.New("event channel closed") 43 | } 44 | return event, nil 45 | case <-time.After(30 * time.Second): 46 | return nil, errors.New("timeout waiting for event") 47 | } 48 | } 49 | 50 | func (listener *BlockEventListener) Close() { 51 | listener.cancel() 52 | } 53 | 54 | type CheckpointBlockEventListener struct { 55 | listener BlockEvents 56 | checkpoint func(*common.Block) 57 | } 58 | 59 | func NewCheckpointBlockEventListener(listener BlockEvents, checkpoint func(*common.Block)) *CheckpointBlockEventListener { 60 | checkpointListener := &CheckpointBlockEventListener{ 61 | listener: listener, 62 | checkpoint: checkpoint, 63 | } 64 | return checkpointListener 65 | } 66 | 67 | func (listener *CheckpointBlockEventListener) Event() (*common.Block, error) { 68 | event, err := listener.listener.Event() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | listener.checkpoint(event) 74 | 75 | return event, nil 76 | } 77 | 78 | func (listener *CheckpointBlockEventListener) Close() { 79 | listener.listener.Close() 80 | } 81 | -------------------------------------------------------------------------------- /scenario/go/chaincodeevents_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package scenario 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "time" 10 | 11 | "github.com/hyperledger/fabric-gateway/pkg/client" 12 | ) 13 | 14 | type ChaincodeEventListener struct { 15 | ctx context.Context 16 | cancel context.CancelFunc 17 | events <-chan *client.ChaincodeEvent 18 | } 19 | 20 | func NewChaincodeEventListener(parentCtx context.Context, network *client.Network, chaincodeName string, options ...client.ChaincodeEventsOption) (*ChaincodeEventListener, error) { 21 | ctx, cancel := context.WithCancel(parentCtx) 22 | 23 | events, err := network.ChaincodeEvents(ctx, chaincodeName, options...) 24 | if err != nil { 25 | cancel() 26 | return nil, err 27 | } 28 | 29 | listener := &ChaincodeEventListener{ 30 | ctx: ctx, 31 | cancel: cancel, 32 | events: events, 33 | } 34 | return listener, nil 35 | } 36 | 37 | func (listener *ChaincodeEventListener) Event() (*client.ChaincodeEvent, error) { 38 | select { 39 | case event, ok := <-listener.events: 40 | if !ok { 41 | return nil, errors.New("event channel closed") 42 | } 43 | return event, nil 44 | case <-time.After(30 * time.Second): 45 | return nil, errors.New("timeout waiting for event") 46 | } 47 | } 48 | 49 | func (listener *ChaincodeEventListener) Close() { 50 | listener.cancel() 51 | } 52 | 53 | type CheckpointChaincodeEventListener struct { 54 | listener ChaincodeEvents 55 | checkpoint func(*client.ChaincodeEvent) 56 | } 57 | 58 | func NewCheckpointChaincodeEventListener(listener ChaincodeEvents, checkpoint func(*client.ChaincodeEvent)) *CheckpointChaincodeEventListener { 59 | checkpointListener := &CheckpointChaincodeEventListener{ 60 | listener: listener, 61 | checkpoint: checkpoint, 62 | } 63 | return checkpointListener 64 | } 65 | 66 | func (listener *CheckpointChaincodeEventListener) Event() (*client.ChaincodeEvent, error) { 67 | event, err := listener.listener.Event() 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | listener.checkpoint(event) 73 | 74 | return event, nil 75 | } 76 | 77 | func (listener *CheckpointChaincodeEventListener) Close() { 78 | listener.listener.Close() 79 | } 80 | -------------------------------------------------------------------------------- /scenario/go/filteredblockevents_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package scenario 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "time" 10 | 11 | "github.com/hyperledger/fabric-gateway/pkg/client" 12 | "github.com/hyperledger/fabric-protos-go-apiv2/peer" 13 | ) 14 | 15 | type FilteredBlockEventListener struct { 16 | ctx context.Context 17 | cancel context.CancelFunc 18 | events <-chan *peer.FilteredBlock 19 | } 20 | 21 | func NewFilteredBlockEventListener(parentCtx context.Context, network *client.Network, options ...client.BlockEventsOption) (*FilteredBlockEventListener, error) { 22 | ctx, cancel := context.WithCancel(parentCtx) 23 | 24 | events, err := network.FilteredBlockEvents(ctx, options...) 25 | if err != nil { 26 | cancel() 27 | return nil, err 28 | } 29 | 30 | listener := &FilteredBlockEventListener{ 31 | ctx: ctx, 32 | cancel: cancel, 33 | events: events, 34 | } 35 | return listener, nil 36 | } 37 | 38 | func (listener *FilteredBlockEventListener) Event() (*peer.FilteredBlock, error) { 39 | select { 40 | case event, ok := <-listener.events: 41 | if !ok { 42 | return nil, errors.New("event channel closed") 43 | } 44 | return event, nil 45 | case <-time.After(30 * time.Second): 46 | return nil, errors.New("timeout waiting for event") 47 | } 48 | } 49 | 50 | func (listener *FilteredBlockEventListener) Close() { 51 | listener.cancel() 52 | } 53 | 54 | type CheckpointFilteredBlockEventListener struct { 55 | listener FilteredBlockEvents 56 | checkpoint func(*peer.FilteredBlock) 57 | } 58 | 59 | func NewCheckpointFilteredBlockEventListener(listener FilteredBlockEvents, checkpoint func(*peer.FilteredBlock)) *CheckpointFilteredBlockEventListener { 60 | checkpointListener := &CheckpointFilteredBlockEventListener{ 61 | listener: listener, 62 | checkpoint: checkpoint, 63 | } 64 | return checkpointListener 65 | } 66 | 67 | func (listener *CheckpointFilteredBlockEventListener) Event() (*peer.FilteredBlock, error) { 68 | event, err := listener.listener.Event() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | listener.checkpoint(event) 74 | 75 | return event, nil 76 | } 77 | 78 | func (listener *CheckpointFilteredBlockEventListener) Close() { 79 | listener.listener.Close() 80 | } 81 | -------------------------------------------------------------------------------- /scenario/go/godogs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package scenario 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | "github.com/cucumber/godog" 11 | "github.com/cucumber/godog/colors" 12 | "github.com/spf13/pflag" 13 | ) 14 | 15 | var opts = godog.Options{ 16 | Output: colors.Colored(os.Stdout), 17 | // Default configuration 18 | Format: "pretty", 19 | } 20 | 21 | func init() { 22 | godog.BindCommandLineFlags("godog.", &opts) 23 | } 24 | 25 | func TestMain(m *testing.M) { 26 | pflag.Parse() 27 | opts.Paths = pflag.Args() 28 | 29 | status := godog.TestSuite{ 30 | Name: "godogs", 31 | TestSuiteInitializer: InitializeTestSuite, 32 | ScenarioInitializer: InitializeScenario, 33 | Options: &opts, 34 | }.Run() 35 | 36 | os.Exit(status) 37 | } 38 | -------------------------------------------------------------------------------- /scenario/node/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /scenario/node/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import tseslint from 'typescript-eslint'; 2 | import base from '../../node/eslint.config.base.mjs'; 3 | 4 | export default tseslint.config(...base, { 5 | languageOptions: { 6 | parserOptions: { 7 | tsconfigRootDir: import.meta.dirname, 8 | }, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /scenario/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hyperledger/fabric-gateway-scenario", 3 | "version": "0.0.1", 4 | "description": "Scenario test for Fabric Gateway", 5 | "engines": { 6 | "node": ">=20.9.0" 7 | }, 8 | "scripts": { 9 | "build": "npm run clean && npm run compile && npm run lint && npm run format", 10 | "clean": "rm -rf dist", 11 | "cucumber": "NODE_OPTIONS='--enable-source-maps' cucumber-js --format cucumber-console-formatter --require 'dist/**/*.js' ../features", 12 | "cucumber:no-hsm": "NODE_OPTIONS='--enable-source-maps' cucumber-js --tags 'not @hsm' --format cucumber-console-formatter --require 'dist/**/*.js' ../features", 13 | "compile": "tsc", 14 | "format": "prettier '**/*.{ts,js}' --check", 15 | "format:fix": "prettier '**/*.{ts,js}' --write", 16 | "lint": "eslint src", 17 | "test": "npm run build && npm run cucumber", 18 | "test:no-hsm": "npm run build && npm run cucumber:no-hsm" 19 | }, 20 | "private": true, 21 | "author": "", 22 | "license": "Apache-2.0", 23 | "dependencies": { 24 | "@hyperledger/fabric-gateway": "file:../../node/fabric-gateway-dev.tgz", 25 | "@hyperledger/fabric-protos": "^0.3.0" 26 | }, 27 | "devDependencies": { 28 | "@cucumber/cucumber": "^11.2.0", 29 | "@tsconfig/node20": "^20.1.5", 30 | "@types/node": "^20.17.32", 31 | "cucumber-console-formatter": "^1.0.0", 32 | "eslint": "^9.25.1", 33 | "eslint-config-prettier": "^10.1.2", 34 | "expect": "^29.7.0", 35 | "prettier": "^3.5.3", 36 | "typescript": "~5.8.3", 37 | "typescript-eslint": "^8.31.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scenario/node/src/baseeventlistener.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { CloseableAsyncIterable } from '@hyperledger/fabric-gateway'; 8 | import { EventListener } from './eventlistener'; 9 | 10 | export class BaseEventListener implements EventListener { 11 | #iterator: AsyncIterator; 12 | #close: () => void; 13 | 14 | constructor(events: CloseableAsyncIterable) { 15 | this.#iterator = events[Symbol.asyncIterator](); 16 | this.#close = () => { 17 | events.close(); 18 | }; 19 | } 20 | 21 | async next(): Promise { 22 | const result = await this.#iterator.next(); 23 | return result.value as T; 24 | } 25 | 26 | close(): void { 27 | this.#close(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scenario/node/src/checkpointeventlistener.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { CloseableAsyncIterable } from '@hyperledger/fabric-gateway'; 8 | import { BaseEventListener } from './baseeventlistener'; 9 | import { EventListener } from './eventlistener'; 10 | 11 | export class CheckpointEventListener implements EventListener { 12 | readonly #checkpoint: (event: T) => Promise; 13 | #eventListener: BaseEventListener; 14 | 15 | constructor(events: CloseableAsyncIterable, checkpoint: (event: T) => Promise) { 16 | this.#eventListener = new BaseEventListener(events); 17 | this.#checkpoint = checkpoint; 18 | } 19 | 20 | async next(): Promise { 21 | const event = await this.#eventListener.next(); 22 | await this.#checkpoint(event); 23 | return event; 24 | } 25 | 26 | close(): void { 27 | this.#eventListener.close(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scenario/node/src/eventlistener.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | export interface EventListener { 8 | next(): Promise; 9 | close(): void; 10 | } 11 | -------------------------------------------------------------------------------- /scenario/node/src/fabricski.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright IBM Corp. All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { KeyObject, X509Certificate, createHash } from 'node:crypto'; 8 | import { assertDefined } from './utils'; 9 | 10 | export function getSKIFromCertificate(certificate: X509Certificate): Buffer { 11 | const uncompressedPoint = getUncompressedPointOnCurve(certificate.publicKey); 12 | return createHash('sha256').update(uncompressedPoint).digest(); 13 | } 14 | 15 | function getUncompressedPointOnCurve(key: KeyObject): Buffer { 16 | const jwk = key.export({ format: 'jwk' }); 17 | const x = Buffer.from(assertDefined(jwk.x, 'x'), 'base64url'); 18 | const y = Buffer.from(assertDefined(jwk.y, 'y'), 'base64url'); 19 | const prefix = Buffer.from('04', 'hex'); 20 | return Buffer.concat([prefix, x, y]); 21 | } 22 | -------------------------------------------------------------------------------- /scenario/node/src/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IBM All Rights Reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import { inspect, TextDecoder } from 'node:util'; 8 | 9 | const utf8Decoder = new TextDecoder(); 10 | 11 | export function bytesAsString(bytes?: Uint8Array): string { 12 | return utf8Decoder.decode(bytes); 13 | } 14 | 15 | export function toString(thing: unknown): string { 16 | return inspect(thing); 17 | } 18 | 19 | export function toError(err: unknown): Error { 20 | if (err instanceof Error) { 21 | return err; 22 | } 23 | return new Error(toString(err)); 24 | } 25 | 26 | export function assertDefined(value: T | undefined, property: string): T { 27 | if (value === undefined) { 28 | throw new Error(`Bad step sequence: ${property} not defined`); 29 | } 30 | return value; 31 | } 32 | 33 | export interface Constructor { 34 | new (...args: never[]): T; 35 | } 36 | 37 | export function isInstanceOf(o: unknown, type: Constructor): o is T { 38 | return o instanceof type; 39 | } 40 | -------------------------------------------------------------------------------- /scenario/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@tsconfig/node20/tsconfig.json", 4 | "compilerOptions": { 5 | "declaration": false, 6 | "erasableSyntaxOnly": true, 7 | "isolatedModules": true, 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "rootDir": "src", 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noImplicitReturns": true, 14 | "noUncheckedIndexedAccess": true, 15 | "forceConsistentCasingInFileNames": true 16 | }, 17 | "include": ["src/"] 18 | } 19 | -------------------------------------------------------------------------------- /scripts/shellcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -u -o pipefail 4 | 5 | fileCount=0 6 | failCount=0 7 | 8 | while read -r scriptFile; do 9 | if [ -n "${scriptFile}" ]; then 10 | ((fileCount++)) 11 | shellcheck "${scriptFile}" || ((failCount++)) 12 | fi 13 | done <<< "$(find . -type d \( -name vendor -o -name node_modules \) -prune -o -type f -name '*.sh' -print)" 14 | 15 | echo "Checked ${fileCount} files with ${failCount} failing." 16 | exit "${failCount}" 17 | -------------------------------------------------------------------------------- /scripts/update-versions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | PROJECT_DIR="$( cd "$( dirname -- "${BASH_SOURCE[0]}" )/.." > /dev/null && pwd )" 6 | 7 | VERIFY_VERSIONS="${PROJECT_DIR}/.github/workflows/verify-versions.yml" 8 | JAVA_DIR="${PROJECT_DIR}/java" 9 | NODE_DIR="${PROJECT_DIR}/node" 10 | 11 | if [ $# -eq 1 ]; then 12 | NEXT_VERSION="$1" 13 | else 14 | NEXT_VERSION="$( 15 | awk 'match($0, /^[ \t]*GATEWAY_VERSION:[ \t]*([0-9]+)\.([0-9]+)\.([0-9]+)/, v) { 16 | printf "%d.%d.%d", v[1], v[2], v[3]+1 17 | exit 18 | }' \ 19 | "${VERIFY_VERSIONS}" 20 | )" 21 | fi 22 | NEXT_JAVA_VERSION="${NEXT_VERSION}-SNAPSHOT" 23 | 24 | echo "Updating Java version to ${NEXT_JAVA_VERSION}" 25 | ( cd "${JAVA_DIR}" && \ 26 | mvn --batch-mode --quiet versions:set -DnewVersion="${NEXT_JAVA_VERSION}" -DgenerateBackupPoms=false ) 27 | 28 | 29 | echo "Updating Node version to ${NEXT_VERSION}" 30 | ( cd "${NODE_DIR}" && \ 31 | npm --allow-same-version --no-git-tag-version version "${NEXT_VERSION}" ) 32 | make build-scenario-node 33 | 34 | echo "Updating verify-versions.yml to ${NEXT_VERSION}" 35 | NEXT_VERIFY_VERSIONS="$( sed -r "s/^([ \t]*GATEWAY_VERSION:[ \t]*)[0-9.]+/\1${NEXT_VERSION}/" "${VERIFY_VERSIONS}" )" 36 | echo "${NEXT_VERIFY_VERSIONS}" > "${VERIFY_VERSIONS}" 37 | --------------------------------------------------------------------------------