From 18b228d3bce444b3db964b76b4dc9cf56f35d61b Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Fri, 1 Mar 2024 15:42:31 -0800 Subject: [PATCH] docs(examples/supply-chain): fix test infra - migrate to Fabric v2.5.6 The supply chain app's build and execution scripts should finally be working after this and also be much more stable than before due to the flakiness of the Fabric V1 test ledger not being an issue anymore. The container image is now published as: `ghcr.io/hyperledger/cactus-example-supply-chain-app:2024-03-08--pr-3059-1` 1.The new contract is compiled with go v1.20 and therefore has to have the contract method names capitalized insted of camelCase, otherwise the methods are not possible to be exported and the contract deployment fails even if everything else is correct. 2. The supply chain app now uses the newest edition of the Fabric v2 AIO test ledger container image which uses Fabric v2.5.6 (current LTS at the time of this writing). 3. The shipment contract's source code has been migrated to Fabric v2 meaning that instead of a stub object we get a context object for each method's first parameter and then the stub can be acquired from that context object. 4. The method arguments no longer need to be passed around as an array of strings and instead the contract method's input arguments are first-class go method parameters. 5. Re-enabled a test case that was being skipped until now due to flakiness: ...`src/test/typescript/integration/supply-chain-backend-api-calls.test.ts` The supply chain app container image was built with this command: ```sh DOCKER_BUILDKIT=1 docker build \ --build-arg="NPM_PKG_VERSION=2.0.0-2945-supply-chain-app-build-failed.241+b2c306ea0" \ -f ./examples/cactus-example-supply-chain-backend/Dockerfile \ . \ -t scaeb ``` The NPM_PKG_VERSION build arg is important because the image defaults to "latest" which at the moment is packages that do not contain the fixes made by this commit, so re-building the image without that extra arg will not work until we issue the next official non-canary release. Depends on #3058 Depends on #3054 Fixes #2945 Fixes #2969 Fixes #1899 Fixes #1521 Fixes #1518 Signed-off-by: Peter Somogyvari --- .cspell.json | 12 ++- .github/workflows/ci.yaml | 2 + README-cactus.md | 2 +- docs/docs/cactus/examples/supply-chain-app.md | 2 +- .../Dockerfile | 64 ++++++++---- .../README.md | 2 +- .../src/main/go/shipment.ts | 98 ++++++++++--------- ...ent-golang-contract-pinned-dependencies.ts | 37 +++++++ .../supply-chain-app-dummy-infrastructure.ts | 68 +++++-------- .../src/main/typescript/supply-chain-app.ts | 12 +-- .../supply-chain-backend-api-calls.test.ts | 41 +++++++- .../supply-chain-cli-via-npm-script.test.ts | 91 ++++++++++++----- .../supervisord.conf | 2 +- .../web-services/insert-shipment-endpoint.ts | 2 +- .../web-services/list-shipment-endpoint.ts | 10 +- 15 files changed, 293 insertions(+), 152 deletions(-) create mode 100644 examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts diff --git a/.cspell.json b/.cspell.json index 686dfceff2..b45f60df1f 100644 --- a/.cspell.json +++ b/.cspell.json @@ -45,6 +45,7 @@ "COUCHDBCONFIG", "Creds", "data", + "davecgh", "dclm", "DHTAPI", "dids", @@ -61,9 +62,12 @@ "fidm", "flowdb", "fsouza", + "genproto", "GETHKEYCHAINPASSWORD", "ghcr", + "gobuffalo", "gopath", + "gopkg", "goquorum", "grpc", "grpcs", @@ -87,6 +91,7 @@ "Irohad", "isready", "jboss", + "joho", "JORDI", "jsrsa", "jsrsasign", @@ -101,6 +106,7 @@ "leveldb", "lmify", "LOCALMSPID", + "mailru", "miekg", "mitchellh", "MSPCONFIGPATH", @@ -129,7 +135,9 @@ "Orgs", "ossp", "outsh", + "Panicf", "parameterizable", + "pmezard", "Postgres", "proto", "protobuf", @@ -137,6 +145,7 @@ "protos", "qscc", "recoverupdateackmessage", + "rogpeppe", "RUSTC", "Rwset", "satp", @@ -171,7 +180,8 @@ "vuln", "wasm", "WSPROVIDER", - "Xdai" + "Xdai", + "xeipuuv" ], "dictionaries": [ "typescript,node,npm,go,rust" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9e439c6df0..2bf143c9b9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -612,6 +612,8 @@ jobs: cactus-example-supply-chain-backend: continue-on-error: false env: + DUMP_DISK_USAGE_INFO_DISABLED: false + FREE_UP_GITHUB_RUNNER_DISK_SPACE_DISABLED: false FULL_BUILD_DISABLED: true JEST_TEST_PATTERN: examples/cactus-example-supply-chain-backend/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts JEST_TEST_RUNNER_DISABLED: false diff --git a/README-cactus.md b/README-cactus.md index c922f85567..3aef25c1a3 100644 --- a/README-cactus.md +++ b/README-cactus.md @@ -32,7 +32,7 @@ As blockchain technology proliferates, blockchain integration will become an inc -p 4000:4000 \ -p 4100:4100 \ -p 4200:4200 \ - ghcr.io/hyperledger/cactus-example-supply-chain-app:2022-04-05--feat-1579 + ghcr.io/hyperledger/cactus-example-supply-chain-app:2024-03-08--pr-3059-1 ``` 3. Wait for the output to show the message `INFO (api-server): Cactus Cockpit reachable http://0.0.0.0:3200` 4. Token generated by the application is displayed below diff --git a/docs/docs/cactus/examples/supply-chain-app.md b/docs/docs/cactus/examples/supply-chain-app.md index 0e8a0d309f..784d8c9a11 100644 --- a/docs/docs/cactus/examples/supply-chain-app.md +++ b/docs/docs/cactus/examples/supply-chain-app.md @@ -15,7 +15,7 @@ Usage \-p 4000:4000 \\ \-p 4100:4100 \\ \-p 4200:4200 \\ - ghcr.io/hyperledger/cactus-example-supply-chain-app:2022-04-05--feat-1579 + ghcr.io/hyperledger/cactus-example-supply-chain-app:2024-03-08--pr-3059-1 2. Observe the example application pulling up in the logs diff --git a/examples/cactus-example-supply-chain-backend/Dockerfile b/examples/cactus-example-supply-chain-backend/Dockerfile index 8487b3b150..a757505717 100644 --- a/examples/cactus-example-supply-chain-backend/Dockerfile +++ b/examples/cactus-example-supply-chain-backend/Dockerfile @@ -1,44 +1,66 @@ -FROM cruizba/ubuntu-dind:19.03.11 as runner +FROM cruizba/ubuntu-dind:jammy-24.0.7-compose-2.24.0 as runner USER root +ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update -RUN apt -y upgrade +RUN apt-get update && \ + apt -y upgrade # Need curl for healthchecks -RUN apt-get -y install --no-install-recommends curl - # The file binary is used to inspect exectubles when debugging container image issues -RUN apt-get -y install --no-install-recommends file - - -RUN apt-get -y install --no-install-recommends ca-certificates -RUN apt-get -y install --no-install-recommends tzdata +RUN apt-get -y install --no-install-recommends -y \ + curl \ + file \ + ca-certificates \ + tzdata \ + git \ + jq ARG APP=/usr/src/app/ ENV TZ=Etc/UTC ENV APP_USER=appuser -RUN useradd -m ${APP_USER} -RUN usermod -a -G ${APP_USER} ${APP_USER} -RUN mkdir -p ${APP} +RUN useradd -m ${APP_USER} && \ + usermod -a -G ${APP_USER} ${APP_USER} && \ + mkdir -p ${APP} -RUN mkdir -p "${APP}/log/" -RUN chown -R $APP_USER:$APP_USER "${APP}/" +RUN mkdir -p "${APP}/log/" && \ + chown -R $APP_USER:$APP_USER "${APP}/" # TODO: Can we hack it together so that the whole thing works rootless? USER ${APP_USER} WORKDIR ${APP} +# COPY --chown=${APP_USER}:${APP_USER} examples/cactus-example-supply-chain-backend/tpl.package.json /usr/src/app/package.json + +# RUN touch /usr/src/app/package.json + SHELL ["/bin/bash", "--login", "-i", "-c"] -# Installing Node Version Manager (nvm) -RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash -ARG NPM_PKG_VERSION=latest +# Installing NodeJS via Node Version Manager (nvm) +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +ARG NPM_PKG_VERSION="latest" RUN source ~/.bashrc && \ - nvm install 16.15.1 && \ - npm install -g yarn && \ - yarn add @hyperledger/cactus-example-supply-chain-backend@${NPM_PKG_VERSION} --ignore-engines --production + nvm install 18.18.2 && \ + npm i -g corepack && corepack enable && corepack prepare yarn@4.1.0 --activate && \ + yarn init --yes --private && \ + yarn config set nodeLinker node-modules && \ + yarn add @hyperledger/cactus-example-supply-chain-backend@${NPM_PKG_VERSION} --exact && \ + yarn add ethers@6.8.1 --exact && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-test-tooling": "\($NPM_PKG_VERSION)" }' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-api-client": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-cmd-api-server": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-common": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-core": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-core-api": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-example-supply-chain-business-logic-plugin": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-example-supply-chain-frontend": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-plugin-consortium-manual": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-plugin-keychain-memory": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-plugin-ledger-connector-besu": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-plugin-ledger-connector-fabric": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + cat /usr/src/app/package.json | jq --arg NPM_PKG_VERSION ${NPM_PKG_VERSION} '.resolutions += { "@hyperledger/cactus-plugin-ledger-connector-quorum": "\($NPM_PKG_VERSION)" } ' | tee /usr/src/app/package.json && \ + yarn install SHELL ["/bin/bash", "--login", "-c"] diff --git a/examples/cactus-example-supply-chain-backend/README.md b/examples/cactus-example-supply-chain-backend/README.md index eb360c64b3..8543e69029 100644 --- a/examples/cactus-example-supply-chain-backend/README.md +++ b/examples/cactus-example-supply-chain-backend/README.md @@ -14,7 +14,7 @@ -p 4000:4000 \ -p 4100:4100 \ -p 4200:4200 \ - ghcr.io/hyperledger/cactus-example-supply-chain-app:2022-04-05--feat-1579 + ghcr.io/hyperledger/cactus-example-supply-chain-app:2024-03-08--pr-3059-1 ``` 2. Observe the example application pulling up in the logs 1. the test ledger containers, diff --git a/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts b/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts index 2a526cc3f2..481231181b 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts @@ -1,15 +1,23 @@ export const SHIPMENT_CONTRACT_GO_SOURCE = `package main import ( - "bytes" "encoding/json" "fmt" + "log" + "strings" + "github.com/hyperledger/fabric-contract-api-go/contractapi" - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/hyperledger/fabric/protos/peer" ) +func isNonBlank(str *string) bool { + if str == nil { + return false + } + return strings.TrimSpace(*str) != "" +} + type ShipmentChaincode struct { + contractapi.Contract } type Shipment struct { @@ -18,85 +26,79 @@ type Shipment struct { } func main() { - if err := shim.Start(new(ShipmentChaincode)); err != nil { - fmt.Printf("Error starting ShipmentChaincode chaincode: %s", err) + shipmentChaincode, err := contractapi.NewChaincode(&ShipmentChaincode{}) + if err != nil { + log.Panicf("Error creating supply-chain shipment chaincode: %v", err) } -} -func (t *ShipmentChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response { - return shim.Success(nil) + if err := shipmentChaincode.Start(); err != nil { + log.Panicf("Error starting supply-chain shipment chaincode: %v", err) + } } -func (t *ShipmentChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { - fn, args := stub.GetFunctionAndParameters() +// InitLedger adds a base set of assets to the ledger +func (t *ShipmentChaincode) InitLedger(ctx contractapi.TransactionContextInterface) error { - if fn == "insertShipment" { - return t.insertShipment(stub, args) - } else if fn == "getListShipment" { - return t.getListShipment(stub, args) - } - return shim.Error("Unknown function") + log.Println("InitLedger ran for supply-chain shipment chaincode. %v", t) + + return nil } -func (t *ShipmentChaincode) insertShipment(stub shim.ChaincodeStubInterface, args []string) peer.Response { - if len(args) != 2 { - return shim.Error("Incorrect arguments. Expecting an id and a bookshelfId") +func (t *ShipmentChaincode) InsertShipment(ctx contractapi.TransactionContextInterface, newShipmentId string, bookshelfId string) (string, error) { + if !isNonBlank(&newShipmentId) { + return "E_NEW_SHIPMENT_ID_BLANK", fmt.Errorf("Incorrect arguments. Expecting a shipment ID as a non-blank string.") + } + if !isNonBlank(&bookshelfId) { + return "E_NEW_BOOKSHELF_ID_BLANK", fmt.Errorf("Incorrect arguments. Expecting a bookshelf ID as a non-blank string.") } var newShipment Shipment - newShipment.Id = args[0] - newShipment.BookshelfId = args[1] + newShipment.Id = newShipmentId + newShipment.BookshelfId = bookshelfId newShipmentJSON, err := json.Marshal(newShipment) if err != nil { - return shim.Error(err.Error()) + return "OP_FAILED", fmt.Errorf("Failed to JSON marshal new shipment data: %v", err) } - err2 := stub.PutState(args[0], newShipmentJSON) + stub := ctx.GetStub() + + err2 := stub.PutState(newShipmentId, newShipmentJSON) if err2 != nil { - return shim.Error("Failed to insert shipment:" + args[0] + err2.Error()) + return "E_PUT_STATE_FAIL", fmt.Errorf("Failed to insert new shipment to ledger state: %v --- %v", newShipmentJSON, err2) } - return shim.Success([]byte(args[0])) + return newShipmentId, nil } -func (t *ShipmentChaincode) getListShipment(stub shim.ChaincodeStubInterface, args []string) peer.Response { - if len(args) != 0 { - return shim.Error("Incorrect arguments. No arguments expected") - } - bookmark := "" +func (t *ShipmentChaincode) GetListShipment(ctx contractapi.TransactionContextInterface) ([]Shipment, error) { - resultsIterator, _, err := stub.GetStateByRangeWithPagination("", "", 25, bookmark) + stub := ctx.GetStub() + + resultsIterator, _, err := stub.GetStateByRangeWithPagination("", "", 25, "") if err != nil { - return shim.Error("Error in getListShipment: " + err.Error()) + return nil, fmt.Errorf("Error in GetListShipment: %v", err) } defer resultsIterator.Close() - var listShipment []Shipment + var shipments []Shipment for resultsIterator.HasNext() { - var buffer bytes.Buffer - var aux Shipment response, err := resultsIterator.Next() if err != nil { - return shim.Error("Error in iterator: " + err.Error()) + return nil, fmt.Errorf("Error in shipment result iterator: %v", err) } - buffer.WriteString(string(response.Value)) - - _ = json.Unmarshal(buffer.Bytes(), &aux) - - listShipment = append(listShipment, aux) - } + var aux Shipment + if err := json.Unmarshal(response.Value, &aux); err != nil { + return nil, fmt.Errorf("Error un-marshalling shipment: %v", err) + } - jsonResponse, err := json.Marshal(listShipment) - if err != nil { - return shim.Error("Error get result: " + err.Error()) + shipments = append(shipments, aux) } - return shim.Success(jsonResponse) -} -`; + return shipments, nil +}`; const exportSourceToFs = async () => { const path = await import("path"); diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts new file mode 100644 index 0000000000..4f5bab11b2 --- /dev/null +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts @@ -0,0 +1,37 @@ +/** + * These are the pinned dependencies used to build the go source code that is + * located in the file at: + * examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts + */ +export const SHIPMENT_GOLANG_CONTRACT_PINNED_DEPENDENCIES = [ + "github.com/hyperledger/fabric-chaincode-go@v0.0.0-20240124143825-7dec3c7e7d45", + "github.com/hyperledger/fabric-contract-api-go@v1.2.2", + "github.com/hyperledger/fabric-protos-go@v0.3.3", + "github.com/hyperledger/fabric-samples/asset-transfer-private-data/chaincode-go@v0.0.0-20240226031326-db86460086c0", + "github.com/stretchr/testify@v1.8.4", + "google.golang.org/protobuf@v1.32.0", + "github.com/davecgh/go-spew@v1.1.1", // indirect + "github.com/go-openapi/jsonpointer@v0.20.0", // indirect + "github.com/go-openapi/jsonreference@v0.20.2", // indirect + "github.com/go-openapi/spec@v0.20.9", // indirect + "github.com/go-openapi/swag@v0.22.4", // indirect + "github.com/gobuffalo/envy@v1.10.2", // indirect + "github.com/gobuffalo/packd@v1.0.2", // indirect + "github.com/gobuffalo/packr@v1.30.1", // indirect + "github.com/golang/protobuf@v1.5.3", // indirect + "github.com/joho/godotenv@v1.5.1", // indirect + "github.com/josharian/intern@v1.0.0", // indirect + "github.com/mailru/easyjson@v0.7.7", // indirect + "github.com/pmezard/go-difflib@v1.0.0", // indirect + "github.com/rogpeppe/go-internal@v1.11.0", // indirect + "github.com/xeipuuv/gojsonpointer@v0.0.0-20190905194746-02993c407bfb", // indirect + "github.com/xeipuuv/gojsonreference@v0.0.0-20180127040603-bd5ef7bd5415", // indirect + "github.com/xeipuuv/gojsonschema@v1.2.0", // indirect + "golang.org/x/mod@v0.14.0", // indirect + "golang.org/x/net@v0.20.0", // indirect + "golang.org/x/sys@v0.16.0", // indirect + "golang.org/x/text@v0.14.0", // indirect + "google.golang.org/genproto/googleapis/rpc@v0.0.0-20231030173426-d783a09b4405", // indirect + "google.golang.org/grpc@v1.59.0", // indirect + "gopkg.in/yaml.v3@v3.0.1", // indirect +]; diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts index 53f6ad805b..f39c4d298b 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts @@ -1,5 +1,6 @@ import { Account } from "web3-core"; import { v4 as uuidv4 } from "uuid"; +import { DiscoveryOptions } from "fabric-network"; import { Logger, Checks, @@ -13,38 +14,31 @@ import { PluginLedgerConnectorQuorum, Web3SigningCredentialType, } from "@hyperledger/cactus-plugin-ledger-connector-quorum"; +import { IPluginKeychain } from "@hyperledger/cactus-core-api"; +import { FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1 } from "@hyperledger/cactus-test-tooling"; +import { FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2 } from "@hyperledger/cactus-test-tooling"; import { BesuTestLedger, + DEFAULT_FABRIC_2_AIO_IMAGE_NAME, + FABRIC_25_LTS_AIO_FABRIC_VERSION, + FABRIC_25_LTS_AIO_IMAGE_VERSION, FabricTestLedgerV1, QuorumTestLedger, } from "@hyperledger/cactus-test-tooling"; - -import BambooHarvestRepositoryJSON from "../../json/generated/BambooHarvestRepository.json"; -import BookshelfRepositoryJSON from "../../json/generated/BookshelfRepository.json"; import { IEthContractDeployment, ISupplyChainContractDeploymentInfo, IFabricContractDeployment, - // OrgEnv, } from "@hyperledger/cactus-example-supply-chain-business-logic-plugin"; import { PluginLedgerConnectorFabric, DefaultEventHandlerStrategy, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; -import { DiscoveryOptions } from "fabric-network"; -import { SHIPMENT_CONTRACT_GO_SOURCE } from "../../go/shipment"; -import { IPluginKeychain } from "@hyperledger/cactus-core-api"; -export const org1Env = { - CORE_PEER_LOCALMSPID: "Org1MSP", - CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", - CORE_PEER_MSPCONFIGPATH: - "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp", - CORE_PEER_TLS_ROOTCERT_FILE: - "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt", - ORDERER_TLS_ROOTCERT_FILE: - "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem", -}; +import BambooHarvestRepositoryJSON from "../../json/generated/BambooHarvestRepository.json"; +import BookshelfRepositoryJSON from "../../json/generated/BookshelfRepository.json"; +import { SHIPMENT_CONTRACT_GO_SOURCE } from "../../go/shipment"; +import { SHIPMENT_GOLANG_CONTRACT_PINNED_DEPENDENCIES } from "./shipment-golang-contract-pinned-dependencies"; export interface ISupplyChainAppDummyInfrastructureOptions { logLevel?: LogLevelDesc; @@ -113,8 +107,10 @@ export class SupplyChainAppDummyInfrastructure { }); this.fabric = new FabricTestLedgerV1({ publishAllPorts: true, - imageName: "ghcr.io/hyperledger/cactus-fabric-all-in-one", + imageName: DEFAULT_FABRIC_2_AIO_IMAGE_NAME, + imageVersion: FABRIC_25_LTS_AIO_IMAGE_VERSION, logLevel: level, + envVars: new Map([["FABRIC_VERSION", FABRIC_25_LTS_AIO_FABRIC_VERSION]]), emitContainerLogs: true, }); @@ -292,7 +288,7 @@ export class SupplyChainAppDummyInfrastructure { sshConfig: sshConfig, logLevel: this.options.logLevel || "INFO", connectionProfile: connectionProfile, - cliContainerEnv: org1Env, + cliContainerEnv: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, discoveryOptions: discoveryOptions, eventHandlerOptions: { strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, @@ -300,8 +296,12 @@ export class SupplyChainAppDummyInfrastructure { }); const res = await connector.deployContractGoSourceV1({ - tlsRootCertFiles: org1Env.CORE_PEER_TLS_ROOTCERT_FILE as string, - targetPeerAddresses: [org1Env.CORE_PEER_ADDRESS as string], + tlsRootCertFiles: + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1.CORE_PEER_TLS_ROOTCERT_FILE, + targetPeerAddresses: [ + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1.CORE_PEER_ADDRESS, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2.CORE_PEER_ADDRESS, + ], policyDslSource: "OR('Org1MSP.member','Org2MSP.member')", channelId: "mychannel", chainCodeVersion: "1.0.0", @@ -311,29 +311,11 @@ export class SupplyChainAppDummyInfrastructure { filename: "shipment.go", }, moduleName: "shipment", - targetOrganizations: [org1Env], - pinnedDeps: [ - "github.com/Knetic/govaluate@v3.0.0+incompatible", - "github.com/Shopify/sarama@v1.27.0", - "github.com/fsouza/go-dockerclient@v1.6.5", - "github.com/grpc-ecosystem/go-grpc-middleware@v1.2.1", - "github.com/hashicorp/go-version@v1.2.1", - "github.com/hyperledger/fabric@v1.4.8", - "github.com/hyperledger/fabric-amcl@v0.0.0-20200424173818-327c9e2cf77a", - "github.com/miekg/pkcs11@v1.0.3", - "github.com/mitchellh/mapstructure@v1.3.3", - "github.com/onsi/ginkgo@v1.14.1", - "github.com/onsi/gomega@v1.10.2", - "github.com/op/go-logging@v0.0.0-20160315200505-970db520ece7", - "github.com/pkg/errors@v0.9.1", - "github.com/spf13/viper@v1.7.1", - "github.com/stretchr/testify@v1.6.1", - "github.com/sykesm/zap-logfmt@v0.0.3", - "go.uber.org/zap@v1.16.0", - "golang.org/x/crypto@v0.0.0-20200820211705-5c72a883971a", - "golang.org/x/net@v0.0.0-20210503060351-7fd8e65b6420", - "google.golang.org/grpc@v1.31.1", + targetOrganizations: [ + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, ], + pinnedDeps: SHIPMENT_GOLANG_CONTRACT_PINNED_DEPENDENCIES, }); this.log.debug("Supply chain app Fabric contract deployment result:", res); diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts index 363e17d810..75aeb381ed 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts @@ -60,10 +60,9 @@ import { DefaultEventHandlerStrategy, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; -import { - SupplyChainAppDummyInfrastructure, - org1Env, -} from "./infrastructure/supply-chain-app-dummy-infrastructure"; +import { FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1 } from "@hyperledger/cactus-test-tooling"; + +import { SupplyChainAppDummyInfrastructure } from "./infrastructure/supply-chain-app-dummy-infrastructure"; import { Configuration, DefaultApi as SupplyChainApi, @@ -193,6 +192,7 @@ export class SupplyChainApp { if (!this.options.disableSignalHandlers) { exitHook((callback: IAsyncExitHookDoneCallback) => { + console.log(`Executing Registered signal handler to stop container.`); this.stop().then(callback); }); this.log.debug(`Registered signal handlers for graceful auto-shutdown`); @@ -388,7 +388,7 @@ export class SupplyChainApp { besuApiClient, quorumApiClient, fabricApiClient, - fabricEnvironment: org1Env, + fabricEnvironment: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, }), this.keychain, ], @@ -403,7 +403,7 @@ export class SupplyChainApp { instanceId: "PluginLedgerConnectorFabric_C", dockerBinary: "/usr/local/bin/docker", peerBinary: "peer", - cliContainerEnv: org1Env, + cliContainerEnv: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, connectionProfile: connectionProfile, sshConfig: sshConfig, logLevel: "INFO", diff --git a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts index ab5e8d0c9a..36fd3affd2 100644 --- a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts +++ b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts @@ -1,3 +1,4 @@ +import { v4 as uuidV4 } from "uuid"; import test, { Test } from "tape-promise/tape"; import { LogLevelDesc } from "@hyperledger/cactus-common"; import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; @@ -8,12 +9,13 @@ import { ConfigService } from "@hyperledger/cactus-cmd-api-server"; import * as publicApi from "../../../main/typescript/public-api"; import { ISupplyChainAppOptions } from "../../../main/typescript/public-api"; import { SupplyChainApp } from "../../../main/typescript/public-api"; +import { Shipment } from "@hyperledger/cactus-example-supply-chain-business-logic-plugin"; const testCase = "can launch via CLI with generated API server .config.json file"; const logLevel: LogLevelDesc = "TRACE"; -test.skip("BEFORE " + testCase, async (t: Test) => { +test("BEFORE " + testCase, async (t: Test) => { const pruning = pruneDockerAllIfGithubAction({ logLevel }); await t.doesNotReject(pruning, "Pruning did not throw OK"); t.end(); @@ -21,7 +23,7 @@ test.skip("BEFORE " + testCase, async (t: Test) => { // FIXME: https://github.com/hyperledger/cactus/issues/1521 // Skipping until test can be stabilized. -test.skip("Supply chain backend API calls can be executed", async (t: Test) => { +test("Supply chain backend API calls can be executed", async (t: Test) => { t.ok(publicApi, "Public API of the package imported OK"); const configService = new ConfigService(); @@ -69,7 +71,7 @@ test.skip("Supply chain backend API calls can be executed", async (t: Test) => { // Node A => Besu // Node B => Quorum - // Node C => Fabric 1.4.x + // Node C => Fabric v2.5.6 const startResult = await app.start(); const { apiServerA, apiServerB, apiServerC } = startResult; t.ok(apiServerA, "ApiServerA truthy OK"); @@ -144,5 +146,38 @@ test.skip("Supply chain backend API calls can be executed", async (t: Test) => { "listShipmentRes status < 300 truthy OK", ); + const shipment: Shipment = { + bookshelfId: "some-id-of-a-bookshelf-" + uuidV4(), + id: "some-id-of-a-shipment-" + uuidV4(), + }; + + const insertShipmentRes = await supplyChainApiClientC.insertShipmentV1({ + shipment, + }); + t.ok(insertShipmentRes, "insertShipmentRes truthy OK"); + t.true( + insertShipmentRes.status > 199, + "insertShipmentRes status > 199 truthy OK", + ); + t.true( + insertShipmentRes.status < 300, + "insertShipmentRes status < 300 truthy OK", + ); + + const listShipmentRes2 = await supplyChainApiClientC.listShipmentV1(); + t.ok(listShipmentRes2, "listShipmentRes2 truthy OK"); + t.true( + listShipmentRes2.status > 199, + "listShipmentRes2 status > 199 truthy OK", + ); + t.true( + listShipmentRes2.status < 300, + "listShipmentRes2 status < 300 truthy OK", + ); + + const shipments = listShipmentRes2.data.data; + const [firstShipment] = shipments; + t.equal(shipment.id, firstShipment.id, "Inserted shipment matches IDs OK"); + t.end(); }); diff --git a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts index a5256b331f..ccbff0f52a 100644 --- a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts +++ b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts @@ -1,53 +1,79 @@ import path from "path"; -import { spawn } from "child_process"; +import { promisify } from "util"; +import { spawn, exec } from "child_process"; +import { v4 as uuidV4 } from "uuid"; import test, { Test } from "tape-promise/tape"; import { LogLevelDesc } from "@hyperledger/cactus-common"; import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; import * as publicApi from "../../../main/typescript/public-api"; -const testCase = - "can launch via CLI with generated API server .config.json file"; +const testCase = "SupplyChainApp can launch via root package.json script"; const logLevel: LogLevelDesc = "TRACE"; -test.skip("BEFORE " + testCase, async (t: Test) => { +test("BEFORE " + testCase, async (t: Test) => { const pruning = pruneDockerAllIfGithubAction({ logLevel }); await t.doesNotReject(pruning, "Pruning did not throw OK"); t.end(); }); -// FIXME: remove the skip once this issue is fixed: -// https://github.com/hyperledger/cactus/issues/1518 -test.skip("Supply chain backend API calls can be executed", async (t: Test) => { +async function psFilter(filter?: string): Promise> { + const execAsync = promisify(exec); + try { + const { stdout } = await execAsync("ps -wo pid,cmd"); + const rows = stdout.split("\n"); + const map = new Map(); + rows.forEach((row) => { + const [pidStr, ...cmdParts] = row.split(" "); + if (pidStr === "") { + return; + } + const cmd = cmdParts.join(" "); + console.log("pid=%o, cmd=%o", pidStr, cmd); + const pid = parseInt(pidStr, 10); + if (filter && cmd.includes(filter)) { + map.set(pid, cmd); + } + }); + return map; + } catch (e: unknown) { + console.error("Crashed while running ps binary. Are you on Linux?", e); + throw e; + } +} + +test(testCase, async (t: Test) => { t.ok(publicApi, "Public API of the package imported OK"); test.onFinish(async () => await pruneDockerAllIfGithubAction({ logLevel })); + const uuid = uuidV4(); + const projectRoot = path.join(__dirname, "../../../../../../"); - const child = spawn("npm", ["run", "start:example-supply-chain"], { - cwd: projectRoot, - }); + // used to find the processes to kill after we are done with the test + const beaconCliArg = "--" + uuid; - const logs = []; - for await (const data of child.stdout) { - console.log(`[child]: ${data}`); - logs.push(data); - } + const child = spawn( + "npm", + ["run", "start:example-supply-chain", "--", beaconCliArg], + { + cwd: projectRoot, + }, + ); - for await (const data of child.stderr) { - console.error(`[child]: ${data}`); - logs.push(data); - } + let apiIsHealthy = false; const childProcessPromise = new Promise((resolve, reject) => { child.once("exit", (code: number, signal: NodeJS.Signals) => { - if (code === 0) { + t.comment(`EVENT:exit child process exited gracefully.`); + t.comment(`EVENT:exit signal=${signal}`); + t.comment(`EVENT:exit exitCode=${code}`); + t.comment(`EVENT:exit apiIsHealthy=${apiIsHealthy}`); + if (apiIsHealthy) { resolve(); } else { const msg = `Child process crashed. exitCode=${code}, signal=${signal}`; reject(new Error(msg)); } - t.comment(`EVENT:exit signal=${signal}`); - t.comment(`EVENT:exit exitCode=${code}`); }); child.on("close", (code: number, signal: NodeJS.Signals) => { t.comment(`EVENT:close signal=${signal}`); @@ -62,7 +88,28 @@ test.skip("Supply chain backend API calls can be executed", async (t: Test) => { }); }); + const logs = []; + for await (const data of child.stdout) { + console.log(`[child]: ${data}`); + if (data.includes("Cactus API reachable http")) { + console.log("Sending kill signal to child process..."); + apiIsHealthy = true; + const killedOK = child.kill("SIGKILL"); + console.log("Sent kill signal, success=%o", killedOK); + + const processMap = await psFilter(uuid); + processMap.forEach((v, k) => { + console.log("Killing sub-process with pid: %o (cmd=%o) ", k, v); + process.kill(k); + }); + break; + } + logs.push(data); + } + await t.doesNotReject(childProcessPromise, "childProcessPromise resolves OK"); t.end(); + + process.exit(0); }); diff --git a/examples/cactus-example-supply-chain-backend/supervisord.conf b/examples/cactus-example-supply-chain-backend/supervisord.conf index 8b1281e717..b83ad6e702 100644 --- a/examples/cactus-example-supply-chain-backend/supervisord.conf +++ b/examples/cactus-example-supply-chain-backend/supervisord.conf @@ -13,7 +13,7 @@ stderr_logfile=/usr/src/app/log/dockerd.err.log stdout_logfile=/usr/src/app/log/dockerd.out.log [program:supply-chain-app] -command=/home/appuser/.nvm/versions/node/v16.14.2/bin/node /usr/src/app/node_modules/@hyperledger/cactus-example-supply-chain-backend/dist/lib/main/typescript/supply-chain-app-cli.js +command=/home/appuser/.nvm/versions/node/v18.18.2/bin/node /usr/src/app/node_modules/@hyperledger/cactus-example-supply-chain-backend/dist/lib/main/typescript/supply-chain-app-cli.js autostart=true autorestart=unexpected startretries=20 diff --git a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts index 6b651b7f17..0159292185 100644 --- a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts +++ b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts @@ -104,7 +104,7 @@ export class InsertShipmentEndpoint implements IWebServiceEndpoint { channelName: "mychannel", contractName: "shipment", invocationType: FabricContractInvocationType.Send, - methodName: "insertShipment", + methodName: "InsertShipment", params: [shipment.id, shipment.bookshelfId], }; const { diff --git a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts index 22c3c7c461..0e9808bb22 100644 --- a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts +++ b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts @@ -7,6 +7,7 @@ import { LoggerProvider, IAsyncProvider, safeStringifyException, + Strings, } from "@hyperledger/cactus-common"; import { IEndpointAuthzOptions, @@ -103,13 +104,16 @@ export class ListShipmentEndpoint implements IWebServiceEndpoint { channelName: "mychannel", contractName: "shipment", invocationType: FabricContractInvocationType.Call, - methodName: "getListShipment", + methodName: "GetListShipment", params: [], }; const { - data: { functionOutput }, + data: { functionOutput: fnOutJsonRaw }, } = await this.opts.fabricApi.runTransactionV1(request); - const output = JSON.parse(functionOutput); + // The functionOutput might come back as an empty string which fails to + // JSON parse and if that happens we just massage it into an empty array. + const fnOutJson = Strings.isNonBlank(fnOutJsonRaw) ? fnOutJsonRaw : "[]"; + const output = JSON.parse(fnOutJson); const body = { data: output }; res.status(200); res.json(body);