diff --git a/.github/workflows/docker_main.yml b/.github/workflows/docker_main.yml index 6b360ed85a..94eadaf729 100644 --- a/.github/workflows/docker_main.yml +++ b/.github/workflows/docker_main.yml @@ -4,10 +4,16 @@ on: push: branches: - main + paths-ignore: + - '.github/**' # exclude .github directory + - '**.md' # exclude all markdown files jobs: docker: runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/docker_release.yml b/.github/workflows/docker_release.yml index b58c9de961..11a5c311a0 100644 --- a/.github/workflows/docker_release.yml +++ b/.github/workflows/docker_release.yml @@ -5,13 +5,25 @@ on: types: [released, prereleased] jobs: + docker: runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Get the latest tag + id: get_latest_tag + run: | + git fetch --tags + latest_tag=$(git tag -l | sort -V | tail -n 1) + echo "latest tag: $latest_tag" + echo "LATEST_TAG=$latest_tag" >> $GITHUB_ENV + - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -25,9 +37,9 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Set latest tag - if: github.event.action == 'released' + if: github.event.action == 'released' && github.ref_name == env.LATEST_TAG run: | echo "DOCKER_TAGS=${{ env.DOCKER_TAGS }},ghcr.io/${{ github.repository }}:latest" >> $GITHUB_ENV diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 83dbdae578..7922048bcd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,32 +23,24 @@ jobs: git config --global user.name "GitHub Actions" git config --global user.email "noreply@github.com" - - name: Check if this is the latest release + - name: Get the latest tag run: | - LATEST_TAG=$( - curl -L \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${{ github.token }}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/${{ github.repository }}/releases/latest \ - | jq -r '.tag_name' - ) - IS_LATEST=${{ env.LATEST_TAG == github.event.release.tag_name }} - echo This release is: "${{ github.event.release.tag_name }}" - echo The latest release is: "$LATEST_TAG" - echo "IS_LATEST_RELEASE=$IS_LATEST" >> "$GITHUB_ENV" + git fetch --tags + latest_tag=$(git tag -l | sort -V | tail -n 1) + echo "latest tag: $latest_tag" + echo "LATEST_TAG=$latest_tag" >> $GITHUB_ENV - name: Install docs dependencies working-directory: doc-site run: pip install -r requirements.txt - name: Update doc site for release - if: ${{ github.event_name == 'release' && env.IS_LATEST_RELEASE != 'true' }} + if: github.event.action == 'released' && github.ref_name != env.LATEST_TAG working-directory: doc-site run: mike deploy ${{ github.event.release.tag_name }} --push - name: Update doc site for latest release - if: ${{ github.event_name == 'release' && env.IS_LATEST_RELEASE == 'true' }} + if: github.event.action == 'released' && github.ref_name == env.LATEST_TAG working-directory: doc-site run: mike deploy ${{ github.event.release.tag_name }} latest -u --push diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 895ee57e21..74d832b7b3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -3,8 +3,19 @@ name: Go on: push: branches: [main] + paths: + - '**' # include all files + - '!.github/**' # exclude .github directory + - '!**.md' # exclude all markdown files + - 'doc-site/docs/reference/**.md' # include markdown files that are auto generated and need to be tested + pull_request: - branches: [main] + paths: + - '**' # include all files + - '!.github/**' # exclude .github directory + - '!**.md' # exclude all markdown files + - 'doc-site/docs/reference/**.md' # include markdown files that are auto generated and need to be tested + workflow_dispatch: jobs: @@ -20,7 +31,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.21 + go-version: 1.22 - name: Build and Test run: make @@ -40,7 +51,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.21 + go-version: 1.22 - name: Build Docker image run: make docker @@ -132,7 +143,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.21 + go-version: 1.22 - name: Download Docker image uses: actions/download-artifact@v3 diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 8d3252bde8..91da3d7f16 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -54,7 +54,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.21 + go-version: 1.22 - name: Update manifest to latest commit for every service run: ./manifestgen.sh head @@ -91,7 +91,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.21 + go-version: 1.22 - name: Update manifest to latest commit for every service run: ./manifestgen.sh head diff --git a/Dockerfile b/Dockerfile index 871f448212..c5632b71b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,24 @@ +# ARG Definitions +# Consider adding default values for the ARGs based on this warning: +# https://github.com/hyperledger/firefly/actions/runs/10795366695/job/29941873807#step:4:171 ARG FIREFLY_BUILDER_TAG ARG FABRIC_BUILDER_TAG ARG FABRIC_BUILDER_PLATFORM ARG SOLIDITY_BUILDER_TAG ARG BASE_TAG + ARG BUILD_VERSION ARG GIT_REF +# Firefly Builder FROM $FIREFLY_BUILDER_TAG AS firefly-builder ARG BUILD_VERSION ARG GIT_REF RUN apk add make=4.4.1-r2 \ gcc=13.2.1_git20231014-r0 \ build-base=0.5-r3 \ - curl=8.9.0-r0 \ - git=2.43.4-r0 + curl=8.9.1-r1 \ + git=2.43.5-r0 WORKDIR /firefly RUN chgrp -R 0 /firefly \ && chmod -R g+rwX /firefly \ @@ -26,6 +31,7 @@ RUN go mod download ADD --chown=1001:0 . . RUN make build +# Fabric Builder FROM --platform=$FABRIC_BUILDER_PLATFORM $FABRIC_BUILDER_TAG AS fabric-builder WORKDIR /firefly/smart_contracts/fabric/firefly-go RUN chgrp -R 0 /firefly \ @@ -39,13 +45,13 @@ RUN GO111MODULE=on go mod vendor WORKDIR /tmp/fabric RUN curl https://github.com/hyperledger/fabric/releases/download/v2.3.2/hyperledger-fabric-linux-amd64-2.3.2.tar.gz -L --output hyperledger-fabric-linux-amd64-2.3.2.tar.gz RUN tar -zxf hyperledger-fabric-linux-amd64-2.3.2.tar.gz -ENV FABRIC_CFG_PATH /tmp/fabric/config/ +ENV FABRIC_CFG_PATH=/tmp/fabric/config/ RUN ./bin/peer lifecycle chaincode package /firefly/smart_contracts/fabric/firefly-go/firefly_fabric.tar.gz --path /firefly/smart_contracts/fabric/firefly-go --lang golang --label firefly_1.0 +# Solidity Builder FROM $SOLIDITY_BUILDER_TAG AS solidity-builder WORKDIR /firefly/solidity_firefly -RUN chgrp -R 0 /firefly \ - && chmod -R g+rwX /firefly +RUN chgrp -R 0 /firefly && chmod -R g+rwX /firefly ADD --chown=1001:0 smart_contracts/ethereum/solidity_firefly/ . USER 1001 RUN mkdir -p build/contracts \ @@ -54,7 +60,8 @@ RUN mkdir -p build/contracts \ && cd ../build/contracts \ && mv combined.json Firefly.json -FROM alpine:3.19 AS SBOM +# SBOM +FROM alpine:3.19 AS sbom WORKDIR / ADD . /SBOM RUN apk add --no-cache curl @@ -62,13 +69,14 @@ RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/ RUN trivy fs --format spdx-json --output /sbom.spdx.json /SBOM RUN trivy sbom /sbom.spdx.json --severity UNKNOWN,HIGH,CRITICAL --exit-code 1 +# Final executable build FROM $BASE_TAG ARG UI_TAG ARG UI_RELEASE RUN apk add --update --no-cache \ sqlite=3.44.2-r0 \ - postgresql16-client=16.3-r0 \ - curl=8.9.0-r0 \ + postgresql16-client=16.4-r0 \ + curl=8.9.1-r1 \ jq=1.7.1-r0 WORKDIR /firefly RUN chgrp -R 0 /firefly \ @@ -83,10 +91,10 @@ COPY --from=firefly-builder --chown=1001:0 /firefly/firefly ./firefly COPY --from=firefly-builder --chown=1001:0 /firefly/db ./db COPY --from=solidity-builder --chown=1001:0 /firefly/solidity_firefly/build/contracts ./contracts COPY --from=fabric-builder --chown=1001:0 /firefly/smart_contracts/fabric/firefly-go/firefly_fabric.tar.gz ./contracts/firefly_fabric.tar.gz -ENV UI_RELEASE https://github.com/hyperledger/firefly-ui/releases/download/$UI_TAG/$UI_RELEASE.tgz +ENV UI_RELEASE=https://github.com/hyperledger/firefly-ui/releases/download/$UI_TAG/$UI_RELEASE.tgz RUN mkdir /firefly/frontend \ && curl -sLo - $UI_RELEASE | tar -C /firefly/frontend -zxvf - -COPY --from=SBOM /sbom.spdx.json /sbom.spdx.json +COPY --from=sbom /sbom.spdx.json /sbom.spdx.json RUN ln -s /firefly/firefly /usr/bin/firefly USER 1001 ENTRYPOINT [ "firefly" ] diff --git a/doc-site/docs/contributors/dev_environment_setup.md b/doc-site/docs/contributors/dev_environment_setup.md index c4385d81e8..850d91c0e5 100644 --- a/doc-site/docs/contributors/dev_environment_setup.md +++ b/doc-site/docs/contributors/dev_environment_setup.md @@ -12,7 +12,7 @@ This guide will walk you through setting up your machine for contributing to Fir You will need a few prerequisites set up on your machine before you can build FireFly from source. We recommend doing development on macOS, Linux, or WSL 2.0. -- [Go 1.21](https://golang.org/dl/) +- [Go 1.22](https://golang.org/dl/) - make - GCC - openssl diff --git a/doc-site/docs/gettingstarted/firefly_cli.md b/doc-site/docs/gettingstarted/firefly_cli.md index 9d00732537..bb26fd574c 100644 --- a/doc-site/docs/gettingstarted/firefly_cli.md +++ b/doc-site/docs/gettingstarted/firefly_cli.md @@ -24,24 +24,34 @@ In order to run the FireFly CLI, you will need a few things installed on your de There are several ways to install the FireFly CLI. The easiest way to get up and running with the FireFly CLI is to download a pre-compiled binary of the latest release. -### Download the package for your OS +### Install via Binary Package Download -Go to the [latest release page](https://github.com/hyperledger/firefly-cli/releases/latest) and download the package for your OS and CPU architecture. +Download the package for your OS by navigating to the [latest release page](https://github.com/hyperledger/firefly-cli/releases/latest) and downloading the appropriate package for your OS and architecture. -### Extract the binary and move it to `/usr/bin/local` +#### Unpack and Install the Binary + + +Assuming you downloaded the package from GitHub into your `Downloads` directory, run the following command to extract the binary and move it to your system path: -Assuming you downloaded the package from GitHub into your `Downloads` directory, run the following command: ``` sudo tar -zxf ~/Downloads/firefly-cli_*.tar.gz -C /usr/local/bin ff && rm ~/Downloads/firefly-cli_*.tar.gz ``` -If you downloaded the package from GitHub into a different directory, you will need to change the `tar` command above to wherever the `firefly-cli_*.tar.gz` file is located. +If you downloaded the package into a different directory, adjust the command to point to the correct location of the `firefly-cli_*.tar.gz` file. -### macOSUsers +#### macOSUsers > **NOTE**: On recent versions of macOS, default security settings will prevent the FireFly CLI binary from running, because it was downloaded from the internet. You will need to [allow the FireFly CLI in System Preferences](https://github.com/hyperledger/firefly-cli/blob/main/docs/mac_help.md), before it will run. +### Install via Homebrew (macOS) + +You can also install the FireFly CLI using Homebrew: + +``` +brew install firefly +``` + ### Alternative installation method: Install via Go If you have a local Go development environment, and you have included `${GOPATH}/bin` in your path, you could also use Go to install the FireFly CLI by running: diff --git a/doc-site/docs/reference/config.md b/doc-site/docs/reference/config.md index 72353252db..82b7f3ef43 100644 --- a/doc-site/docs/reference/config.md +++ b/doc-site/docs/reference/config.md @@ -291,15 +291,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## events.webhooks.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## events.webhooks.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -343,11 +353,14 @@ title: Configuration Reference |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -420,11 +433,14 @@ title: Configuration Reference |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -490,11 +506,14 @@ title: Configuration Reference |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -620,15 +639,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.blockchain[].ethereum.addressResolver.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.blockchain[].ethereum.addressResolver.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -686,15 +715,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.blockchain[].ethereum.ethconnect.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.blockchain[].ethereum.ethconnect.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -748,15 +787,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.blockchain[].ethereum.fftm.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.blockchain[].ethereum.fftm.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -815,15 +864,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.blockchain[].fabric.fabconnect.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.blockchain[].fabric.fabconnect.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -883,15 +942,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.blockchain[].tezos.addressResolver.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.blockchain[].tezos.addressResolver.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -947,15 +1016,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.blockchain[].tezos.tezosconnect.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.blockchain[].tezos.tezosconnect.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -1076,15 +1155,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.dataexchange[].ffdx.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.dataexchange[].ffdx.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -1152,15 +1241,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.sharedstorage[].ipfs.api.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.sharedstorage[].ipfs.api.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -1202,15 +1301,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.sharedstorage[].ipfs.gateway.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.sharedstorage[].ipfs.gateway.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -1277,15 +1386,25 @@ title: Configuration Reference |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## plugins.tokens[].fftokens.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## plugins.tokens[].fftokens.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -1346,11 +1465,14 @@ title: Configuration Reference |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` diff --git a/doc-site/docs/reference/types/_includes/blockchainevent_description.md b/doc-site/docs/reference/types/_includes/blockchainevent_description.md index 67d37f978e..85546259ea 100644 --- a/doc-site/docs/reference/types/_includes/blockchainevent_description.md +++ b/doc-site/docs/reference/types/_includes/blockchainevent_description.md @@ -25,8 +25,8 @@ the blockchain plugins to try and create some consistency. An example `protocolId` string is: `000000000041/000020/000003` - `000000000041` - this is the block number -- `000020` - this is the transaction index within that block -- `000003` - this is the event (/log) index within that transaction +- `000020` - this is the **transaction** index within that **block** +- `000003` - this is the **event (/log)** index within that **block** The string is alphanumerically sortable as a plain string; diff --git a/doc-site/docs/reference/types/_includes/ffbigint_description.md b/doc-site/docs/reference/types/_includes/ffbigint_description.md index 497b545b16..e9b6762446 100644 --- a/doc-site/docs/reference/types/_includes/ffbigint_description.md +++ b/doc-site/docs/reference/types/_includes/ffbigint_description.md @@ -7,5 +7,49 @@ strings (with base 10). On input you can provide JSON string (string with an `0x` prefix are parsed at base 16), or a JSON number. -Be careful when using JSON numbers, that the largest -number that is safe to transfer using a JSON number is 2^53 - 1. \ No newline at end of file +## Maximum size of numbers in versions of FireFly up to `v1.3.1` + +In versions of FireFly up to and including `v1.3.1`, be careful when using large JSON numbers. The largest number that is safe to transfer using a JSON number is 2^53 - 1 and it is +possible to receive errors from the transaction manager, or for precision to be silently lost when passing numeric parameters larger than that. It is recommended to pass large numbers as strings to avoid loss of precision. + +## Maximum size of numbers in versions of FireFly `v1.3.2` and higher + +In FireFly `v1.3.2` support was added for 256-bit precision JSON numbers. Some application frameworks automatically serialize large JSON numbers to a string which FireFly already supports, but there is no upper limit +to the size of a number that can be represented in JSON. FireFly now supports much larger JSON numbers, up to 256-bit precision. For example the following input parameter to a contract constructor is now supported: + +``` + ... + "definition": [{ + "inputs": [ + { + "internalType":" uint256", + "name": "x", + "type": "uint256" + } + ], + "outputs":[], + "type":"constructor" + }], + "params": [ 10000000000000000000000000 ] + ... +``` + +Some application frameworks seralize large numbers in scientific notation e.g. `1e+25`. FireFly `v1.3.2` added supported for handling scientific numbers in parameters. This removes the need to change an application +that uses this number format. For example the following input parameter to a contract constructor is now supported: + +``` + ... + "definition": [{ + "inputs": [ + { + "internalType":" uint256", + "name": "x", + "type": "uint256" + } + ], + "outputs":[], + "type":"constructor" + }], + "params": [ 1e+25 ] + ... +``` diff --git a/doc-site/docs/releasenotes/index.md b/doc-site/docs/releasenotes/index.md index 40b349b7a2..9e8eddb499 100644 --- a/doc-site/docs/releasenotes/index.md +++ b/doc-site/docs/releasenotes/index.md @@ -4,6 +4,18 @@ title: Release Notes [Full release notes](https://github.com/hyperledger/firefly/releases) +## [v1.3.2 - Oct 3, 2024](https://github.com/hyperledger/firefly/releases/tag/v1.3.2) + +What's New: + +- Support for JSON numbers larger than `2^53-1` + - See [FFBigInt](../reference/types/simpletypes.md#ffbigint) for detailed explanation + - Support added to FireFly core, including the UI, FireFly Transaction Manager, and FireFly EVMConnect +- Ability to install FireFly CLI with Brew for MacOS users + See [Brew](../gettingstarted/firefly_cli.md#install-via-homebrew-macOS) +- Miscellaneous bug fixes and minor improvements +- FireFly has been upgraded to use Go 1.22 + ## [v1.3.1 - Aug 5, 2024](https://github.com/hyperledger/firefly/releases/tag/v1.3.1) What's New: diff --git a/doc-site/docs/tutorials/custom_contracts/index.md b/doc-site/docs/tutorials/custom_contracts/index.md index 449099a1e0..70a4467cd0 100644 --- a/doc-site/docs/tutorials/custom_contracts/index.md +++ b/doc-site/docs/tutorials/custom_contracts/index.md @@ -12,23 +12,32 @@ FireFly's unified API creates a consistent application experience regardless of FireFly defines the following constructs to support custom smart contracts: -- **Contract Interface**: FireFly defines a common, blockchain agnostic way to describe smart contracts. This is referred to as a Contract Interface. A contract interface is written in the FireFly Interface (FFI) format. It is a simple JSON document that has a name, a namespace, a version, a list of methods, and a list of events. +### Contract Interface + +FireFly defines a common, blockchain agnostic way to describe smart contracts. This is referred to as a Contract Interface. A contract interface is written in the FireFly Interface (FFI) format. It is a simple JSON document that has a name, a namespace, a version, a list of methods, and a list of events. For more details, you can also have a look at the [Reference page for the FireFly Interface Format](../../reference/firefly_interface_format.md). -For blockchains that offer a DSL describing the smart contract interface, such as Ethereum's ABI (Application Binary Interface), FireFly offers a convenience tool to convert the DSL into the FFI format. +For blockchains that offer a DSL describing the smart contract interface, such as Ethereum's ABI (Application Binary Interface), FireFly offers an API to [convert the DSL into the FFI format](../custom_contracts/ethereum.md#the-firefly-interface-format). + > **NOTE**: Contract interfaces are scoped to a namespace. Within a namespace each contract interface must have a unique name and version combination. The same name and version combination can exist in _different_ namespaces simultaneously. -- **HTTP API**: Based on a Contract Interface, FireFly further defines an HTTP API for the smart contract, which is complete with an OpenAPI Specification and the Swagger UI. An HTTP API defines an `/invoke` root path to submit transactions, and a `/query` root path to send query requests to read the state back out. +### HTTP API + +Based on a Contract Interface, FireFly further defines an HTTP API for the smart contract, which is complete with an OpenAPI Specification and the Swagger UI. An HTTP API defines an `/invoke` root path to submit transactions, and a `/query` root path to send query requests to read the state back out. -How the invoke vs. query requests get interpreted into the native blockchain requests are specific to the blockchain's connector. For instance, the Ethereum connector translates `/invoke` calls to `eth_sendTransaction` JSON-RPC requests, while `/query` calls are translated into `eth_call` JSON-RPC requests. One the other hand, the Fabric connector translates `/invoke` calls to the multiple requests required to submit a transaction to a Fabric channel (which first collects endorsements from peer nodes, and then sends the assembled transaction payload to an orderer, for details please refer to the Fabric documentation). +How the invoke vs. query requests get interpreted into the native blockchain requests are specific to the blockchain's connector. For instance, the Ethereum connector translates `/invoke` calls to `eth_sendTransaction` JSON-RPC requests, while `/query` calls are translated into `eth_call` JSON-RPC requests. On the other hand, the Fabric connector translates `/invoke` calls to the multiple requests required to submit a transaction to a Fabric channel (which first collects endorsements from peer nodes, and then sends the assembled transaction payload to an orderer, for details please refer to the Fabric documentation). -- **Blockchain Event Listener**: Regardless of a blockchain's specific design, transaction processing are always asynchronous. This means a transaction is submitted to the network, at which point the submitting client gets an acknowledgement that it has been accepted for further processing. The client then listens for notifications by the blockchain when the transaction gets committed to the blockchain's ledger. +### Blockchain Event Listener + +Regardless of a blockchain's specific design, transaction processing are always asynchronous. This means a transaction is submitted to the network, at which point the submitting client gets an acknowledgement that it has been accepted for further processing. The client then listens for notifications by the blockchain when the transaction gets committed to the blockchain's ledger. FireFly defines event listeners to allow the client application to specify the relevant blockchain events to keep track of. A client application can then receive the notifications from FireFly via an event subscription. -- **Event Subscription**: While an event listener tells FireFly to keep track of certain events emitted by the blockchain, an event subscription tells FireFly to relay those events to the client application. Each subscriptions represents a stream of events that can be delivered to a listening client with various modes of delivery with at-least-once delivery guarantee. +### Event Subscription + +An event listener in FireFly tracks specific blockchain events, while an event subscription directs FireFly to send those events to the client application. Each subscription creates a stream of events that can be delivered to the client with various delivery options, ensuring an at-least-once delivery guarantee. This is exactly the same as listening for any other events from FireFly. For more details on how Subscriptions work in FireFly you can read the [Getting Started guide to Listen for events](../events.md). diff --git a/ffconfig/main_test.go b/ffconfig/main_test.go new file mode 100644 index 0000000000..f7b1365613 --- /dev/null +++ b/ffconfig/main_test.go @@ -0,0 +1,106 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" +) + +var configPath string = "../test/data/config/firefly.core.yaml" + +func TestMainFail(t *testing.T) { + // Run the crashing code when FLAG is set + if os.Getenv("FLAG") == "1" { + main() + return + } + // Run the test in a subprocess + cmd := exec.Command(os.Args[0], "-test.run=TestMainFail") + cmd.Env = append(os.Environ(), "FLAG=1") + err := cmd.Run() + + // Cast the error as *exec.ExitError and compare the result + e, ok := err.(*exec.ExitError) + expectedErrorString := "exit status 1" + assert.Equal(t, true, ok) + assert.Equal(t, expectedErrorString, e.Error()) +} + +func TestConfigMigrateRootCmdErrorNoArgs(t *testing.T) { + rootCmd.SetArgs([]string{}) + defer rootCmd.SetArgs([]string{}) + err := rootCmd.Execute() + assert.Error(t, err) + assert.Regexp(t, "a command is required", err) +} + +func TestConfigMigrateCmdMissingConfig(t *testing.T) { + rootCmd.SetArgs([]string{"migrate"}) + defer rootCmd.SetArgs([]string{}) + err := rootCmd.Execute() + assert.Error(t, err) + assert.Regexp(t, "no such file or directory", err) +} + +func TestConfigMigrateCmd(t *testing.T) { + rootCmd.SetArgs([]string{"migrate", "-f", configPath}) + defer rootCmd.SetArgs([]string{}) + err := rootCmd.Execute() + assert.NoError(t, err) +} + +func TestMain(t *testing.T) { + // Run the exiting code when FLAG is set + if os.Getenv("FLAG") == "0" { + rootCmd.SetArgs([]string{"migrate", "-f", configPath}) + main() + return + } + + // Run the test in a subprocess + cmd := exec.Command(os.Args[0], "-test.run=TestMain") + cmd.Env = append(os.Environ(), "FLAG=0") + err := cmd.Run() + + // Cast the error as *exec.ExitError and compare the result + _, ok := err.(*exec.ExitError) + assert.Equal(t, false, ok) +} + +func TestConfigMigrateCmdWriteOutput(t *testing.T) { + tmpDir, err := os.MkdirTemp(os.TempDir(), "out") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + rootCmd.SetArgs([]string{"migrate", "-f", configPath, "-o", fmt.Sprintf(tmpDir, "out.config")}) + defer rootCmd.SetArgs([]string{}) + err = rootCmd.Execute() + assert.NoError(t, err) +} + +func TestConfigMigrateCmdBadVersion(t *testing.T) { + rootCmd.SetArgs([]string{"migrate", "-f", configPath, "--from", "badversion"}) + defer rootCmd.SetArgs([]string{}) + err := rootCmd.Execute() + assert.Error(t, err) + assert.Regexp(t, "bad 'from' version", err) +} diff --git a/go.mod b/go.mod index 2d6c72a2a3..d9ed32faa2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/hyperledger/firefly -go 1.21 +go 1.22 + +toolchain go1.22.7 require ( blockwatch.cc/tzgo v1.17.1 @@ -15,8 +17,8 @@ require ( github.com/golang-migrate/migrate/v4 v4.17.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 - github.com/hyperledger/firefly-common v1.4.6 - github.com/hyperledger/firefly-signer v1.1.12 + github.com/hyperledger/firefly-common v1.4.11 + github.com/hyperledger/firefly-signer v1.1.17 github.com/jarcoal/httpmock v1.2.0 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.19 @@ -87,6 +89,7 @@ require ( golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index cf382953b7..ab3abce00b 100644 --- a/go.sum +++ b/go.sum @@ -77,10 +77,10 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hyperledger/firefly-common v1.4.6 h1:qqXoSaRml3WjUnWcWxrrXs5AIOWa+UcMXLCF8yEa4Pk= -github.com/hyperledger/firefly-common v1.4.6/go.mod h1:jkErZdQmC9fsAJZQO427tURdwB9iiW+NMUZSqS3eBIE= -github.com/hyperledger/firefly-signer v1.1.12 h1:wv1cq4HV60G2MQdmIEkYkywoxUSkaH0ss95Nn3ohdEk= -github.com/hyperledger/firefly-signer v1.1.12/go.mod h1:4MW7bcTqPsS7SKwANJZRL030cJRsHcpB/a+06wUROvc= +github.com/hyperledger/firefly-common v1.4.11 h1:WKv2hQuNpS7yP51THxzpzrqU3jkln23C9vq5iminzBk= +github.com/hyperledger/firefly-common v1.4.11/go.mod h1:E7w/RxNtVnX52WXLQW9f2xVAgZnW70voZeE9sZrx/q0= +github.com/hyperledger/firefly-signer v1.1.17 h1:JV38nNeCS/K31kPDk5mwnoqw6SoulcYF+12JW8a3/Mw= +github.com/hyperledger/firefly-signer v1.1.17/go.mod h1:HDaDdht94JypRTunRGrcPL5Pvxfh4yigjatTrie5JUI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= diff --git a/go.work b/go.work index 28b0fad5d2..80c90d39e5 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,6 @@ -go 1.21 +go 1.22 + +toolchain go1.22.7 use ( . diff --git a/go.work.sum b/go.work.sum index ee356555a1..e0f19a971d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -253,6 +253,7 @@ github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYi github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/hyperledger/firefly-common v1.4.11 h1:WKv2hQuNpS7yP51THxzpzrqU3jkln23C9vq5iminzBk= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= diff --git a/internal/blockchain/bifactory/factory_test.go b/internal/blockchain/bifactory/factory_test.go new file mode 100644 index 0000000000..a2b493c796 --- /dev/null +++ b/internal/blockchain/bifactory/factory_test.go @@ -0,0 +1,60 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bifactory + +import ( + "context" + "testing" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPluginUnknown(t *testing.T) { + ctx := context.Background() + _, err := GetPlugin(ctx, "foo") + assert.Error(t, err) + assert.Regexp(t, "FF10110", err) +} + +func TestGetPluginEthereum(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "ethereum") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +func TestGetPluginFabric(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "fabric") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +func TestGetPluginTezos(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "tezos") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +var root = config.RootSection("di") + +func TestInitConfig(t *testing.T) { + conf := root.SubArray("plugins") + InitConfig(conf) +} diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go index ab45cf18e8..753ab7e54f 100644 --- a/internal/contracts/manager.go +++ b/internal/contracts/manager.go @@ -160,7 +160,12 @@ func NewContractManager(ctx context.Context, ns string, di database.Plugin, bi b // cause recreation of all the listeners (noting that listeners that were specified to start // from latest, will start from the new latest rather than replaying from the block they // started from before they were deleted). - return cm, cm.verifyListeners(ctx) + err = cm.verifyListeners(ctx) + if err != nil { + return nil, err + } + + return cm, nil } func (cm *contractManager) Name() string { diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go index d022d3157f..e936fb28d2 100644 --- a/internal/contracts/manager_test.go +++ b/internal/contracts/manager_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -98,6 +98,33 @@ func TestName(t *testing.T) { assert.Equal(t, "ContractManager", cm.Name()) } +func TestNewContractManagerVerifyListenersFails(t *testing.T) { + mdi := &databasemocks.Plugin{} + mdm := &datamocks.Manager{} + mbm := &broadcastmocks.Manager{} + mpm := &privatemessagingmocks.Manager{} + mbp := &batchmocks.Manager{} + mim := &identitymanagermocks.Manager{} + mbi := &blockchainmocks.Plugin{} + mom := &operationmocks.Manager{} + txw := &txwritermocks.Writer{} + cmi := &cachemocks.Manager{} + msa := &syncasyncmocks.Bridge{} + + ctx := context.Background() + + cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil) + txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi) + mbi.On("GetFFIParamValidator", mock.Anything).Return(nil, nil) + mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything) + mbi.On("Name").Return("mockblockchain").Maybe() + mdi.On("GetContractListeners", mock.Anything, "ns1", mock.Anything).Return(nil, nil, fmt.Errorf("KABOOM!")).Once() + + cm, err := NewContractManager(context.Background(), "ns1", mdi, mbi, mdm, mbm, mpm, mbp, mim, mom, txHelper, txw, msa, cmi) + assert.Nil(t, cm) + assert.NotNil(t, err) +} + func TestNewContractManagerFFISchemaLoaderFail(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} diff --git a/internal/contracts/operations.go b/internal/contracts/operations.go index c308aa51ad..cbb9c63e3f 100644 --- a/internal/contracts/operations.go +++ b/internal/contracts/operations.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -17,6 +17,7 @@ package contracts import ( + "bytes" "context" "encoding/json" @@ -38,7 +39,9 @@ type blockchainContractDeployData struct { func addBlockchainReqInputs(op *core.Operation, req interface{}) (err error) { var reqJSON []byte if reqJSON, err = json.Marshal(req); err == nil { - err = json.Unmarshal(reqJSON, &op.Input) + d := json.NewDecoder(bytes.NewReader(reqJSON)) + d.UseNumber() + err = d.Decode(&op.Input) } return err } diff --git a/internal/contracts/operations_test.go b/internal/contracts/operations_test.go index bd425dabc1..b3dcb9b4ff 100644 --- a/internal/contracts/operations_test.go +++ b/internal/contracts/operations_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -16,7 +16,9 @@ package contracts import ( + "bytes" "context" + "encoding/json" "fmt" "testing" @@ -35,6 +37,21 @@ import ( "github.com/stretchr/testify/mock" ) +const sampleRequestLargeNumberInput = `{ + "location": { + "address": "0x1111" + }, + "key": "0x123", + "method": { + "name": "set", + "params": null, + "returns": null + }, + "input": { + "value": 10000000000000000000000000001 + } +}` + func reqWithMessage(msgType core.MessageType) *core.ContractCallRequest { return &core.ContractCallRequest{ Key: "0x123", @@ -60,6 +77,7 @@ func reqWithMessage(msgType core.MessageType) *core.ContractCallRequest { } func TestPrepareAndRunBlockchainInvoke(t *testing.T) { + cm := newTestContractManager() op := &core.Operation{ @@ -101,6 +119,46 @@ func TestPrepareAndRunBlockchainInvoke(t *testing.T) { mbi.AssertExpectations(t) } +func TestPrepareAndRunBlockchainInvokeLargeNumberInput(t *testing.T) { + + cm := newTestContractManager() + + var req core.ContractCallRequest + d := json.NewDecoder(bytes.NewReader([]byte(sampleRequestLargeNumberInput))) + d.UseNumber() + err := d.Decode(&req) + assert.NoError(t, err) + + op := &core.Operation{ + Type: core.OpTypeBlockchainInvoke, + ID: fftypes.NewUUID(), + Namespace: "ns1", + } + + err = addBlockchainReqInputs(op, req) + assert.NoError(t, err) + + mbi := cm.blockchain.(*blockchainmocks.Plugin) + opaqueData := "anything" + mbi.On("ParseInterface", context.Background(), mock.MatchedBy(func(method *fftypes.FFIMethod) bool { + return method.Name == req.Method.Name + }), req.Errors).Return(opaqueData, nil) + mbi.On("InvokeContract", context.Background(), "ns1:"+op.ID.String(), "0x123", mock.MatchedBy(func(loc *fftypes.JSONAny) bool { + return loc.String() == req.Location.String() + }), opaqueData, req.Input, req.Options, (*blockchain.BatchPin)(nil)).Return(false, nil) + + po, err := cm.PrepareOperation(context.Background(), op) + assert.NoError(t, err) + assert.Equal(t, &req, po.Data.(txcommon.BlockchainInvokeData).Request) + + _, phase, err := cm.RunOperation(context.Background(), po) + + assert.Equal(t, core.OpPhasePending, phase) + assert.NoError(t, err) + + mbi.AssertExpectations(t) +} + func TestPrepareAndRunBlockchainInvokeRejected(t *testing.T) { cm := newTestContractManager() diff --git a/internal/coremsgs/es/es_struct_descriptions.go b/internal/coremsgs/es/es_struct_descriptions.go deleted file mode 100644 index cc585b6669..0000000000 --- a/internal/coremsgs/es/es_struct_descriptions.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright © 2022 Kaleido, Inc. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package es - -import ( - "github.com/hyperledger/firefly-common/pkg/i18n" - "golang.org/x/text/language" -) - -//revive:disable - -/* -This file contains the field level descriptions that are used in -OpenAPI Spec generation. Each struct field that wants to use one of these -needs to have an ffstruct tag on it, indicating the name of the struct. -That will be combined with the JSON field name (note, it is not the GO -field name, but the JSON serialized name), separated by a "." This is the -key used to lookup the translation below. If it is not found, the description -is left blank in the OpenAPI spec - -Example: -// message.go -type Message struct { - Header MessageHeader `ffstruct:"Message" json:"header"` - -// en_translations_descriptions.go -MessageHeader = ffm("Message.header", "The message header") - -*/ - -var ffm = func(key, translation string) i18n.MessageKey { - return i18n.FFM(language.Spanish, key, translation) -} - -var ( - // MessageHeader field descriptions - MessageHeaderID = ffm("MessageHeader.id", "El UUID del mensaje. Único para cada mensaje") -) diff --git a/internal/database/difactory/factory_test.go b/internal/database/difactory/factory_test.go new file mode 100644 index 0000000000..f334df0875 --- /dev/null +++ b/internal/database/difactory/factory_test.go @@ -0,0 +1,53 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package difactory + +import ( + "context" + "testing" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPluginUnknown(t *testing.T) { + ctx := context.Background() + _, err := GetPlugin(ctx, "foo") + assert.Error(t, err) + assert.Regexp(t, "FF10122", err) +} + +func TestGetPluginPostgres(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "postgres") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +func TestGetPluginSQLite(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "sqlite3") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +var root = config.RootSection("di") + +func TestInitConfig(t *testing.T) { + conf := root.SubArray("plugins") + InitConfig(conf) +} diff --git a/internal/dataexchange/dxfactory/factory_test.go b/internal/dataexchange/dxfactory/factory_test.go new file mode 100644 index 0000000000..989570a105 --- /dev/null +++ b/internal/dataexchange/dxfactory/factory_test.go @@ -0,0 +1,46 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dxfactory + +import ( + "context" + "testing" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPluginUnknown(t *testing.T) { + ctx := context.Background() + _, err := GetPlugin(ctx, "foo") + assert.Error(t, err) + assert.Regexp(t, "FF10213", err) +} + +func TestGetPlugin(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "ffdx") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +var root = config.RootSection("di") + +func TestInitConfig(t *testing.T) { + conf := root.SubArray("plugins") + InitConfig(conf) +} diff --git a/internal/definitions/sender.go b/internal/definitions/sender.go index 3d998c3a86..70f3664095 100644 --- a/internal/definitions/sender.go +++ b/internal/definitions/sender.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -93,8 +93,12 @@ func NewDefinitionSender(ctx context.Context, ns *core.Namespace, multiparty boo tokenBroadcastNames: tokenBroadcastNames, } dh, err := newDefinitionHandler(ctx, ns, multiparty, di, bi, dx, dm, im, am, cm, reverseMap(tokenBroadcastNames)) + if err != nil { + return nil, nil, err + } + ds.handler = dh - return ds, dh, err + return ds, dh, nil } // reverseMap reverses the key/values of a given map diff --git a/internal/definitions/sender_test.go b/internal/definitions/sender_test.go index b7a8f41499..261c5c3579 100644 --- a/internal/definitions/sender_test.go +++ b/internal/definitions/sender_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -105,6 +105,26 @@ func TestInitSenderFail(t *testing.T) { assert.Regexp(t, "FF10128", err) } +func TestNewDefinitionSenderHandlerThrows(t *testing.T) { + mdi := &databasemocks.Plugin{} + mbi := &blockchainmocks.Plugin{} + mdx := &dataexchangemocks.Plugin{} + mbm := &broadcastmocks.Manager{} + mim := &identitymanagermocks.Manager{} + mdm := &datamocks.Manager{} + mcm := &contractmocks.Manager{} + + tokenBroadcastNames := make(map[string]string) + tokenBroadcastNames["connector1"] = "remote1" + + ctx := context.Background() + ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"} + ds, dh, err := NewDefinitionSender(ctx, ns, false, mdi, mbi, mdx, mbm, mim, mdm, nil, mcm, tokenBroadcastNames) + assert.Nil(t, ds) + assert.Nil(t, dh) + assert.NotNil(t, err) +} + func TestName(t *testing.T) { ds := newTestDefinitionSender(t) defer ds.cleanup(t) diff --git a/internal/events/blockchain_event.go b/internal/events/blockchain_event.go index 8708c9cd31..337cadcff2 100644 --- a/internal/events/blockchain_event.go +++ b/internal/events/blockchain_event.go @@ -71,20 +71,21 @@ func (em *eventManager) getChainListenerByProtocolIDCached(ctx context.Context, return l, nil } -func (em *eventManager) maybePersistBlockchainEvent(ctx context.Context, chainEvent *core.BlockchainEvent, listener *core.ContractListener) error { +// handleBlockchainBatchPinEvent handles a blockchain event, returning true if the event was created, false if it was a duplicate along with an error if any failures occur +func (em *eventManager) maybePersistBlockchainEvent(ctx context.Context, chainEvent *core.BlockchainEvent, listener *core.ContractListener) (bool, error) { existing, err := em.txHelper.InsertOrGetBlockchainEvent(ctx, chainEvent) if err != nil { - return err + return false, err } if existing != nil { log.L(ctx).Debugf("Ignoring duplicate blockchain event %s", chainEvent.ProtocolID) // Return the ID of the existing event chainEvent.ID = existing.ID - return nil + return false, nil } topic := em.getTopicForChainListener(listener) ffEvent := core.NewEvent(core.EventTypeBlockchainEventReceived, chainEvent.Namespace, chainEvent.ID, chainEvent.TX.ID, topic) - return em.database.InsertEvent(ctx, ffEvent) + return true, em.database.InsertEvent(ctx, ffEvent) } func (em *eventManager) getChainListenerCached(cacheKey string, getter func() (*core.ContractListener, error)) (*core.ContractListener, error) { diff --git a/internal/events/blockchain_event_test.go b/internal/events/blockchain_event_test.go index 67c293a5cd..4823eff538 100644 --- a/internal/events/blockchain_event_test.go +++ b/internal/events/blockchain_event_test.go @@ -151,6 +151,7 @@ func TestContractEventWrongNS(t *testing.T) { } +// TODO: Add test case for event not existing func TestPersistBlockchainEventDuplicate(t *testing.T) { em := newTestEventManager(t) defer em.cleanup(t) @@ -173,9 +174,10 @@ func TestPersistBlockchainEventDuplicate(t *testing.T) { em.mth.On("InsertOrGetBlockchainEvent", mock.Anything, ev). Return(&core.BlockchainEvent{ID: existingID}, nil) - err := em.maybePersistBlockchainEvent(em.ctx, ev, nil) + created, err := em.maybePersistBlockchainEvent(em.ctx, ev, nil) assert.NoError(t, err) assert.Equal(t, existingID, ev.ID) + assert.False(t, created) } diff --git a/internal/events/eifactory/factory_test.go b/internal/events/eifactory/factory_test.go new file mode 100644 index 0000000000..fb964793e0 --- /dev/null +++ b/internal/events/eifactory/factory_test.go @@ -0,0 +1,59 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package eifactory + +import ( + "context" + "testing" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPluginUnknown(t *testing.T) { + ctx := context.Background() + _, err := GetPlugin(ctx, "foo") + assert.Error(t, err) + assert.Regexp(t, "FF10172", err) +} + +func TestGetPluginWebSockets(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "websockets") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +func TestGetPluginWebHooks(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "webhooks") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +func TestGetPluginEvents(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "system") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +var root = config.RootSection("di") + +func TestInitConfig(t *testing.T) { + InitConfig(root) +} diff --git a/internal/events/event_manager_test.go b/internal/events/event_manager_test.go index f634108c42..ac06c88c08 100644 --- a/internal/events/event_manager_test.go +++ b/internal/events/event_manager_test.go @@ -681,7 +681,7 @@ func TestEventFilterOnSubscriptionMatchesEventType(t *testing.T) { filteredEvents, _ = em.FilterHistoricalEventsOnSubscription(context.Background(), events, subscription) assert.NotNil(t, filteredEvents) assert.Equal(t, 1, len(filteredEvents)) - + listenerUuid := fftypes.NewUUID() events[0].Event.Topic = "" diff --git a/internal/events/token_pool_created.go b/internal/events/token_pool_created.go index dec69f4468..2fb4c1ea2c 100644 --- a/internal/events/token_pool_created.go +++ b/internal/events/token_pool_created.go @@ -62,10 +62,13 @@ func (em *eventManager) confirmPool(ctx context.Context, pool *core.TokenPool, e Type: pool.TX.Type, BlockchainID: blockchainID, }) - if err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil); err != nil { + created, err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil) + if err != nil { return err } - em.emitBlockchainEventMetric(ev) + if created { + em.emitBlockchainEventMetric(ev) + } } if _, err := em.txHelper.PersistTransaction(ctx, pool.TX.ID, pool.TX.Type, blockchainID); err != nil { return err diff --git a/internal/events/tokens_approved.go b/internal/events/tokens_approved.go index f93b719681..272641f546 100644 --- a/internal/events/tokens_approved.go +++ b/internal/events/tokens_approved.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -97,10 +97,13 @@ func (em *eventManager) persistTokenApproval(ctx context.Context, approval *toke Type: approval.TX.Type, BlockchainID: approval.Event.BlockchainTXID, }) - if err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil); err != nil { + created, err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil) + if err != nil { return false, err } - em.emitBlockchainEventMetric(approval.Event) + if created { + em.emitBlockchainEventMetric(approval.Event) + } approval.BlockchainEvent = chainEvent.ID fb := database.TokenApprovalQueryFactory.NewFilter(ctx) diff --git a/internal/events/tokens_transferred.go b/internal/events/tokens_transferred.go index 74cccc89de..c4048611f3 100644 --- a/internal/events/tokens_transferred.go +++ b/internal/events/tokens_transferred.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -89,10 +89,13 @@ func (em *eventManager) persistTokenTransfer(ctx context.Context, transfer *toke Type: transfer.TX.Type, BlockchainID: transfer.Event.BlockchainTXID, }) - if err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil); err != nil { + created, err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil) + if err != nil { return false, err } - em.emitBlockchainEventMetric(transfer.Event) + if created { + em.emitBlockchainEventMetric(transfer.Event) + } transfer.BlockchainEvent = chainEvent.ID // This is a no-op if we've already persisted this token transfer diff --git a/internal/identity/iifactory/factory_test.go b/internal/identity/iifactory/factory_test.go new file mode 100644 index 0000000000..3110dbb65b --- /dev/null +++ b/internal/identity/iifactory/factory_test.go @@ -0,0 +1,46 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iifactory + +import ( + "context" + "testing" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPluginUnknown(t *testing.T) { + ctx := context.Background() + _, err := GetPlugin(ctx, "foo") + assert.Error(t, err) + assert.Regexp(t, "FF10212", err) +} + +func TestGetPlugin(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "onchain") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +var root = config.RootSection("di") + +func TestInitConfig(t *testing.T) { + conf := root.SubArray("plugins") + InitConfig(conf) +} diff --git a/internal/operations/manager.go b/internal/operations/manager.go index 65c3d0f8ef..27e67c5dc1 100644 --- a/internal/operations/manager.go +++ b/internal/operations/manager.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -234,10 +234,12 @@ func (om *operationsManager) RetryOperation(ctx context.Context, opID *fftypes.U var po *core.PreparedOperation var idempotencyKey core.IdempotencyKey err = om.database.RunAsGroup(ctx, func(ctx context.Context) error { - op, err = om.findLatestRetry(ctx, opID) + parent, err := om.findLatestRetry(ctx, opID) if err != nil { return err } + // Deep copy the operation so the parent ID will not get overwritten + op = parent.DeepCopy() tx, err := om.updater.txHelper.GetTransactionByIDCached(ctx, op.Transaction) if err != nil { diff --git a/internal/operations/manager_test.go b/internal/operations/manager_test.go index 4e5448c01e..626dc7292f 100644 --- a/internal/operations/manager_test.go +++ b/internal/operations/manager_test.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -346,9 +346,12 @@ func TestRetryOperationSuccess(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, len(info.SetOperations)) assert.Equal(t, "retry", info.SetOperations[0].Field) - val, err := info.SetOperations[0].Value.Value() + retryVal, err := info.SetOperations[0].Value.Value() assert.NoError(t, err) - assert.Equal(t, op.ID.String(), val) + // The retry value of the parent operation should be the new operation ID + assert.Equal(t, op.Retry.String(), retryVal.(string)) + // The parent ID should not change + assert.Equal(t, op.ID.String(), opID.String()) return true })).Return(true, nil) mdi.On("GetTransactionByID", mock.Anything, "ns1", txID).Return(&core.Transaction{ diff --git a/internal/sharedstorage/ssfactory/factory_test.go b/internal/sharedstorage/ssfactory/factory_test.go new file mode 100644 index 0000000000..5013d4cf45 --- /dev/null +++ b/internal/sharedstorage/ssfactory/factory_test.go @@ -0,0 +1,46 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ssfactory + +import ( + "context" + "testing" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPluginUnknown(t *testing.T) { + ctx := context.Background() + _, err := GetPlugin(ctx, "foo") + assert.Error(t, err) + assert.Regexp(t, "FF10134", err) +} + +func TestGetPlugin(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "ipfs") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +var root = config.RootSection("di") + +func TestInitConfig(t *testing.T) { + conf := root.SubArray("plugins") + InitConfig(conf) +} diff --git a/internal/tokens/tifactory/factory_test.go b/internal/tokens/tifactory/factory_test.go new file mode 100644 index 0000000000..f1d1229321 --- /dev/null +++ b/internal/tokens/tifactory/factory_test.go @@ -0,0 +1,46 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tifactory + +import ( + "context" + "testing" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPluginUnknown(t *testing.T) { + ctx := context.Background() + _, err := GetPlugin(ctx, "foo") + assert.Error(t, err) + assert.Regexp(t, "FF10272", err) +} + +func TestGetPlugin(t *testing.T) { + ctx := context.Background() + plugin, err := GetPlugin(ctx, "fftokens") + assert.NoError(t, err) + assert.NotNil(t, plugin) +} + +var root = config.RootSection("tokens") + +func TestInitConfig(t *testing.T) { + conf := root.SubArray("plugins") + InitConfig(conf) +} diff --git a/internal/txcommon/contract_inputs.go b/internal/txcommon/contract_inputs.go index 942ca66258..9b900f023c 100644 --- a/internal/txcommon/contract_inputs.go +++ b/internal/txcommon/contract_inputs.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -17,6 +17,7 @@ package txcommon import ( + "bytes" "context" "encoding/json" @@ -39,7 +40,9 @@ type BlockchainInvokeData struct { func RetrieveBlockchainInvokeInputs(ctx context.Context, op *core.Operation) (*core.ContractCallRequest, error) { var req core.ContractCallRequest s := op.Input.String() - if err := json.Unmarshal([]byte(s), &req); err != nil { + d := json.NewDecoder(bytes.NewReader([]byte(s))) + d.UseNumber() + if err := d.Decode(&req); err != nil { return nil, i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, s) } return &req, nil diff --git a/manifest.json b/manifest.json index bd063f42b1..a6285d4da1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,23 +1,23 @@ { "ethconnect": { "image": "ghcr.io/hyperledger/firefly-ethconnect", - "tag": "v3.3.1", - "sha": "4b78a0f88a06d2de1e6868b0cbeef44b9be622b7ef46b5a8d6ca381a36c1c6a5" + "tag": "v3.3.2", + "sha": "bdbfa05a0bc1610813ba0ef65b2c8f9f7df4a5ead5643a580e3ba5927747c2cf" }, "evmconnect": { "image": "ghcr.io/hyperledger/firefly-evmconnect", - "tag": "v1.3.14", - "sha": "7fbd5b236262b8f13ed43e1c691aad0ee31437f2db20e675d75864b568afe9ed" + "tag": "v1.3.18", + "sha": "11a782c778227d198c0bff2fc94ad7e52ceea5a9994728ba05e781541680c32d" }, "fabconnect": { "image": "ghcr.io/hyperledger/firefly-fabconnect", - "tag": "v0.9.20", - "sha": "e597be0db4dc34b455e2e001abf0f8e867874ae6984a60a3db91bce3f8fdc542" + "tag": "v0.9.21", + "sha": "394262c5888fb6647a7b8b1f3f8d652e1db941b67e3c68bccfbedfa53c4d874d" }, "tezosconnect": { "image": "ghcr.io/hyperledger/firefly-tezosconnect", - "tag": "v0.2.4", - "sha": "cbb0bef8ae8b5d4adffd564591ddab0e1ca68442c10183679ba207049e64a59e" + "tag": "v0.2.7", + "sha": "cee6ecda8208e24989a6db2c1a2dd1b0defb89363e7728571b8858e794a295be" }, "dataexchange-https": { "image": "ghcr.io/hyperledger/firefly-dataexchange-https", @@ -26,25 +26,25 @@ }, "tokens-erc1155": { "image": "ghcr.io/hyperledger/firefly-tokens-erc1155", - "tag": "v1.3.2", - "sha": "7bb27808ab2b4582775ca1a18bdc40f7db3a2de56c69c7778224a36105da111a" + "tag": "v1.3.3", + "sha": "5efbe76ce2c484d86e9e3cc2cfe3cee85d91ee8712b31dfc592a2de771749e78" }, "tokens-erc20-erc721": { "image": "ghcr.io/hyperledger/firefly-tokens-erc20-erc721", - "tag": "v1.3.2", - "sha": "c75699b05cb41c8950dcb1b1eed49f7beee910433ff78830df6db61dfc325812" + "tag": "v1.3.3", + "sha": "5cfa10432c1cd885f3cbe5934799d692ba7186013e03a6b92bd6aa53ddf4f414" }, "signer": { "image": "ghcr.io/hyperledger/firefly-signer", - "tag": "v1.1.13", - "sha": "9f4c29ea05eb111d958d8210601cdf876b4f70fbe443c080dbc7c0c16c7921e9" + "tag": "v1.1.17", + "sha": "4391674f66dadb42ef29f3ea0ac62b130bad041675ff25ad3f8c7c0c8eda3854" }, "build": { "firefly-builder": { - "image": "golang:1.21-alpine3.19" + "image": "golang:1.22-alpine3.19" }, "fabric-builder": { - "image": "golang:1.21", + "image": "golang:1.22", "platform": "linux/x86_64" }, "solidity-builder": { @@ -55,10 +55,10 @@ } }, "ui": { - "tag": "v1.3.0", - "release": "v1.3.0" + "tag": "v1.3.1", + "release": "v1.3.1" }, "cli": { - "tag": "v1.3.1" + "tag": "v1.3.2" } } diff --git a/pkg/core/operation.go b/pkg/core/operation.go index 5d8d098b8a..ff7a39607f 100644 --- a/pkg/core/operation.go +++ b/pkg/core/operation.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -74,6 +74,79 @@ func (op *Operation) IsTokenOperation() bool { return op.Type == OpTypeTokenActivatePool || op.Type == OpTypeTokenApproval || op.Type == OpTypeTokenCreatePool || op.Type == OpTypeTokenTransfer } +func (op *Operation) DeepCopy() *Operation { + cop := &Operation{ + Namespace: op.Namespace, + Type: op.Type, + Status: op.Status, + Plugin: op.Plugin, + Error: op.Error, + } + if op.ID != nil { + idCopy := *op.ID + cop.ID = &idCopy + } + if op.Transaction != nil { + txCopy := *op.Transaction + cop.Transaction = &txCopy + } + if op.Created != nil { + createdCopy := *op.Created + cop.Created = &createdCopy + } + if op.Updated != nil { + updatedCopy := *op.Updated + cop.Updated = &updatedCopy + } + if op.Retry != nil { + retryCopy := *op.Retry + cop.Retry = &retryCopy + } + if op.Input != nil { + cop.Input = deepCopyMap(op.Input) + } + if op.Output != nil { + cop.Output = deepCopyMap(op.Output) + } + return cop +} + +func deepCopyMap(original map[string]interface{}) map[string]interface{} { + if original == nil { + return nil + } + copy := make(map[string]interface{}, len(original)) + for key, value := range original { + switch v := value.(type) { + case map[string]interface{}: + copy[key] = deepCopyMap(v) + case []interface{}: + copy[key] = deepCopySlice(v) + default: + copy[key] = v + } + } + return copy +} + +func deepCopySlice(original []interface{}) []interface{} { + if original == nil { + return nil + } + copy := make([]interface{}, len(original)) + for i, value := range original { + switch v := value.(type) { + case map[string]interface{}: + copy[i] = deepCopyMap(v) + case []interface{}: + copy[i] = deepCopySlice(v) + default: + copy[i] = v + } + } + return copy +} + // OpStatus is the current status of an operation type OpStatus string diff --git a/pkg/core/operation_test.go b/pkg/core/operation_test.go index 0ac132814c..6f9f4b44ef 100644 --- a/pkg/core/operation_test.go +++ b/pkg/core/operation_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -18,6 +18,7 @@ package core import ( "context" + "reflect" "testing" "github.com/hyperledger/firefly-common/pkg/fftypes" @@ -97,6 +98,63 @@ func TestOperationTypes(t *testing.T) { assert.False(t, op.IsBlockchainOperation()) } +func TestOperationDeepCopy(t *testing.T) { + op := &Operation{ + ID: fftypes.NewUUID(), + Namespace: "ns1", + Transaction: fftypes.NewUUID(), + Type: OpTypeBlockchainInvoke, + Status: OpStatusInitialized, + Plugin: "fake", + Input: fftypes.JSONObject{"key": "value"}, + Output: fftypes.JSONObject{"result": "success"}, + Error: "error message", + Created: fftypes.Now(), + Updated: fftypes.Now(), + Retry: fftypes.NewUUID(), + } + + copyOp := op.DeepCopy() + shallowCopy := op // Shallow copy for showcasing that DeepCopy is a deep copy + + // Ensure the data was copied correctly + assert.Equal(t, op.ID, copyOp.ID) + assert.Equal(t, op.Namespace, copyOp.Namespace) + assert.Equal(t, op.Transaction, copyOp.Transaction) + assert.Equal(t, op.Type, copyOp.Type) + assert.Equal(t, op.Status, copyOp.Status) + assert.Equal(t, op.Plugin, copyOp.Plugin) + assert.Equal(t, op.Input, copyOp.Input) + assert.Equal(t, op.Output, copyOp.Output) + assert.Equal(t, op.Error, copyOp.Error) + assert.Equal(t, op.Created, copyOp.Created) + assert.Equal(t, op.Updated, copyOp.Updated) + assert.Equal(t, op.Retry, copyOp.Retry) + + // Modify the original and ensure the copy is not modified + *op.ID = *fftypes.NewUUID() + assert.NotEqual(t, copyOp.ID, op.ID) + + *op.Created = *fftypes.Now() + assert.NotEqual(t, copyOp.Created, op.Created) + + // Ensure the copy is a deep copy by comparing the pointers of the fields + assert.NotSame(t, copyOp.ID, op.ID) + assert.NotSame(t, copyOp.Created, op.Created) + assert.NotSame(t, copyOp.Updated, op.Updated) + assert.NotSame(t, copyOp.Transaction, op.Transaction) + assert.NotSame(t, copyOp.Retry, op.Retry) + assert.NotSame(t, copyOp.Input, op.Input) + assert.NotSame(t, copyOp.Output, op.Output) + + // showcasing that the shallow copy is a shallow copy and the copied object value changed as well the pointer has the same address as the original + assert.Equal(t, shallowCopy.ID, op.ID) + assert.Same(t, shallowCopy.ID, op.ID) + + // Ensure no new fields are added to the Operation struct + // If a new field is added, this test will fail and the DeepCopy function should be updated + assert.Equal(t, 12, reflect.TypeOf(Operation{}).NumField()) +} func TestParseNamespacedOpID(t *testing.T) { ctx := context.Background() @@ -124,3 +182,113 @@ func TestParseNamespacedOpID(t *testing.T) { assert.Equal(t, "ns1", ns) } + +func TestDeepCopyMapNil(t *testing.T) { + original := map[string]interface{}(nil) + copy := deepCopyMap(original) + assert.Nil(t, copy) +} + +func TestDeepCopyMapEmpty(t *testing.T) { + original := map[string]interface{}{} + copy := deepCopyMap(original) + assert.NotNil(t, copy) + assert.Empty(t, copy) +} + +func TestDeepCopyMapSimple(t *testing.T) { + original := map[string]interface{}{ + "key1": "value1", + "key2": 42, + } + copy := deepCopyMap(original) + assert.Equal(t, original, copy) +} + +func TestDeepCopyMapNestedMap(t *testing.T) { + original := map[string]interface{}{ + "key1": map[string]interface{}{ + "nestedKey1": "nestedValue1", + }, + } + copy := deepCopyMap(original) + assert.Equal(t, original, copy) + assert.NotSame(t, original["key1"], copy["key1"]) +} + +func TestDeepCopyMapNestedSlice(t *testing.T) { + original := map[string]interface{}{ + "key1": []interface{}{"value1", 42}, + } + copy := deepCopyMap(original) + assert.Equal(t, original, copy) + assert.NotSame(t, original["key1"], copy["key1"]) +} + +func TestDeepCopyMapMixed(t *testing.T) { + original := map[string]interface{}{ + "key1": "value1", + "key2": map[string]interface{}{ + "nestedKey1": "nestedValue1", + }, + "key3": []interface{}{"value1", 42}, + } + copy := deepCopyMap(original) + assert.Equal(t, original, copy) + assert.NotSame(t, original["key2"], copy["key2"]) + assert.NotSame(t, original["key3"], copy["key3"]) +} + +func TestDeepCopySliceNil(t *testing.T) { + original := []interface{}(nil) + copy := deepCopySlice(original) + assert.Nil(t, copy) +} + +func TestDeepCopySliceEmpty(t *testing.T) { + original := []interface{}{} + copy := deepCopySlice(original) + assert.NotNil(t, copy) + assert.Empty(t, copy) +} + +func TestDeepCopySliceSimple(t *testing.T) { + original := []interface{}{"value1", 42} + copy := deepCopySlice(original) + assert.Equal(t, original, copy) +} + +func TestDeepCopySliceNestedMap(t *testing.T) { + original := []interface{}{ + map[string]interface{}{ + "nestedKey1": "nestedValue1", + }, + } + copy := deepCopySlice(original) + assert.Equal(t, original, copy) + assert.NotSame(t, original[0], copy[0]) +} + +func TestDeepCopySliceNestedSlice(t *testing.T) { + original := []interface{}{ + []interface{}{"value1", 42}, + } + copy := deepCopySlice(original) + assert.Equal(t, original, copy) + assert.NotSame(t, original[0], copy[0]) +} + +func TestDeepCopySliceMixed(t *testing.T) { + original := []interface{}{ + "value1", + 42, + map[string]interface{}{ + "nestedKey1": "nestedValue1", + }, + []interface{}{"value2", 43}, + } + copy := deepCopySlice(original) + assert.Equal(t, original, copy) + assert.NotSame(t, original[2], copy[2]) + assert.NotSame(t, original[3], copy[3]) +} diff --git a/smart_contracts/fabric/custompin-sample/go.mod b/smart_contracts/fabric/custompin-sample/go.mod index 5577683ebe..0c0df215bc 100644 --- a/smart_contracts/fabric/custompin-sample/go.mod +++ b/smart_contracts/fabric/custompin-sample/go.mod @@ -1,6 +1,6 @@ module github.com/hyperledger/firefly/custompin_sample -go 1.21 +go 1.22 require ( github.com/hyperledger/fabric-chaincode-go v0.0.0-20240124143825-7dec3c7e7d45 diff --git a/smart_contracts/fabric/firefly-go/go.mod b/smart_contracts/fabric/firefly-go/go.mod index 11f2be79fd..6dee927138 100644 --- a/smart_contracts/fabric/firefly-go/go.mod +++ b/smart_contracts/fabric/firefly-go/go.mod @@ -1,6 +1,6 @@ module github.com/hyperledger/firefly/chaincode-go -go 1.21 +go 1.22 require ( github.com/golang/protobuf v1.5.3 diff --git a/test/data/contracts/assetcreator/go.mod b/test/data/contracts/assetcreator/go.mod index a90777bf1e..2a9d8cee11 100644 --- a/test/data/contracts/assetcreator/go.mod +++ b/test/data/contracts/assetcreator/go.mod @@ -1,8 +1,8 @@ module github.com/hyperledger/firefly/test/data/assetcreator -go 1.21 +go 1.22 -toolchain go1.21.0 +toolchain go1.22.0 require github.com/hyperledger/fabric-contract-api-go v1.2.2