diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 460ca6e5f33..f614d7d315e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2157,6 +2157,39 @@ jobs: node-version: ${{ env.NODEJS_VERSION }} - uses: actions/checkout@v4.1.7 + - id: yarn-cache + name: Restore Yarn Cache + uses: actions/cache@v4.0.2 + with: + key: ${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }} + path: ./.yarn/ + restore-keys: | + ${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }} + - run: ./tools/ci.sh + if: ${{ env.RUN_CODE_COVERAGE == 'true' }} + - name: Upload coverage reports as artifacts + uses: actions/upload-artifact@v4.3.3 + with: + name: coverage-reports-36 + path: ./code-coverage-ts/**/ + cp-consortium-static: + continue-on-error: false + env: + FULL_BUILD_DISABLED: true + JEST_TEST_RUNNER_DISABLED: false + JEST_TEST_PATTERN: packages/cacti-plugin-consortium-static/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts + JEST_TEST_COVERAGE_PATH: ./code-coverage-ts/cp-consortium-static + JEST_TEST_CODE_COVERAGE_ENABLED: true + TAPE_TEST_RUNNER_DISABLED: true + needs: build-dev + runs-on: ubuntu-22.04 + steps: + - name: Use Node.js ${{ env.NODEJS_VERSION }} + uses: actions/setup-node@v4.0.3 + with: + node-version: ${{ env.NODEJS_VERSION }} + - uses: actions/checkout@v4.1.7 + - id: yarn-cache name: Restore Yarn Cache uses: actions/cache@v4.0.2 @@ -2205,7 +2238,6 @@ jobs: with: name: coverage-reports-37 path: ./code-coverage-ts/**/ - ct-cmd-api-server: continue-on-error: false needs: diff --git a/packages/cacti-plugin-consortium-static/.gitignore b/packages/cacti-plugin-consortium-static/.gitignore new file mode 100644 index 00000000000..ce4bacc38a3 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/.gitignore @@ -0,0 +1,2 @@ +cactus-openapi-spec-plugin-consortium-manual.json +src/main/typescript/generated/openapi/typescript-axios/.npmignore \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/README.md b/packages/cacti-plugin-consortium-static/README.md new file mode 100644 index 00000000000..f879b40501f --- /dev/null +++ b/packages/cacti-plugin-consortium-static/README.md @@ -0,0 +1,56 @@ +# `@hyperledger/cacti-plugin-consortium-static` + +## Cacti Consortium Static + +This plugin is an improvement of the package /cactus-plugin-consortium-manual ,bringing some new features to the table while conserving the possibility to be used as the old one (not allowing runtime changes) + +### Add Nodes to Consortium + +It is possible to add a new node to the consortium using the api of the plugin. + +New nodes need to belong/be certified by one of the organizations that are part of the consortium. On creating the consortium, it is required to specify the public keys of the organizations that are part of the consortium. When a new node requests to join, the request carries a jwt token signed by the organization it is tied to, which serves as proof that the organization reccognises the new node identity. + +When a new node submits a request to join, the receiving node verifies the request and broadcasts it (or not, depending on the verification) to the remaining nodes in the consortium. There is no consensus or reliable broadcast implemented. All the other nodes submit the request to the same verification process. If, for some reason, there are disparities in the consortiumDatabases of each node, either it is due to a network issue (broadcast did not reach destination) or due to malfunction of some node. + + +### Consortium Repository + +In addition to the default consortium repository (in cactus-core), the new repository includes data about the Node the repository belongs to: +```typescript +//data about self + private readonly node: CactusNode; + private readonly ledgers: Ledger[]; + private readonly pluginInstances: PluginInstance[]; + private readonly memberId: string; +``` +It also includes the root PolicyGroup of the consortium (explained in next section), and the common configurations of the packages deployed by nodes within the consortium. + +We do not verify if the nodes actually apply these configurations and policies, the information so far is used just to check that nodes have knowledge of this settings. Compliance or not is at the responsibility of each node, and to be verified if necessary by other means. + +To verify new nodes have the same policies and package configs as the others already in the consortium, we deterministically build two merkle trees (one with each info), concat both roots, and each node verifies the result against their own policies and package common configs. + +As a result of this proccess, nodes with divergent policies and configs are not accepted in the consortium (we assume all nodes are correctly configured when the network is created). + +### Policy Model + +We introduce in this package a proposal of a general-purpose policy model based in work done by the IETF: Core Policy Framework [RFC3060](https://www.rfc-editor.org/rfc/rfc3060). + +The model (simplified version) can be viewed in the policy-model directory. + +As a brief description, we group PolicyRules in PolicyGroups. PolicyGroups contain PolicyRules and possibly other PolicyGroups. A PolicyRule is composed by a PolicyCondition (constraint to be verified prior to applying the policy) and a PolicyAction (action to be applied). + +Below a simplified UML with the relationships between the classes: + +![policy model uml](https://github.com/eduv09/images/blob/main/policy-model-uml.jpg) + +The consortium information needs to hold only the root policyGroup (others are reached going down in the hierarchy). Each PolicyGroup has a Role. Roles identify the scope of the policy, so a PolicyRule has a set of Roles (role of the group it belongs to, and groups higher in the hierarchy). + +The model is in an early stage, and serves only as a POC for now. The goal is to refine it, and possibly move it to cactus-core once if it is accepted by the community as a advantageous feature. It is possible to create consortium without any policy rule or group defined. + + + +## Notes + +Please reffer to package "@hyperledger/cactus-plugin-consortium-manual" as the documentation there applies to this one, namely information about the Prometheus Exporter. + +For usage, check the tests in the /integration folder \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/openapitools.json b/packages/cacti-plugin-consortium-static/openapitools.json new file mode 100644 index 00000000000..225ff1aaaee --- /dev/null +++ b/packages/cacti-plugin-consortium-static/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.6.0" + } +} diff --git a/packages/cacti-plugin-consortium-static/package.json b/packages/cacti-plugin-consortium-static/package.json new file mode 100644 index 00000000000..34969fc645e --- /dev/null +++ b/packages/cacti-plugin-consortium-static/package.json @@ -0,0 +1,98 @@ +{ + "name": "@hyperledger/cacti-plugin-consortium-static", + "version": "2.0.0-rc.3", + "description": "A web service plugin that provides management capabilities on a Cactus consortium as a whole for administrative purposes.", + "keywords": [ + "Hyperledger", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "homepage": "https://github.com/hyperledger/cacti#readme", + "bugs": { + "url": "https://github.com/hyperledger/cacti/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cacti.git" + }, + "license": "Apache-2.0", + "author": { + "name": "Hyperledger Cactus Contributors", + "email": "cactus@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cacti" + }, + "contributors": [ + { + "name": "Eduardo Vasques", + "email": "eduardovasques10@tecnico.ulisboa.pt", + "url": "https://example.com" + }, + { + "name": "Peter Somogyvari", + "email": "peter.somogyvari@accenture.com", + "url": "https://accenture.com" + } + ], + "main": "dist/lib/main/typescript/index.js", + "module": "dist/lib/main/typescript/index.js", + "browser": "dist/cacti-plugin-consortium-static.web.umd.js", + "types": "dist/lib/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "codegen": "run-p 'codegen:*'", + "codegen:openapi": "npm run generate-sdk", + "generate-sdk": "run-p 'generate-sdk:*'", + "generate-sdk:kotlin": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g kotlin -o ./src/main/kotlin/generated/openapi/kotlin-client/ --reserved-words-mappings protected=protected --ignore-file-override ../../openapi-generator-ignore", + "generate-sdk:typescript-axios": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/ --reserved-words-mappings protected=protected --ignore-file-override ../../openapi-generator-ignore", + "watch": "npm-watch", + "webpack": "npm-run-all webpack:dev", + "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", + "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", + "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js" + }, + "dependencies": { + "@hyperledger/cactus-common": "2.0.0-rc.3", + "@hyperledger/cactus-core": "2.0.0-rc.3", + "@hyperledger/cactus-core-api": "2.0.0-rc.3", + "axios": "1.6.0", + "body-parser": "1.20.2", + "express": "4.19.2", + "http-errors-enhanced-cjs": "2.0.1", + "jose": "4.15.5", + "merkletreejs": "0.4.0", + "prom-client": "15.1.3", + "safe-stable-stringify": "2.4.3", + "typescript-optional": "2.0.1", + "uuid": "10.0.0" + }, + "devDependencies": { + "@hyperledger/cactus-api-client": "2.0.0-rc.3", + "@hyperledger/cactus-cmd-api-server": "2.0.0-rc.3", + "@hyperledger/cactus-plugin-ledger-connector-besu": "2.0.0-rc.3", + "@hyperledger/cactus-test-tooling": "2.0.0-rc.3", + "@types/express": "4.17.21", + "@types/json-stable-stringify": "1.0.33", + "@types/uuid": "10.0.0", + "web3": "1.6.1" + }, + "engines": { + "node": ">=18", + "npm": ">=8" + }, + "publishConfig": { + "access": "public" + }, + "browserMinified": "dist/cacti-plugin-consortium-static.web.umd.min.js", + "mainMinified": "dist/cacti-plugin-consortium-static.node.umd.min.js", + "watch": { + "codegen:openapi": { + "patterns": [ + "./src/main/json/openapi.json" + ] + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/json/openapi.json b/packages/cacti-plugin-consortium-static/src/main/json/openapi.json new file mode 100644 index 00000000000..1e698575cd0 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/json/openapi.json @@ -0,0 +1,505 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cactus Plugin - Consortium Web Service", + "description": "Manage a Cactus consortium through the APIs. Needs administrative privileges.", + "version": "2.0.0-rc.2", + "license": { + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "components": { + "schemas": { + "Identity": { + "type": "object", + "description": "Identity object of a Cacti Node", + "required": ["pubKey", "memberId", "proof"], + "properties": { + "pubKey": { + "description": "Public key of the Node", + "type": "string", + "nullable": false + }, + "memberId": { + "description": "memberId of the organization the node belongs to", + "type": "string", + "nullable": false + }, + "proof": { + "description": "JWT generated by the organization the node belongs to", + "type": "string", + "nullable": false + } + } + }, + "NewNodeRequestV1": { + "type": "object", + "required": [ + "identity", + "treeHash", + "node", + "ledger", + "pluginInstance", + "signature" + ], + "properties": { + "identity": { + "$ref": "#/components/schemas/Identity", + "nullable": false + }, + "treeHash": { + "description": "proof of common configs and policies defined by the consortium", + "type": "string", + "nullable": false + }, + "node": { + "description": "A Cacti Node", + "$ref": "#/components/schemas/CactusNode", + "nullable": false + }, + "ledger": { + "description": "Ledger metadata", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ledger", + "nullable": false + }, + "default": [] + }, + "pluginInstance": { + "description": "Plugin Instance metadata", + "items": { + "$ref": "#/components/schemas/PluginInstance", + "nullable": false + }, + "default": [] + }, + "signature": { + "description": "signature of the message", + "type": "string", + "nullable": false + } + } + }, + "PrimaryKey": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "nullable": false + }, + "ConsortiumMemberId": { + "$ref": "#/components/schemas/PrimaryKey", + "description": "ID of Consortium member who operates the ledger (if any). Defined as an optional property in case the ledger is a permissionless and/or public one such as the Bitcoin or Ethereum mainnets." + }, + "CactusNodeId": { + "$ref": "#/components/schemas/PrimaryKey", + "description": "ID of a Cactus node that must uniquely distinguish it from all other Cactus nodes within a Consortium. Note that API server instances do not have their own identity the way a node does." + }, + "ConsortiumId": { + "$ref": "#/components/schemas/PrimaryKey" + }, + "LedgerId": { + "description": "String that uniquely identifies a ledger within a Cactus consortium so that transactions can be routed to the correct ledger.", + "$ref": "#/components/schemas/PrimaryKey" + }, + "PluginInstanceId": { + "description": "String that uniquely identifies a plugin instance within a Cactus consortium so that requests can be addressed/routed directly to individual plugins when necessary.", + "$ref": "#/components/schemas/PrimaryKey" + }, + "Ledger": { + "type": "object", + "required": ["id", "ledgerType"], + "properties": { + "id": { + "$ref": "#/components/schemas/LedgerId" + }, + "ledgerType": { + "$ref": "#/components/schemas/LedgerType", + "nullable": false + }, + "consortiumMemberId": { + "$ref": "#/components/schemas/ConsortiumMemberId" + } + } + }, + "LedgerType": { + "description": "Enumerates the different ledger vendors and their major versions encoded within the name of the LedgerType. For example \"BESU_1X\" involves all of the [1.0.0;2.0.0) where 1.0.0 is included and anything up until, but not 2.0.0. See: https://stackoverflow.com/a/4396303/698470 for further explanation.", + "type": "string", + "enum": [ + "BESU_1X", + "BESU_2X", + "BURROW_0X", + "CORDA_4X", + "FABRIC_2", + "SAWTOOTH_1X" + ] + }, + "CactusNodeMeta": { + "description": "A Cactus node meta information", + "type": "object", + "required": ["nodeApiHost", "publicKeyPem"], + "properties": { + "nodeApiHost": { + "type": "string", + "minLength": 1, + "maxLength": 1024, + "nullable": false + }, + "publicKeyPem": { + "description": "The PEM encoded public key that was used to generate the JWS included in the response (the jws property)", + "type": "string", + "minLength": 1, + "maxLength": 65535, + "nullable": false, + "format": "Must only contain the public key, never include here the PEM that also contains a private key. See PEM format: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail" + } + } + }, + "CactusNode": { + "description": "A Cactus node can be a single server, or a set of servers behind a load balancer acting as one.", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/CactusNodeMeta" + }, + { + "type": "object", + "required": [ + "id", + "consortiumId", + "nodeApiHost", + "memberId", + "publicKeyPem", + "pluginInstanceIds", + "ledgerIds" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/CactusNodeId", + "example": "809a76ba-cfb8-4045-a5c6-ed70a7314c25" + }, + "consortiumId": { + "$ref": "#/components/schemas/ConsortiumId", + "description": "ID of the Cactus Consortium this node is in.", + "example": "3e2670d9-2d14-45bd-96f5-33e2c4b4e3fb" + }, + "memberId": { + "$ref": "#/components/schemas/ConsortiumMemberId", + "example": "b3674a28-e442-4feb-b1f3-8cbe46c20e5e" + }, + "ledgerIds": { + "description": "Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions.", + "type": "array", + "nullable": false, + "minItems": 0, + "maxItems": 2048, + "default": [], + "items": { + "$ref": "#/components/schemas/LedgerId" + } + }, + "pluginInstanceIds": { + "type": "array", + "nullable": false, + "minItems": 0, + "maxItems": 2048, + "default": [], + "items": { + "$ref": "#/components/schemas/PluginInstanceId" + } + } + } + } + ] + }, + "PluginInstance": { + "type": "object", + "required": ["id", "packageName"], + "properties": { + "id": { + "$ref": "#/components/schemas/PluginInstanceId" + }, + "packageName": { + "type": "string", + "minLength": 1, + "maxLength": 4096, + "nullable": false + } + } + }, + "BroadcastRequestV1": { + "type": "object", + "required": ["message", "signature"], + "properties": { + "message": { + "type": "object", + "nullable": false, + "required": ["message", "pubKey"], + "properties": { + "message": { + "$ref": "#/components/schemas/NewNodeRequestV1" + }, + "pubKey": { + "type": "string", + "nullable": false + } + } + }, + "signature": { + "type": "string", + "nullable": false + } + } + }, + "BroadcastResponseV1": { + "type": "object", + "nullable": false, + "required": ["result"], + "properties": { + "result": { + "type": "boolean", + "nullable": false + } + } + }, + "GetNodeJwsResponse": { + "type": "object", + "required": ["jws"], + "properties": { + "jws": { + "description": "The JSON Web Signature of the Cactus node.", + "$ref": "#/components/schemas/JWSGeneral", + "nullable": false + } + } + }, + "GetConsortiumJwsResponse": { + "type": "object", + "required": ["jws"], + "properties": { + "jws": { + "description": "The JSON Web Signature of the Cactus consortium.", + "$ref": "#/components/schemas/JWSGeneral", + "nullable": false, + "format": "The general format which is a JSON object, not a string." + } + } + }, + "PrometheusExporterMetricsResponse": { + "type": "string", + "nullable": false + }, + "GetNodeJwsRequest": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "GetConsortiumJwsRequest": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "JWSRecipient": { + "description": "A JSON Web Signature. See: https://tools.ietf.org/html/rfc7515 for info about standard.", + "type": "object", + "required": ["signature"], + "properties": { + "signature": { + "type": "string" + }, + "protected": { + "type": "string" + }, + "header": { + "type": "object", + "additionalProperties": true + } + } + }, + "JWSGeneral": { + "type": "object", + "required": ["payload", "signatures"], + "properties": { + "payload": { + "type": "string", + "minLength": 1, + "maxLength": 65535 + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JWSRecipient" + } + } + } + } + } + }, + "paths": { + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws" + } + }, + "operationId": "getConsortiumJwsV1", + "summary": "Retrieves a consortium JWS", + "description": "The JWS asserting the consortium metadata (pub keys and hosts of nodes)", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetConsortiumJwsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetConsortiumJwsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws" + } + }, + "operationId": "getNodeJwsV1", + "summary": "Retrieves the JWT of a Cactus Node", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNodeJwsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNodeJwsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics": { + "get": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "get", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics" + } + }, + "operationId": "getPrometheusMetricsV1", + "summary": "Get the Prometheus Metrics", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PrometheusExporterMetricsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node" + } + }, + "operationId": "addNodeToConsortiumV1", + "summary": "Adds a node to consortium JWS", + "description": "", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewNodeRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetConsortiumJwsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast" + } + }, + "operationId": "receiveBroadcastV1", + "summary": "Adds a node to consortium JWS", + "description": "", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BroadcastRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BroadcastResponseV1" + } + } + } + } + } + } + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/json/openapi.tpl.json b/packages/cacti-plugin-consortium-static/src/main/json/openapi.tpl.json new file mode 100644 index 00000000000..1e698575cd0 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/json/openapi.tpl.json @@ -0,0 +1,505 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cactus Plugin - Consortium Web Service", + "description": "Manage a Cactus consortium through the APIs. Needs administrative privileges.", + "version": "2.0.0-rc.2", + "license": { + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "components": { + "schemas": { + "Identity": { + "type": "object", + "description": "Identity object of a Cacti Node", + "required": ["pubKey", "memberId", "proof"], + "properties": { + "pubKey": { + "description": "Public key of the Node", + "type": "string", + "nullable": false + }, + "memberId": { + "description": "memberId of the organization the node belongs to", + "type": "string", + "nullable": false + }, + "proof": { + "description": "JWT generated by the organization the node belongs to", + "type": "string", + "nullable": false + } + } + }, + "NewNodeRequestV1": { + "type": "object", + "required": [ + "identity", + "treeHash", + "node", + "ledger", + "pluginInstance", + "signature" + ], + "properties": { + "identity": { + "$ref": "#/components/schemas/Identity", + "nullable": false + }, + "treeHash": { + "description": "proof of common configs and policies defined by the consortium", + "type": "string", + "nullable": false + }, + "node": { + "description": "A Cacti Node", + "$ref": "#/components/schemas/CactusNode", + "nullable": false + }, + "ledger": { + "description": "Ledger metadata", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ledger", + "nullable": false + }, + "default": [] + }, + "pluginInstance": { + "description": "Plugin Instance metadata", + "items": { + "$ref": "#/components/schemas/PluginInstance", + "nullable": false + }, + "default": [] + }, + "signature": { + "description": "signature of the message", + "type": "string", + "nullable": false + } + } + }, + "PrimaryKey": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "nullable": false + }, + "ConsortiumMemberId": { + "$ref": "#/components/schemas/PrimaryKey", + "description": "ID of Consortium member who operates the ledger (if any). Defined as an optional property in case the ledger is a permissionless and/or public one such as the Bitcoin or Ethereum mainnets." + }, + "CactusNodeId": { + "$ref": "#/components/schemas/PrimaryKey", + "description": "ID of a Cactus node that must uniquely distinguish it from all other Cactus nodes within a Consortium. Note that API server instances do not have their own identity the way a node does." + }, + "ConsortiumId": { + "$ref": "#/components/schemas/PrimaryKey" + }, + "LedgerId": { + "description": "String that uniquely identifies a ledger within a Cactus consortium so that transactions can be routed to the correct ledger.", + "$ref": "#/components/schemas/PrimaryKey" + }, + "PluginInstanceId": { + "description": "String that uniquely identifies a plugin instance within a Cactus consortium so that requests can be addressed/routed directly to individual plugins when necessary.", + "$ref": "#/components/schemas/PrimaryKey" + }, + "Ledger": { + "type": "object", + "required": ["id", "ledgerType"], + "properties": { + "id": { + "$ref": "#/components/schemas/LedgerId" + }, + "ledgerType": { + "$ref": "#/components/schemas/LedgerType", + "nullable": false + }, + "consortiumMemberId": { + "$ref": "#/components/schemas/ConsortiumMemberId" + } + } + }, + "LedgerType": { + "description": "Enumerates the different ledger vendors and their major versions encoded within the name of the LedgerType. For example \"BESU_1X\" involves all of the [1.0.0;2.0.0) where 1.0.0 is included and anything up until, but not 2.0.0. See: https://stackoverflow.com/a/4396303/698470 for further explanation.", + "type": "string", + "enum": [ + "BESU_1X", + "BESU_2X", + "BURROW_0X", + "CORDA_4X", + "FABRIC_2", + "SAWTOOTH_1X" + ] + }, + "CactusNodeMeta": { + "description": "A Cactus node meta information", + "type": "object", + "required": ["nodeApiHost", "publicKeyPem"], + "properties": { + "nodeApiHost": { + "type": "string", + "minLength": 1, + "maxLength": 1024, + "nullable": false + }, + "publicKeyPem": { + "description": "The PEM encoded public key that was used to generate the JWS included in the response (the jws property)", + "type": "string", + "minLength": 1, + "maxLength": 65535, + "nullable": false, + "format": "Must only contain the public key, never include here the PEM that also contains a private key. See PEM format: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail" + } + } + }, + "CactusNode": { + "description": "A Cactus node can be a single server, or a set of servers behind a load balancer acting as one.", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/CactusNodeMeta" + }, + { + "type": "object", + "required": [ + "id", + "consortiumId", + "nodeApiHost", + "memberId", + "publicKeyPem", + "pluginInstanceIds", + "ledgerIds" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/CactusNodeId", + "example": "809a76ba-cfb8-4045-a5c6-ed70a7314c25" + }, + "consortiumId": { + "$ref": "#/components/schemas/ConsortiumId", + "description": "ID of the Cactus Consortium this node is in.", + "example": "3e2670d9-2d14-45bd-96f5-33e2c4b4e3fb" + }, + "memberId": { + "$ref": "#/components/schemas/ConsortiumMemberId", + "example": "b3674a28-e442-4feb-b1f3-8cbe46c20e5e" + }, + "ledgerIds": { + "description": "Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions.", + "type": "array", + "nullable": false, + "minItems": 0, + "maxItems": 2048, + "default": [], + "items": { + "$ref": "#/components/schemas/LedgerId" + } + }, + "pluginInstanceIds": { + "type": "array", + "nullable": false, + "minItems": 0, + "maxItems": 2048, + "default": [], + "items": { + "$ref": "#/components/schemas/PluginInstanceId" + } + } + } + } + ] + }, + "PluginInstance": { + "type": "object", + "required": ["id", "packageName"], + "properties": { + "id": { + "$ref": "#/components/schemas/PluginInstanceId" + }, + "packageName": { + "type": "string", + "minLength": 1, + "maxLength": 4096, + "nullable": false + } + } + }, + "BroadcastRequestV1": { + "type": "object", + "required": ["message", "signature"], + "properties": { + "message": { + "type": "object", + "nullable": false, + "required": ["message", "pubKey"], + "properties": { + "message": { + "$ref": "#/components/schemas/NewNodeRequestV1" + }, + "pubKey": { + "type": "string", + "nullable": false + } + } + }, + "signature": { + "type": "string", + "nullable": false + } + } + }, + "BroadcastResponseV1": { + "type": "object", + "nullable": false, + "required": ["result"], + "properties": { + "result": { + "type": "boolean", + "nullable": false + } + } + }, + "GetNodeJwsResponse": { + "type": "object", + "required": ["jws"], + "properties": { + "jws": { + "description": "The JSON Web Signature of the Cactus node.", + "$ref": "#/components/schemas/JWSGeneral", + "nullable": false + } + } + }, + "GetConsortiumJwsResponse": { + "type": "object", + "required": ["jws"], + "properties": { + "jws": { + "description": "The JSON Web Signature of the Cactus consortium.", + "$ref": "#/components/schemas/JWSGeneral", + "nullable": false, + "format": "The general format which is a JSON object, not a string." + } + } + }, + "PrometheusExporterMetricsResponse": { + "type": "string", + "nullable": false + }, + "GetNodeJwsRequest": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "GetConsortiumJwsRequest": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "JWSRecipient": { + "description": "A JSON Web Signature. See: https://tools.ietf.org/html/rfc7515 for info about standard.", + "type": "object", + "required": ["signature"], + "properties": { + "signature": { + "type": "string" + }, + "protected": { + "type": "string" + }, + "header": { + "type": "object", + "additionalProperties": true + } + } + }, + "JWSGeneral": { + "type": "object", + "required": ["payload", "signatures"], + "properties": { + "payload": { + "type": "string", + "minLength": 1, + "maxLength": 65535 + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JWSRecipient" + } + } + } + } + } + }, + "paths": { + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws" + } + }, + "operationId": "getConsortiumJwsV1", + "summary": "Retrieves a consortium JWS", + "description": "The JWS asserting the consortium metadata (pub keys and hosts of nodes)", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetConsortiumJwsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetConsortiumJwsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws" + } + }, + "operationId": "getNodeJwsV1", + "summary": "Retrieves the JWT of a Cactus Node", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNodeJwsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNodeJwsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics": { + "get": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "get", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics" + } + }, + "operationId": "getPrometheusMetricsV1", + "summary": "Get the Prometheus Metrics", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PrometheusExporterMetricsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node" + } + }, + "operationId": "addNodeToConsortiumV1", + "summary": "Adds a node to consortium JWS", + "description": "", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewNodeRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetConsortiumJwsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast" + } + }, + "operationId": "receiveBroadcastV1", + "summary": "Adds a node to consortium JWS", + "description": "", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BroadcastRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BroadcastResponseV1" + } + } + } + } + } + } + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES new file mode 100644 index 00000000000..e009b9122d2 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES @@ -0,0 +1,38 @@ +README.md +build.gradle +gradlew +gradlew.bat +settings.gradle +src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt +src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt +src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +src/main/kotlin/org/openapitools/client/infrastructure/ApiResponse.kt +src/main/kotlin/org/openapitools/client/infrastructure/BigDecimalAdapter.kt +src/main/kotlin/org/openapitools/client/infrastructure/BigIntegerAdapter.kt +src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt +src/main/kotlin/org/openapitools/client/infrastructure/Errors.kt +src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt +src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt +src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt +src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt +src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt +src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt +src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt +src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt +src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt +src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt +src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1.kt +src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1Message.kt +src/main/kotlin/org/openapitools/client/models/BroadcastResponseV1.kt +src/main/kotlin/org/openapitools/client/models/CactusNode.kt +src/main/kotlin/org/openapitools/client/models/CactusNodeAllOf.kt +src/main/kotlin/org/openapitools/client/models/CactusNodeMeta.kt +src/main/kotlin/org/openapitools/client/models/GetConsortiumJwsResponse.kt +src/main/kotlin/org/openapitools/client/models/GetNodeJwsResponse.kt +src/main/kotlin/org/openapitools/client/models/Identity.kt +src/main/kotlin/org/openapitools/client/models/JWSGeneral.kt +src/main/kotlin/org/openapitools/client/models/JWSRecipient.kt +src/main/kotlin/org/openapitools/client/models/Ledger.kt +src/main/kotlin/org/openapitools/client/models/LedgerType.kt +src/main/kotlin/org/openapitools/client/models/NewNodeRequestV1.kt +src/main/kotlin/org/openapitools/client/models/PluginInstance.kt diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/VERSION b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/VERSION new file mode 100644 index 00000000000..cd802a1ec4e --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.6.0 \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/README.md b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/README.md new file mode 100644 index 00000000000..3dc25042e1e --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/README.md @@ -0,0 +1,78 @@ +# org.openapitools.client - Kotlin client library for Hyperledger Cactus Plugin - Consortium Web Service + +Manage a Cactus consortium through the APIs. Needs administrative privileges. + +## Overview +This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate an API client. + +- API version: 2.0.0-rc.2 +- Package version: +- Build package: org.openapitools.codegen.languages.KotlinClientCodegen + +## Requires + +* Kotlin 1.7.21 +* Gradle 7.5 + +## Build + +First, create the gradle wrapper script: + +``` +gradle wrapper +``` + +Then, run: + +``` +./gradlew check assemble +``` + +This runs all tests and packages the library. + +## Features/Implementation Notes + +* Supports JSON inputs/outputs, File inputs, and Form inputs. +* Supports collection formats for query parameters: csv, tsv, ssv, pipes. +* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions. +* Implementation of ApiClient is intended to reduce method counts, specifically to benefit Android targets. + + +## Documentation for API Endpoints + +All URIs are relative to *http://localhost* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*DefaultApi* | [**addNodeToConsortiumV1**](docs/DefaultApi.md#addnodetoconsortiumv1) | **POST** /api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node | Adds a node to consortium JWS +*DefaultApi* | [**getConsortiumJwsV1**](docs/DefaultApi.md#getconsortiumjwsv1) | **POST** /api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws | Retrieves a consortium JWS +*DefaultApi* | [**getNodeJwsV1**](docs/DefaultApi.md#getnodejwsv1) | **POST** /api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws | Retrieves the JWT of a Cactus Node +*DefaultApi* | [**getPrometheusMetricsV1**](docs/DefaultApi.md#getprometheusmetricsv1) | **GET** /api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics | Get the Prometheus Metrics +*DefaultApi* | [**receiveBroadcastV1**](docs/DefaultApi.md#receivebroadcastv1) | **POST** /api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast | Adds a node to consortium JWS + + + +## Documentation for Models + + - [org.openapitools.client.models.BroadcastRequestV1](docs/BroadcastRequestV1.md) + - [org.openapitools.client.models.BroadcastRequestV1Message](docs/BroadcastRequestV1Message.md) + - [org.openapitools.client.models.BroadcastResponseV1](docs/BroadcastResponseV1.md) + - [org.openapitools.client.models.CactusNode](docs/CactusNode.md) + - [org.openapitools.client.models.CactusNodeAllOf](docs/CactusNodeAllOf.md) + - [org.openapitools.client.models.CactusNodeMeta](docs/CactusNodeMeta.md) + - [org.openapitools.client.models.GetConsortiumJwsResponse](docs/GetConsortiumJwsResponse.md) + - [org.openapitools.client.models.GetNodeJwsResponse](docs/GetNodeJwsResponse.md) + - [org.openapitools.client.models.Identity](docs/Identity.md) + - [org.openapitools.client.models.JWSGeneral](docs/JWSGeneral.md) + - [org.openapitools.client.models.JWSRecipient](docs/JWSRecipient.md) + - [org.openapitools.client.models.Ledger](docs/Ledger.md) + - [org.openapitools.client.models.LedgerType](docs/LedgerType.md) + - [org.openapitools.client.models.NewNodeRequestV1](docs/NewNodeRequestV1.md) + - [org.openapitools.client.models.PluginInstance](docs/PluginInstance.md) + + + +## Documentation for Authorization + +Endpoints do not require authorization. + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/build.gradle b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/build.gradle new file mode 100644 index 00000000000..66a3c68b890 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/build.gradle @@ -0,0 +1,38 @@ +group 'org.openapitools' +version '1.0.0' + +wrapper { + gradleVersion = '7.5' + distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" +} + +buildscript { + ext.kotlin_version = '1.7.21' + + repositories { + maven { url "https://repo1.maven.org/maven2" } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'kotlin' +apply plugin: 'maven-publish' + +repositories { + maven { url "https://repo1.maven.org/maven2" } +} + +test { + useJUnitPlatform() +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + implementation "com.squareup.moshi:moshi-kotlin:1.13.0" + implementation "com.squareup.moshi:moshi-adapters:1.13.0" + implementation "com.squareup.okhttp3:okhttp:4.10.0" + testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2" +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/gradlew b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/gradlew new file mode 100644 index 00000000000..aeb74cbb43e --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/gradlew.bat b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/gradlew.bat new file mode 100644 index 00000000000..93e3f59f135 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/settings.gradle b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/settings.gradle new file mode 100644 index 00000000000..391dcea81db --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/settings.gradle @@ -0,0 +1,2 @@ + +rootProject.name = 'kotlin-client' \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt new file mode 100644 index 00000000000..5117aa63107 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt @@ -0,0 +1,410 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.apis + +import java.io.IOException +import okhttp3.OkHttpClient +import okhttp3.HttpUrl + +import org.openapitools.client.models.BroadcastRequestV1 +import org.openapitools.client.models.BroadcastResponseV1 +import org.openapitools.client.models.GetConsortiumJwsResponse +import org.openapitools.client.models.GetNodeJwsResponse +import org.openapitools.client.models.NewNodeRequestV1 + +import com.squareup.moshi.Json + +import org.openapitools.client.infrastructure.ApiClient +import org.openapitools.client.infrastructure.ApiResponse +import org.openapitools.client.infrastructure.ClientException +import org.openapitools.client.infrastructure.ClientError +import org.openapitools.client.infrastructure.ServerException +import org.openapitools.client.infrastructure.ServerError +import org.openapitools.client.infrastructure.MultiValueMap +import org.openapitools.client.infrastructure.PartConfig +import org.openapitools.client.infrastructure.RequestConfig +import org.openapitools.client.infrastructure.RequestMethod +import org.openapitools.client.infrastructure.ResponseType +import org.openapitools.client.infrastructure.Success +import org.openapitools.client.infrastructure.toMultiValue + +class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient = ApiClient.defaultClient) : ApiClient(basePath, client) { + companion object { + @JvmStatic + val defaultBasePath: String by lazy { + System.getProperties().getProperty(ApiClient.baseUrlKey, "http://localhost") + } + } + + /** + * Adds a node to consortium JWS + * + * @param newNodeRequestV1 (optional) + * @return GetConsortiumJwsResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun addNodeToConsortiumV1(newNodeRequestV1: NewNodeRequestV1? = null) : GetConsortiumJwsResponse { + val localVarResponse = addNodeToConsortiumV1WithHttpInfo(newNodeRequestV1 = newNodeRequestV1) + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as GetConsortiumJwsResponse + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * Adds a node to consortium JWS + * + * @param newNodeRequestV1 (optional) + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun addNodeToConsortiumV1WithHttpInfo(newNodeRequestV1: NewNodeRequestV1?) : ApiResponse { + val localVariableConfig = addNodeToConsortiumV1RequestConfig(newNodeRequestV1 = newNodeRequestV1) + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation addNodeToConsortiumV1 + * + * @param newNodeRequestV1 (optional) + * @return RequestConfig + */ + fun addNodeToConsortiumV1RequestConfig(newNodeRequestV1: NewNodeRequestV1?) : RequestConfig { + val localVariableBody = newNodeRequestV1 + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/json" + localVariableHeaders["Accept"] = "application/json" + + return RequestConfig( + method = RequestMethod.POST, + path = "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node", + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + + /** + * Retrieves a consortium JWS + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @param body (optional) + * @return GetConsortiumJwsResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun getConsortiumJwsV1(body: kotlin.Any? = null) : GetConsortiumJwsResponse { + val localVarResponse = getConsortiumJwsV1WithHttpInfo(body = body) + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as GetConsortiumJwsResponse + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * Retrieves a consortium JWS + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @param body (optional) + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun getConsortiumJwsV1WithHttpInfo(body: kotlin.Any?) : ApiResponse { + val localVariableConfig = getConsortiumJwsV1RequestConfig(body = body) + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation getConsortiumJwsV1 + * + * @param body (optional) + * @return RequestConfig + */ + fun getConsortiumJwsV1RequestConfig(body: kotlin.Any?) : RequestConfig { + val localVariableBody = body + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/json" + localVariableHeaders["Accept"] = "application/json" + + return RequestConfig( + method = RequestMethod.POST, + path = "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws", + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + + /** + * Retrieves the JWT of a Cactus Node + * + * @param body (optional) + * @return GetNodeJwsResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun getNodeJwsV1(body: kotlin.Any? = null) : GetNodeJwsResponse { + val localVarResponse = getNodeJwsV1WithHttpInfo(body = body) + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as GetNodeJwsResponse + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * Retrieves the JWT of a Cactus Node + * + * @param body (optional) + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun getNodeJwsV1WithHttpInfo(body: kotlin.Any?) : ApiResponse { + val localVariableConfig = getNodeJwsV1RequestConfig(body = body) + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation getNodeJwsV1 + * + * @param body (optional) + * @return RequestConfig + */ + fun getNodeJwsV1RequestConfig(body: kotlin.Any?) : RequestConfig { + val localVariableBody = body + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/json" + localVariableHeaders["Accept"] = "application/json" + + return RequestConfig( + method = RequestMethod.POST, + path = "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws", + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + + /** + * Get the Prometheus Metrics + * + * @return kotlin.String + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun getPrometheusMetricsV1() : kotlin.String { + val localVarResponse = getPrometheusMetricsV1WithHttpInfo() + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as kotlin.String + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * Get the Prometheus Metrics + * + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun getPrometheusMetricsV1WithHttpInfo() : ApiResponse { + val localVariableConfig = getPrometheusMetricsV1RequestConfig() + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation getPrometheusMetricsV1 + * + * @return RequestConfig + */ + fun getPrometheusMetricsV1RequestConfig() : RequestConfig { + val localVariableBody = null + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + + return RequestConfig( + method = RequestMethod.GET, + path = "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics", + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + + /** + * Adds a node to consortium JWS + * + * @param broadcastRequestV1 (optional) + * @return BroadcastResponseV1 + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun receiveBroadcastV1(broadcastRequestV1: BroadcastRequestV1? = null) : BroadcastResponseV1 { + val localVarResponse = receiveBroadcastV1WithHttpInfo(broadcastRequestV1 = broadcastRequestV1) + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as BroadcastResponseV1 + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * Adds a node to consortium JWS + * + * @param broadcastRequestV1 (optional) + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun receiveBroadcastV1WithHttpInfo(broadcastRequestV1: BroadcastRequestV1?) : ApiResponse { + val localVariableConfig = receiveBroadcastV1RequestConfig(broadcastRequestV1 = broadcastRequestV1) + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation receiveBroadcastV1 + * + * @param broadcastRequestV1 (optional) + * @return RequestConfig + */ + fun receiveBroadcastV1RequestConfig(broadcastRequestV1: BroadcastRequestV1?) : RequestConfig { + val localVariableBody = broadcastRequestV1 + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/json" + localVariableHeaders["Accept"] = "application/json" + + return RequestConfig( + method = RequestMethod.POST, + path = "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast", + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + + + private fun encodeURIComponent(uriComponent: kotlin.String): kotlin.String = + HttpUrl.Builder().scheme("http").host("localhost").addPathSegment(uriComponent).build().encodedPathSegments[0] +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt new file mode 100644 index 00000000000..ef7a8f1e1a6 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt @@ -0,0 +1,23 @@ +package org.openapitools.client.infrastructure + +typealias MultiValueMap = MutableMap> + +fun collectionDelimiter(collectionFormat: String) = when(collectionFormat) { + "csv" -> "," + "tsv" -> "\t" + "pipe" -> "|" + "space" -> " " + else -> "" +} + +val defaultMultiValueConverter: (item: Any?) -> String = { item -> "$item" } + +fun toMultiValue(items: Array, collectionFormat: String, map: (item: T) -> String = defaultMultiValueConverter) + = toMultiValue(items.asIterable(), collectionFormat, map) + +fun toMultiValue(items: Iterable, collectionFormat: String, map: (item: T) -> String = defaultMultiValueConverter): List { + return when(collectionFormat) { + "multi" -> items.map(map) + else -> listOf(items.joinToString(separator = collectionDelimiter(collectionFormat), transform = map)) + } +} \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt new file mode 100644 index 00000000000..ea4b7b65935 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -0,0 +1,245 @@ +package org.openapitools.client.infrastructure + +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.FormBody +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.ResponseBody +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.Request +import okhttp3.Headers +import okhttp3.Headers.Companion.toHeaders +import okhttp3.MultipartBody +import okhttp3.Call +import okhttp3.Callback +import okhttp3.Response +import okhttp3.internal.EMPTY_REQUEST +import java.io.BufferedWriter +import java.io.File +import java.io.FileWriter +import java.io.IOException +import java.net.URLConnection +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime +import java.util.Locale +import com.squareup.moshi.adapter + +open class ApiClient(val baseUrl: String, val client: OkHttpClient = defaultClient) { + companion object { + protected const val ContentType = "Content-Type" + protected const val Accept = "Accept" + protected const val Authorization = "Authorization" + protected const val JsonMediaType = "application/json" + protected const val FormDataMediaType = "multipart/form-data" + protected const val FormUrlEncMediaType = "application/x-www-form-urlencoded" + protected const val XmlMediaType = "application/xml" + + val apiKey: MutableMap = mutableMapOf() + val apiKeyPrefix: MutableMap = mutableMapOf() + var username: String? = null + var password: String? = null + var accessToken: String? = null + const val baseUrlKey = "org.openapitools.client.baseUrl" + + @JvmStatic + val defaultClient: OkHttpClient by lazy { + builder.build() + } + + @JvmStatic + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + } + + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + protected fun guessContentTypeFromFile(file: File): String { + val contentType = URLConnection.guessContentTypeFromName(file.name) + return contentType ?: "application/octet-stream" + } + + protected inline fun requestBody(content: T, mediaType: String?): RequestBody = + when { + content is File -> content.asRequestBody((mediaType ?: guessContentTypeFromFile(content)).toMediaTypeOrNull()) + mediaType == FormDataMediaType -> + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .apply { + // content's type *must* be Map> + @Suppress("UNCHECKED_CAST") + (content as Map>).forEach { (name, part) -> + if (part.body is File) { + val partHeaders = part.headers.toMutableMap() + + ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"") + val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull() + addPart( + partHeaders.toHeaders(), + part.body.asRequestBody(fileMediaType) + ) + } else { + val partHeaders = part.headers.toMutableMap() + + ("Content-Disposition" to "form-data; name=\"$name\"") + addPart( + partHeaders.toHeaders(), + parameterToString(part.body).toRequestBody(null) + ) + } + } + }.build() + mediaType == FormUrlEncMediaType -> { + FormBody.Builder().apply { + // content's type *must* be Map> + @Suppress("UNCHECKED_CAST") + (content as Map>).forEach { (name, part) -> + add(name, parameterToString(part.body)) + } + }.build() + } + mediaType == null || mediaType.startsWith("application/") && mediaType.endsWith("json") -> + if (content == null) { + EMPTY_REQUEST + } else { + Serializer.moshi.adapter(T::class.java).toJson(content) + .toRequestBody((mediaType ?: JsonMediaType).toMediaTypeOrNull()) + } + mediaType == XmlMediaType -> throw UnsupportedOperationException("xml not currently supported.") + // TODO: this should be extended with other serializers + else -> throw UnsupportedOperationException("requestBody currently only supports JSON body and File body.") + } + + @OptIn(ExperimentalStdlibApi::class) + protected inline fun responseBody(body: ResponseBody?, mediaType: String? = JsonMediaType): T? { + if(body == null) { + return null + } + if (T::class.java == File::class.java) { + // return tempFile + // Attention: if you are developing an android app that supports API Level 25 and bellow, please check flag supportAndroidApiLevel25AndBelow in https://openapi-generator.tech/docs/generators/kotlin#config-options + val tempFile = java.nio.file.Files.createTempFile("tmp.org.openapitools.client", null).toFile() + tempFile.deleteOnExit() + body.byteStream().use { inputStream -> + tempFile.outputStream().use { tempFileOutputStream -> + inputStream.copyTo(tempFileOutputStream) + } + } + return tempFile as T + } + val bodyContent = body.string() + if (bodyContent.isEmpty()) { + return null + } + return when { + mediaType==null || (mediaType.startsWith("application/") && mediaType.endsWith("json")) -> + Serializer.moshi.adapter().fromJson(bodyContent) + else -> throw UnsupportedOperationException("responseBody currently only supports JSON body.") + } + } + + + protected inline fun request(requestConfig: RequestConfig): ApiResponse { + val httpUrl = baseUrl.toHttpUrlOrNull() ?: throw IllegalStateException("baseUrl is invalid.") + + val url = httpUrl.newBuilder() + .addEncodedPathSegments(requestConfig.path.trimStart('/')) + .apply { + requestConfig.query.forEach { query -> + query.value.forEach { queryValue -> + addQueryParameter(query.key, queryValue) + } + } + }.build() + + // take content-type/accept from spec or set to default (application/json) if not defined + if (requestConfig.body != null && requestConfig.headers[ContentType].isNullOrEmpty()) { + requestConfig.headers[ContentType] = JsonMediaType + } + if (requestConfig.headers[Accept].isNullOrEmpty()) { + requestConfig.headers[Accept] = JsonMediaType + } + val headers = requestConfig.headers + + if (headers[Accept].isNullOrEmpty()) { + throw kotlin.IllegalStateException("Missing Accept header. This is required.") + } + + val contentType = if (headers[ContentType] != null) { + // TODO: support multiple contentType options here. + (headers[ContentType] as String).substringBefore(";").lowercase(Locale.US) + } else { + null + } + + val request = when (requestConfig.method) { + RequestMethod.DELETE -> Request.Builder().url(url).delete(requestBody(requestConfig.body, contentType)) + RequestMethod.GET -> Request.Builder().url(url) + RequestMethod.HEAD -> Request.Builder().url(url).head() + RequestMethod.PATCH -> Request.Builder().url(url).patch(requestBody(requestConfig.body, contentType)) + RequestMethod.PUT -> Request.Builder().url(url).put(requestBody(requestConfig.body, contentType)) + RequestMethod.POST -> Request.Builder().url(url).post(requestBody(requestConfig.body, contentType)) + RequestMethod.OPTIONS -> Request.Builder().url(url).method("OPTIONS", null) + }.apply { + headers.forEach { header -> addHeader(header.key, header.value) } + }.build() + + val response = client.newCall(request).execute() + + val accept = response.header(ContentType)?.substringBefore(";")?.lowercase(Locale.US) + + // TODO: handle specific mapping types. e.g. Map> + return when { + response.isRedirect -> Redirection( + response.code, + response.headers.toMultimap() + ) + response.isInformational -> Informational( + response.message, + response.code, + response.headers.toMultimap() + ) + response.isSuccessful -> Success( + responseBody(response.body, accept), + response.code, + response.headers.toMultimap() + ) + response.isClientError -> ClientError( + response.message, + response.body?.string(), + response.code, + response.headers.toMultimap() + ) + else -> ServerError( + response.message, + response.body?.string(), + response.code, + response.headers.toMultimap() + ) + } + } + + protected fun parameterToString(value: Any?): String = when (value) { + null -> "" + is Array<*> -> toMultiValue(value, "csv").toString() + is Iterable<*> -> toMultiValue(value, "csv").toString() + is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime -> + parseDateToQueryString(value) + else -> value.toString() + } + + protected inline fun parseDateToQueryString(value : T): String { + /* + .replace("\"", "") converts the json object string to an actual string for the query parameter. + The moshi or gson adapter allows a more generic solution instead of trying to use a native + formatter. It also easily allows to provide a simple way to define a custom date format pattern + inside a gson/moshi adapter. + */ + return Serializer.moshi.adapter(T::class.java).toJson(value).replace("\"", "") + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiResponse.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiResponse.kt new file mode 100644 index 00000000000..cf2cfaa95d9 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiResponse.kt @@ -0,0 +1,43 @@ +package org.openapitools.client.infrastructure + +enum class ResponseType { + Success, Informational, Redirection, ClientError, ServerError +} + +interface Response + +abstract class ApiResponse(val responseType: ResponseType): Response { + abstract val statusCode: Int + abstract val headers: Map> +} + +class Success( + val data: T, + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +): ApiResponse(ResponseType.Success) + +class Informational( + val statusText: String, + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +) : ApiResponse(ResponseType.Informational) + +class Redirection( + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +) : ApiResponse(ResponseType.Redirection) + +class ClientError( + val message: String? = null, + val body: Any? = null, + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +) : ApiResponse(ResponseType.ClientError) + +class ServerError( + val message: String? = null, + val body: Any? = null, + override val statusCode: Int = -1, + override val headers: Map> +): ApiResponse(ResponseType.ServerError) diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/BigDecimalAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/BigDecimalAdapter.kt new file mode 100644 index 00000000000..064b57fc6b8 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/BigDecimalAdapter.kt @@ -0,0 +1,17 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.math.BigDecimal + +class BigDecimalAdapter { + @ToJson + fun toJson(value: BigDecimal): String { + return value.toPlainString() + } + + @FromJson + fun fromJson(value: String): BigDecimal { + return BigDecimal(value) + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/BigIntegerAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/BigIntegerAdapter.kt new file mode 100644 index 00000000000..7df6057b450 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/BigIntegerAdapter.kt @@ -0,0 +1,17 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.math.BigInteger + +class BigIntegerAdapter { + @ToJson + fun toJson(value: BigInteger): String { + return value.toString() + } + + @FromJson + fun fromJson(value: String): BigInteger { + return BigInteger(value) + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt new file mode 100644 index 00000000000..ff5e2a81ee8 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt @@ -0,0 +1,12 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson + +class ByteArrayAdapter { + @ToJson + fun toJson(data: ByteArray): String = String(data) + + @FromJson + fun fromJson(data: String): ByteArray = data.toByteArray() +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/Errors.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/Errors.kt new file mode 100644 index 00000000000..b5310e71f13 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/Errors.kt @@ -0,0 +1,18 @@ +@file:Suppress("unused") +package org.openapitools.client.infrastructure + +import java.lang.RuntimeException + +open class ClientException(message: kotlin.String? = null, val statusCode: Int = -1, val response: Response? = null) : RuntimeException(message) { + + companion object { + private const val serialVersionUID: Long = 123L + } +} + +open class ServerException(message: kotlin.String? = null, val statusCode: Int = -1, val response: Response? = null) : RuntimeException(message) { + + companion object { + private const val serialVersionUID: Long = 456L + } +} \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt new file mode 100644 index 00000000000..b2e1654479a --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt @@ -0,0 +1,19 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +class LocalDateAdapter { + @ToJson + fun toJson(value: LocalDate): String { + return DateTimeFormatter.ISO_LOCAL_DATE.format(value) + } + + @FromJson + fun fromJson(value: String): LocalDate { + return LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE) + } + +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt new file mode 100644 index 00000000000..e082db94811 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt @@ -0,0 +1,19 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +class LocalDateTimeAdapter { + @ToJson + fun toJson(value: LocalDateTime): String { + return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(value) + } + + @FromJson + fun fromJson(value: String): LocalDateTime { + return LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME) + } + +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt new file mode 100644 index 00000000000..87437871a31 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt @@ -0,0 +1,19 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter + +class OffsetDateTimeAdapter { + @ToJson + fun toJson(value: OffsetDateTime): String { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(value) + } + + @FromJson + fun fromJson(value: String): OffsetDateTime { + return OffsetDateTime.parse(value, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + } + +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt new file mode 100644 index 00000000000..be00e38fbae --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt @@ -0,0 +1,11 @@ +package org.openapitools.client.infrastructure + +/** + * Defines a config object for a given part of a multi-part request. + * NOTE: Headers is a Map because rfc2616 defines + * multi-valued headers as csv-only. + */ +data class PartConfig( + val headers: MutableMap = mutableMapOf(), + val body: T? = null +) diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt new file mode 100644 index 00000000000..625a19002b5 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt @@ -0,0 +1,18 @@ +package org.openapitools.client.infrastructure + +/** + * Defines a config object for a given request. + * NOTE: This object doesn't include 'body' because it + * allows for caching of the constructed object + * for many request definitions. + * NOTE: Headers is a Map because rfc2616 defines + * multi-valued headers as csv-only. + */ +data class RequestConfig( + val method: RequestMethod, + val path: String, + val headers: MutableMap = mutableMapOf(), + val query: MutableMap> = mutableMapOf(), + val requiresAuthentication: Boolean, + val body: T? = null +) \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt new file mode 100644 index 00000000000..931b12b8bd7 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt @@ -0,0 +1,8 @@ +package org.openapitools.client.infrastructure + +/** + * Provides enumerated HTTP verbs + */ +enum class RequestMethod { + GET, DELETE, HEAD, OPTIONS, PATCH, POST, PUT +} \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt new file mode 100644 index 00000000000..9bd2790dc14 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt @@ -0,0 +1,24 @@ +package org.openapitools.client.infrastructure + +import okhttp3.Response + +/** + * Provides an extension to evaluation whether the response is a 1xx code + */ +val Response.isInformational : Boolean get() = this.code in 100..199 + +/** + * Provides an extension to evaluation whether the response is a 3xx code + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +val Response.isRedirect : Boolean get() = this.code in 300..399 + +/** + * Provides an extension to evaluation whether the response is a 4xx code + */ +val Response.isClientError : Boolean get() = this.code in 400..499 + +/** + * Provides an extension to evaluation whether the response is a 5xx (Standard) through 999 (non-standard) code + */ +val Response.isServerError : Boolean get() = this.code in 500..999 diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt new file mode 100644 index 00000000000..e22592e47d7 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt @@ -0,0 +1,23 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory + +object Serializer { + @JvmStatic + val moshiBuilder: Moshi.Builder = Moshi.Builder() + .add(OffsetDateTimeAdapter()) + .add(LocalDateTimeAdapter()) + .add(LocalDateAdapter()) + .add(UUIDAdapter()) + .add(ByteArrayAdapter()) + .add(URIAdapter()) + .add(KotlinJsonAdapterFactory()) + .add(BigDecimalAdapter()) + .add(BigIntegerAdapter()) + + @JvmStatic + val moshi: Moshi by lazy { + moshiBuilder.build() + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt new file mode 100644 index 00000000000..927522757da --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt @@ -0,0 +1,13 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.net.URI + +class URIAdapter { + @ToJson + fun toJson(uri: URI) = uri.toString() + + @FromJson + fun fromJson(s: String): URI = URI.create(s) +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt new file mode 100644 index 00000000000..7ccf7dc25d2 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt @@ -0,0 +1,13 @@ +package org.openapitools.client.infrastructure + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.util.UUID + +class UUIDAdapter { + @ToJson + fun toJson(uuid: UUID) = uuid.toString() + + @FromJson + fun fromJson(s: String): UUID = UUID.fromString(s) +} diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1.kt new file mode 100644 index 00000000000..255240614de --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1.kt @@ -0,0 +1,40 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.BroadcastRequestV1Message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param message + * @param signature + */ + + +data class BroadcastRequestV1 ( + + @Json(name = "message") + val message: BroadcastRequestV1Message, + + @Json(name = "signature") + val signature: kotlin.String + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1Message.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1Message.kt new file mode 100644 index 00000000000..2eab7e0e9da --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastRequestV1Message.kt @@ -0,0 +1,40 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.NewNodeRequestV1 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param message + * @param pubKey + */ + + +data class BroadcastRequestV1Message ( + + @Json(name = "message") + val message: NewNodeRequestV1, + + @Json(name = "pubKey") + val pubKey: kotlin.String + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastResponseV1.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastResponseV1.kt new file mode 100644 index 00000000000..7a32207857a --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/BroadcastResponseV1.kt @@ -0,0 +1,35 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param result + */ + + +data class BroadcastResponseV1 ( + + @Json(name = "result") + val result: kotlin.Boolean + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNode.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNode.kt new file mode 100644 index 00000000000..020128ccb68 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNode.kt @@ -0,0 +1,61 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * A Cactus node can be a single server, or a set of servers behind a load balancer acting as one. + * + * @param nodeApiHost + * @param publicKeyPem The PEM encoded public key that was used to generate the JWS included in the response (the jws property) + * @param id + * @param consortiumId + * @param memberId + * @param ledgerIds Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions. + * @param pluginInstanceIds + */ + + +data class CactusNode ( + + @Json(name = "nodeApiHost") + val nodeApiHost: kotlin.String, + + /* The PEM encoded public key that was used to generate the JWS included in the response (the jws property) */ + @Json(name = "publicKeyPem") + val publicKeyPem: kotlin.String, + + @Json(name = "id") + val id: kotlin.String, + + @Json(name = "consortiumId") + val consortiumId: kotlin.String, + + @Json(name = "memberId") + val memberId: kotlin.String, + + /* Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions. */ + @Json(name = "ledgerIds") + val ledgerIds: kotlin.collections.List = arrayListOf(), + + @Json(name = "pluginInstanceIds") + val pluginInstanceIds: kotlin.collections.List = arrayListOf() + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNodeAllOf.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNodeAllOf.kt new file mode 100644 index 00000000000..895eb1c8a63 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNodeAllOf.kt @@ -0,0 +1,52 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param id + * @param consortiumId + * @param memberId + * @param ledgerIds Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions. + * @param pluginInstanceIds + */ + + +data class CactusNodeAllOf ( + + @Json(name = "id") + val id: kotlin.String, + + @Json(name = "consortiumId") + val consortiumId: kotlin.String, + + @Json(name = "memberId") + val memberId: kotlin.String, + + /* Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions. */ + @Json(name = "ledgerIds") + val ledgerIds: kotlin.collections.List = arrayListOf(), + + @Json(name = "pluginInstanceIds") + val pluginInstanceIds: kotlin.collections.List = arrayListOf() + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNodeMeta.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNodeMeta.kt new file mode 100644 index 00000000000..ec9d921ad1a --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactusNodeMeta.kt @@ -0,0 +1,40 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * A Cactus node meta information + * + * @param nodeApiHost + * @param publicKeyPem The PEM encoded public key that was used to generate the JWS included in the response (the jws property) + */ + + +data class CactusNodeMeta ( + + @Json(name = "nodeApiHost") + val nodeApiHost: kotlin.String, + + /* The PEM encoded public key that was used to generate the JWS included in the response (the jws property) */ + @Json(name = "publicKeyPem") + val publicKeyPem: kotlin.String + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetConsortiumJwsResponse.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetConsortiumJwsResponse.kt new file mode 100644 index 00000000000..eb880bcea16 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetConsortiumJwsResponse.kt @@ -0,0 +1,36 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.JWSGeneral + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param jws + */ + + +data class GetConsortiumJwsResponse ( + + @Json(name = "jws") + val jws: JWSGeneral + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetNodeJwsResponse.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetNodeJwsResponse.kt new file mode 100644 index 00000000000..c1d327a7d79 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetNodeJwsResponse.kt @@ -0,0 +1,36 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.JWSGeneral + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param jws + */ + + +data class GetNodeJwsResponse ( + + @Json(name = "jws") + val jws: JWSGeneral + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/Identity.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/Identity.kt new file mode 100644 index 00000000000..561a85c83f5 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/Identity.kt @@ -0,0 +1,46 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Identity object of a Cacti Node + * + * @param pubKey Public key of the Node + * @param memberId memberId of the organization the node belongs to + * @param proof JWT generated by the organization the node belongs to + */ + + +data class Identity ( + + /* Public key of the Node */ + @Json(name = "pubKey") + val pubKey: kotlin.String, + + /* memberId of the organization the node belongs to */ + @Json(name = "memberId") + val memberId: kotlin.String, + + /* JWT generated by the organization the node belongs to */ + @Json(name = "proof") + val proof: kotlin.String + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/JWSGeneral.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/JWSGeneral.kt new file mode 100644 index 00000000000..f32650672e8 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/JWSGeneral.kt @@ -0,0 +1,40 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.JWSRecipient + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param payload + * @param signatures + */ + + +data class JWSGeneral ( + + @Json(name = "payload") + val payload: kotlin.String, + + @Json(name = "signatures") + val signatures: kotlin.collections.List + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/JWSRecipient.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/JWSRecipient.kt new file mode 100644 index 00000000000..7bf5dd698ed --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/JWSRecipient.kt @@ -0,0 +1,43 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * A JSON Web Signature. See: https://tools.ietf.org/html/rfc7515 for info about standard. + * + * @param signature + * @param `protected` + * @param header + */ + + +data class JWSRecipient ( + + @Json(name = "signature") + val signature: kotlin.String, + + @Json(name = "protected") + val `protected`: kotlin.String? = null, + + @Json(name = "header") + val header: kotlin.collections.Map? = null + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/Ledger.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/Ledger.kt new file mode 100644 index 00000000000..55c44b1d914 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/Ledger.kt @@ -0,0 +1,44 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.LedgerType + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param id + * @param ledgerType + * @param consortiumMemberId + */ + + +data class Ledger ( + + @Json(name = "id") + val id: kotlin.String, + + @Json(name = "ledgerType") + val ledgerType: LedgerType, + + @Json(name = "consortiumMemberId") + val consortiumMemberId: kotlin.String? = null + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/LedgerType.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/LedgerType.kt new file mode 100644 index 00000000000..60fe48e4ae1 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/LedgerType.kt @@ -0,0 +1,75 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Enumerates the different ledger vendors and their major versions encoded within the name of the LedgerType. For example \"BESU_1X\" involves all of the [1.0.0;2.0.0) where 1.0.0 is included and anything up until, but not 2.0.0. See: https://stackoverflow.com/a/4396303/698470 for further explanation. + * + * Values: bESU1X,bESU2X,bURROW0X,cORDA4X,fABRIC2,sAWTOOTH1X + */ + +@JsonClass(generateAdapter = false) +enum class LedgerType(val value: kotlin.String) { + + @Json(name = "BESU_1X") + bESU1X("BESU_1X"), + + @Json(name = "BESU_2X") + bESU2X("BESU_2X"), + + @Json(name = "BURROW_0X") + bURROW0X("BURROW_0X"), + + @Json(name = "CORDA_4X") + cORDA4X("CORDA_4X"), + + @Json(name = "FABRIC_2") + fABRIC2("FABRIC_2"), + + @Json(name = "SAWTOOTH_1X") + sAWTOOTH1X("SAWTOOTH_1X"); + + /** + * Override [toString()] to avoid using the enum variable name as the value, and instead use + * the actual value defined in the API spec file. + * + * This solves a problem when the variable name and its value are different, and ensures that + * the client sends the correct enum values to the server always. + */ + override fun toString(): String = value + + companion object { + /** + * Converts the provided [data] to a [String] on success, null otherwise. + */ + fun encode(data: kotlin.Any?): kotlin.String? = if (data is LedgerType) "$data" else null + + /** + * Returns a valid [LedgerType] for [data], null otherwise. + */ + fun decode(data: kotlin.Any?): LedgerType? = data?.let { + val normalizedData = "$it".lowercase() + values().firstOrNull { value -> + it == value || normalizedData == "$value".lowercase() + } + } + } +} + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/NewNodeRequestV1.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/NewNodeRequestV1.kt new file mode 100644 index 00000000000..747458dc899 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/NewNodeRequestV1.kt @@ -0,0 +1,63 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.CactusNode +import org.openapitools.client.models.Identity +import org.openapitools.client.models.Ledger +import org.openapitools.client.models.PluginInstance + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param identity + * @param treeHash proof of common configs and policies defined by the consortium + * @param node + * @param ledger Ledger metadata + * @param pluginInstance Plugin Instance metadata + * @param signature signature of the message + */ + + +data class NewNodeRequestV1 ( + + @Json(name = "identity") + val identity: Identity, + + /* proof of common configs and policies defined by the consortium */ + @Json(name = "treeHash") + val treeHash: kotlin.String, + + @Json(name = "node") + val node: CactusNode, + + /* Ledger metadata */ + @Json(name = "ledger") + val ledger: kotlin.collections.List = arrayListOf(), + + /* Plugin Instance metadata */ + @Json(name = "pluginInstance") + val pluginInstance: kotlin.collections.List = arrayListOf(), + + /* signature of the message */ + @Json(name = "signature") + val signature: kotlin.String + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/PluginInstance.kt b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/PluginInstance.kt new file mode 100644 index 00000000000..5b40ac29bc5 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/PluginInstance.kt @@ -0,0 +1,39 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * + * @param id + * @param packageName + */ + + +data class PluginInstance ( + + @Json(name = "id") + val id: kotlin.String, + + @Json(name = "packageName") + val packageName: kotlin.String + +) + diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/add-new-node-endpoint-v1.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/add-new-node-endpoint-v1.ts new file mode 100644 index 00000000000..1bcbcba600b --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/add-new-node-endpoint-v1.ts @@ -0,0 +1,121 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { GetConsortiumJwsResponse } from "../generated/openapi/typescript-axios"; + +import { + Logger, + LogLevelDesc, + LoggerProvider, + Checks, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { + handleRestEndpointException, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; + +import OAS from "../../json/openapi.json"; +import { PluginConsortiumStatic } from "../plugin-consortium-static"; +import { StaticConsortiumRepository } from "../repository/static-consortium-repository"; + +export interface IAddNewNodeEndpointOptions { + plugin: PluginConsortiumStatic; + keyPairPem: string; + consortiumRepo: StaticConsortiumRepository; + logLevel?: LogLevelDesc; +} + +export class AddNewNodeEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "AddNewNodeEndpoint"; + + private readonly log: Logger; + private readonly plugin: PluginConsortiumStatic; + + constructor(public readonly options: IAddNewNodeEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + if (!options.keyPairPem) { + throw new Error(`${fnTag} options.keyPairPem falsy.`); + } + Checks.truthy(options.consortiumRepo, `${fnTag} options.consortiumRepo`); + Checks.truthy(options.plugin, `${fnTag} options.plugin`); + Checks.truthy( + options.plugin instanceof PluginConsortiumStatic, + `${fnTag} options.plugin instanceof PluginConsortiumStatic`, + ); + this.plugin = options.plugin; + + const level = options.logLevel || "INFO"; + const label = "add-node-endpoint-v1"; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get className(): string { + return AddNewNodeEndpoint.CLASS_NAME; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node" + ]; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = `${this.className}#handleRequest()`; + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + await this.options.plugin.processNewNodeRequest(req.body); + this.options.plugin.broadcastJoinRequest(req.body); + const jws = await this.options.plugin.getConsortiumJws(); + const body: GetConsortiumJwsResponse = { jws }; + res.status(200); + res.json(body); + } catch (ex: unknown) { + const errorMsg = `${fnTag} request handler fn crashed for: ${reqTag}`; + handleRestEndpointException({ errorMsg, log: this.log, error: ex, res }); + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-consortium-jws-endpoint-v1.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-consortium-jws-endpoint-v1.ts new file mode 100644 index 00000000000..6f1eb04b772 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-consortium-jws-endpoint-v1.ts @@ -0,0 +1,122 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { GetConsortiumJwsResponse } from "../generated/openapi/typescript-axios"; + +import { + LogLevelDesc, + Logger, + LoggerProvider, + IAsyncProvider, + Checks, +} from "@hyperledger/cactus-common"; + +import { + handleRestEndpointException, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; + +import OAS from "../../json/openapi.json"; +import { PluginConsortiumStatic } from "../plugin-consortium-static"; +import { StaticConsortiumRepository } from "../repository/static-consortium-repository"; + +export interface IGetConsortiumJwsEndpointOptions { + plugin: PluginConsortiumStatic; + keyPairPem: string; + consortiumRepo: StaticConsortiumRepository; + logLevel?: LogLevelDesc; +} + +export class GetConsortiumEndpointV1 implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "GetConsortiumEndpointV1"; + + private readonly log: Logger; + private readonly plugin: PluginConsortiumStatic; + + constructor(public readonly options: IGetConsortiumJwsEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + if (!options.keyPairPem) { + throw new Error(`${fnTag} options.keyPairPem falsy.`); + } + if (!options.consortiumRepo) { + throw new Error(`${fnTag} options.consortium falsy.`); + } + Checks.truthy(options.plugin, `${fnTag} options.plugin`); + Checks.truthy( + options.plugin instanceof PluginConsortiumStatic, + `${fnTag} options.plugin instanceof PluginConsortiumStatic`, + ); + this.plugin = options.plugin; + + const label = "get-consortium-jws-endpoint"; + const level = options.logLevel || "INFO"; + this.log = LoggerProvider.getOrCreate({ label, level }); + } + + public get className(): string { + return GetConsortiumEndpointV1.CLASS_NAME; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws" + ]; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = `${this.className}#handleRequest()`; + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + const jws = await this.options.plugin.getConsortiumJws(); + + const body: GetConsortiumJwsResponse = { jws }; + res.status(200); + res.json(body); + } catch (ex: unknown) { + const errorMsg = `${fnTag} request handler fn crashed for: ${reqTag}`; + handleRestEndpointException({ errorMsg, log: this.log, error: ex, res }); + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-node-jws-endpoint-v1.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-node-jws-endpoint-v1.ts new file mode 100644 index 00000000000..f91fe8060fc --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-node-jws-endpoint-v1.ts @@ -0,0 +1,118 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { GetNodeJwsResponse } from "../generated/openapi/typescript-axios"; + +import { + Logger, + LogLevelDesc, + LoggerProvider, + Checks, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { + handleRestEndpointException, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; + +import OAS from "../../json/openapi.json"; +import { PluginConsortiumStatic } from "../plugin-consortium-static"; +import { StaticConsortiumRepository } from "../repository/static-consortium-repository"; + +export interface IGetNodeJwsEndpointOptions { + plugin: PluginConsortiumStatic; + keyPairPem: string; + consortiumRepo: StaticConsortiumRepository; + logLevel?: LogLevelDesc; +} + +export class GetNodeJwsEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "GetNodeJwsEndpoint"; + + private readonly log: Logger; + private readonly plugin: PluginConsortiumStatic; + + constructor(public readonly options: IGetNodeJwsEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + if (!options.keyPairPem) { + throw new Error(`${fnTag} options.keyPairPem falsy.`); + } + Checks.truthy(options.consortiumRepo, `${fnTag} options.consortiumRepo`); + Checks.truthy(options.plugin, `${fnTag} options.plugin`); + Checks.truthy( + options.plugin instanceof PluginConsortiumStatic, + `${fnTag} options.plugin instanceof PluginConsortiumStatic`, + ); + this.plugin = options.plugin; + + const level = options.logLevel || "INFO"; + const label = "get-node-jws-endpoint-v1"; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get className(): string { + return GetNodeJwsEndpoint.CLASS_NAME; + } + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws" + ]; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = `${this.className}#handleRequest()`; + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + const jws = await this.options.plugin.getNodeJws(); + const body: GetNodeJwsResponse = { jws }; + res.status(200); + res.json(body); + } catch (ex: unknown) { + const errorMsg = `${fnTag} request handler fn crashed for: ${reqTag}`; + handleRestEndpointException({ errorMsg, log: this.log, error: ex, res }); + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-prometheus-exporter-metrics-endpoint-v1.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-prometheus-exporter-metrics-endpoint-v1.ts new file mode 100644 index 00000000000..eb64650e63a --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/get-prometheus-exporter-metrics-endpoint-v1.ts @@ -0,0 +1,109 @@ +import { Express, Request, Response } from "express"; + +import { + Logger, + LoggerProvider, + LogLevelDesc, + Checks, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import OAS from "../../json/openapi.json"; + +import { + handleRestEndpointException, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; + +import { PluginConsortiumStatic } from "../plugin-consortium-static"; + +export interface IGetPrometheusExporterMetricsEndpointV1Options { + logLevel?: LogLevelDesc; + plugin: PluginConsortiumStatic; +} + +export class GetPrometheusExporterMetricsEndpointV1 + implements IWebServiceEndpoint +{ + public static readonly CLASS_NAME = "GetPrometheusExporterMetricsEndpointV1"; + private readonly log: Logger; + + constructor( + public readonly opts: IGetPrometheusExporterMetricsEndpointV1Options, + ) { + const fnTag = `${this.className}#constructor()`; + + Checks.truthy(opts, `${fnTag} options`); + Checks.truthy(opts.plugin, `${fnTag} options.plugin`); + + this.log = LoggerProvider.getOrCreate({ + label: "get-prometheus-exporter-metrics-v1", + level: opts.logLevel || "INFO", + }); + } + + public get className(): string { + return GetPrometheusExporterMetricsEndpointV1.CLASS_NAME; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics" + ]; + } + + public getPath(): string { + return this.oasPath.get["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.get["x-hyperledger-cacti"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.get.operationId; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = `${this.className}#handleRequest()`; + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + const verbUpper = this.getVerbLowerCase().toUpperCase(); + this.log.debug(`${verbUpper} ${this.getPath()}`); + + try { + const resBody = await this.opts.plugin.getPrometheusExporterMetrics(); + res.status(200); + res.send(resBody); + } catch (ex: unknown) { + const errorMsg = `${fnTag} request handler fn crashed for: ${reqTag}`; + handleRestEndpointException({ errorMsg, log: this.log, error: ex, res }); + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/process-broadcast-endpoint-v1.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/process-broadcast-endpoint-v1.ts new file mode 100644 index 00000000000..f21ccd2a9fe --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/consortium/process-broadcast-endpoint-v1.ts @@ -0,0 +1,132 @@ +import { Express, Request, Response } from "express"; +import { stringify as safeStableStringify } from "safe-stable-stringify"; +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { BroadcastResponseV1 } from "../generated/openapi/typescript-axios"; + +import { + Logger, + LogLevelDesc, + LoggerProvider, + Checks, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { + handleRestEndpointException, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; + +import OAS from "../../json/openapi.json"; +import { PluginConsortiumStatic } from "../plugin-consortium-static"; +import { StaticConsortiumRepository } from "../repository/static-consortium-repository"; +import { InternalServerError } from "http-errors-enhanced-cjs"; + +export interface IProcessBroadcastEndpointOptions { + plugin: PluginConsortiumStatic; + keyPairPem: string; + consortiumRepo: StaticConsortiumRepository; + logLevel?: LogLevelDesc; +} + +export class ProcessBroadcastEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "ProcessBroadcastEndpoint"; + + private readonly log: Logger; + private readonly plugin: PluginConsortiumStatic; + + constructor(public readonly options: IProcessBroadcastEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + if (!options.keyPairPem) { + throw new Error(`${fnTag} options.keyPairPem falsy.`); + } + Checks.truthy(options.consortiumRepo, `${fnTag} options.consortiumRepo`); + Checks.truthy(options.plugin, `${fnTag} options.plugin`); + Checks.truthy( + options.plugin instanceof PluginConsortiumStatic, + `${fnTag} options.plugin instanceof PluginConsortiumStatic`, + ); + this.plugin = options.plugin; + + const level = options.logLevel || "INFO"; + const label = "add-node-endpoint-v1"; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get className(): string { + return ProcessBroadcastEndpoint.CLASS_NAME; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast" + ]; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = `${this.className}#handleRequest()`; + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + + this.log.debug(reqTag); + try { + const msgStringified = safeStableStringify(req.body.message); + if (typeof msgStringified !== "string") { + throw new InternalServerError( + fnTag + "#safeStableStringify() returned with non-string value.", + ); + } + await this.plugin.verifySignature( + req.body.signature, + msgStringified, + req.body.message.pubKey, + ); + await this.options.plugin.processNewNodeRequest(req.body.message.message); + const body: BroadcastResponseV1 = { result: true }; + res.status(200); + res.json(body); + } catch (ex: unknown) { + const errorMsg = `${fnTag} request handler fn crashed for: ${reqTag}`; + handleRestEndpointException({ errorMsg, log: this.log, error: ex, res }); + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES new file mode 100644 index 00000000000..53250c02696 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES @@ -0,0 +1,5 @@ +api.ts +base.ts +common.ts +configuration.ts +index.ts diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION new file mode 100644 index 00000000000..cd802a1ec4e --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.6.0 \ No newline at end of file diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 00000000000..7bbf9b1c147 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,750 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative privileges. + * + * The version of the OpenAPI document: 2.0.0-rc.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; +import type { RequestArgs } from './base'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base'; + +/** + * + * @export + * @interface BroadcastRequestV1 + */ +export interface BroadcastRequestV1 { + /** + * + * @type {BroadcastRequestV1Message} + * @memberof BroadcastRequestV1 + */ + 'message': BroadcastRequestV1Message; + /** + * + * @type {string} + * @memberof BroadcastRequestV1 + */ + 'signature': string; +} +/** + * + * @export + * @interface BroadcastRequestV1Message + */ +export interface BroadcastRequestV1Message { + /** + * + * @type {NewNodeRequestV1} + * @memberof BroadcastRequestV1Message + */ + 'message': NewNodeRequestV1; + /** + * + * @type {string} + * @memberof BroadcastRequestV1Message + */ + 'pubKey': string; +} +/** + * + * @export + * @interface BroadcastResponseV1 + */ +export interface BroadcastResponseV1 { + /** + * + * @type {boolean} + * @memberof BroadcastResponseV1 + */ + 'result': boolean; +} +/** + * A Cactus node can be a single server, or a set of servers behind a load balancer acting as one. + * @export + * @interface CactusNode + */ +export interface CactusNode { + /** + * + * @type {string} + * @memberof CactusNode + */ + 'nodeApiHost': string; + /** + * The PEM encoded public key that was used to generate the JWS included in the response (the jws property) + * @type {string} + * @memberof CactusNode + */ + 'publicKeyPem': string; + /** + * + * @type {string} + * @memberof CactusNode + */ + 'id': string; + /** + * + * @type {string} + * @memberof CactusNode + */ + 'consortiumId': string; + /** + * + * @type {string} + * @memberof CactusNode + */ + 'memberId': string; + /** + * Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions. + * @type {Array} + * @memberof CactusNode + */ + 'ledgerIds': Array; + /** + * + * @type {Array} + * @memberof CactusNode + */ + 'pluginInstanceIds': Array; +} +/** + * + * @export + * @interface CactusNodeAllOf + */ +export interface CactusNodeAllOf { + /** + * + * @type {string} + * @memberof CactusNodeAllOf + */ + 'id': string; + /** + * + * @type {string} + * @memberof CactusNodeAllOf + */ + 'consortiumId': string; + /** + * + * @type {string} + * @memberof CactusNodeAllOf + */ + 'memberId': string; + /** + * Stores an array of Ledger entity IDs that are reachable (routable) via this Cactus Node. This information is used by the client side SDK API client to figure out at runtime where to send API requests that are specific to a certain ledger such as requests to execute transactions. + * @type {Array} + * @memberof CactusNodeAllOf + */ + 'ledgerIds': Array; + /** + * + * @type {Array} + * @memberof CactusNodeAllOf + */ + 'pluginInstanceIds': Array; +} +/** + * A Cactus node meta information + * @export + * @interface CactusNodeMeta + */ +export interface CactusNodeMeta { + /** + * + * @type {string} + * @memberof CactusNodeMeta + */ + 'nodeApiHost': string; + /** + * The PEM encoded public key that was used to generate the JWS included in the response (the jws property) + * @type {string} + * @memberof CactusNodeMeta + */ + 'publicKeyPem': string; +} +/** + * + * @export + * @interface GetConsortiumJwsResponse + */ +export interface GetConsortiumJwsResponse { + /** + * + * @type {JWSGeneral} + * @memberof GetConsortiumJwsResponse + */ + 'jws': JWSGeneral; +} +/** + * + * @export + * @interface GetNodeJwsResponse + */ +export interface GetNodeJwsResponse { + /** + * + * @type {JWSGeneral} + * @memberof GetNodeJwsResponse + */ + 'jws': JWSGeneral; +} +/** + * Identity object of a Cacti Node + * @export + * @interface Identity + */ +export interface Identity { + /** + * Public key of the Node + * @type {string} + * @memberof Identity + */ + 'pubKey': string; + /** + * memberId of the organization the node belongs to + * @type {string} + * @memberof Identity + */ + 'memberId': string; + /** + * JWT generated by the organization the node belongs to + * @type {string} + * @memberof Identity + */ + 'proof': string; +} +/** + * + * @export + * @interface JWSGeneral + */ +export interface JWSGeneral { + /** + * + * @type {string} + * @memberof JWSGeneral + */ + 'payload': string; + /** + * + * @type {Array} + * @memberof JWSGeneral + */ + 'signatures': Array; +} +/** + * A JSON Web Signature. See: https://tools.ietf.org/html/rfc7515 for info about standard. + * @export + * @interface JWSRecipient + */ +export interface JWSRecipient { + /** + * + * @type {string} + * @memberof JWSRecipient + */ + 'signature': string; + /** + * + * @type {string} + * @memberof JWSRecipient + */ + 'protected'?: string; + /** + * + * @type {{ [key: string]: any; }} + * @memberof JWSRecipient + */ + 'header'?: { [key: string]: any; }; +} +/** + * + * @export + * @interface Ledger + */ +export interface Ledger { + /** + * + * @type {string} + * @memberof Ledger + */ + 'id': string; + /** + * + * @type {LedgerType} + * @memberof Ledger + */ + 'ledgerType': LedgerType; + /** + * + * @type {string} + * @memberof Ledger + */ + 'consortiumMemberId'?: string; +} + + +/** + * Enumerates the different ledger vendors and their major versions encoded within the name of the LedgerType. For example \"BESU_1X\" involves all of the [1.0.0;2.0.0) where 1.0.0 is included and anything up until, but not 2.0.0. See: https://stackoverflow.com/a/4396303/698470 for further explanation. + * @export + * @enum {string} + */ + +export const LedgerType = { + Besu1X: 'BESU_1X', + Besu2X: 'BESU_2X', + Burrow0X: 'BURROW_0X', + Corda4X: 'CORDA_4X', + Fabric2: 'FABRIC_2', + Sawtooth1X: 'SAWTOOTH_1X' +} as const; + +export type LedgerType = typeof LedgerType[keyof typeof LedgerType]; + + +/** + * + * @export + * @interface NewNodeRequestV1 + */ +export interface NewNodeRequestV1 { + /** + * + * @type {Identity} + * @memberof NewNodeRequestV1 + */ + 'identity': Identity; + /** + * proof of common configs and policies defined by the consortium + * @type {string} + * @memberof NewNodeRequestV1 + */ + 'treeHash': string; + /** + * + * @type {CactusNode} + * @memberof NewNodeRequestV1 + */ + 'node': CactusNode; + /** + * Ledger metadata + * @type {Array} + * @memberof NewNodeRequestV1 + */ + 'ledger': Array; + /** + * Plugin Instance metadata + * @type {Array} + * @memberof NewNodeRequestV1 + */ + 'pluginInstance': Array; + /** + * signature of the message + * @type {string} + * @memberof NewNodeRequestV1 + */ + 'signature': string; +} +/** + * + * @export + * @interface PluginInstance + */ +export interface PluginInstance { + /** + * + * @type {string} + * @memberof PluginInstance + */ + 'id': string; + /** + * + * @type {string} + * @memberof PluginInstance + */ + 'packageName': string; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @summary Adds a node to consortium JWS + * @param {NewNodeRequestV1} [newNodeRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + addNodeToConsortiumV1: async (newNodeRequestV1?: NewNodeRequestV1, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/add-node`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(newNodeRequestV1, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getConsortiumJwsV1: async (body?: object, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/consortium/jws`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getNodeJwsV1: async (body?: object, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/node/jws`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPrometheusMetricsV1: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/get-prometheus-exporter-metrics`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Adds a node to consortium JWS + * @param {BroadcastRequestV1} [broadcastRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + receiveBroadcastV1: async (broadcastRequestV1?: BroadcastRequestV1, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-consortium-static/receive-broadcast`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(broadcastRequestV1, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @summary Adds a node to consortium JWS + * @param {NewNodeRequestV1} [newNodeRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async addNodeToConsortiumV1(newNodeRequestV1?: NewNodeRequestV1, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.addNodeToConsortiumV1(newNodeRequestV1, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getConsortiumJwsV1(body?: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getConsortiumJwsV1(body, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getNodeJwsV1(body?: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getNodeJwsV1(body, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getPrometheusMetricsV1(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPrometheusMetricsV1(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Adds a node to consortium JWS + * @param {BroadcastRequestV1} [broadcastRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async receiveBroadcastV1(broadcastRequestV1?: BroadcastRequestV1, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.receiveBroadcastV1(broadcastRequestV1, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = DefaultApiFp(configuration) + return { + /** + * + * @summary Adds a node to consortium JWS + * @param {NewNodeRequestV1} [newNodeRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + addNodeToConsortiumV1(newNodeRequestV1?: NewNodeRequestV1, options?: any): AxiosPromise { + return localVarFp.addNodeToConsortiumV1(newNodeRequestV1, options).then((request) => request(axios, basePath)); + }, + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getConsortiumJwsV1(body?: object, options?: any): AxiosPromise { + return localVarFp.getConsortiumJwsV1(body, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getNodeJwsV1(body?: object, options?: any): AxiosPromise { + return localVarFp.getNodeJwsV1(body, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPrometheusMetricsV1(options?: any): AxiosPromise { + return localVarFp.getPrometheusMetricsV1(options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Adds a node to consortium JWS + * @param {BroadcastRequestV1} [broadcastRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + receiveBroadcastV1(broadcastRequestV1?: BroadcastRequestV1, options?: any): AxiosPromise { + return localVarFp.receiveBroadcastV1(broadcastRequestV1, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * + * @summary Adds a node to consortium JWS + * @param {NewNodeRequestV1} [newNodeRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public addNodeToConsortiumV1(newNodeRequestV1?: NewNodeRequestV1, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).addNodeToConsortiumV1(newNodeRequestV1, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getConsortiumJwsV1(body?: object, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getConsortiumJwsV1(body, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getNodeJwsV1(body?: object, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getNodeJwsV1(body, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getPrometheusMetricsV1(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getPrometheusMetricsV1(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Adds a node to consortium JWS + * @param {BroadcastRequestV1} [broadcastRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public receiveBroadcastV1(broadcastRequestV1?: BroadcastRequestV1, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).receiveBroadcastV1(broadcastRequestV1, options).then((request) => request(this.axios, this.basePath)); + } +} + + diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/base.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/base.ts new file mode 100644 index 00000000000..dcb038e0b6d --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/base.ts @@ -0,0 +1,72 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative privileges. + * + * The version of the OpenAPI document: 2.0.0-rc.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: AxiosRequestConfig; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/common.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/common.ts new file mode 100644 index 00000000000..66442d8c0e7 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative privileges. + * + * The version of the OpenAPI document: 2.0.0-rc.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from "./configuration"; +import type { RequestArgs } from "./base"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from "./base"; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/configuration.ts new file mode 100644 index 00000000000..fb9374a9584 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/configuration.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative privileges. + * + * The version of the OpenAPI document: 2.0.0-rc.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/index.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/index.ts new file mode 100644 index 00000000000..a346bc2fdb6 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/generated/openapi/typescript-axios/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative privileges. + * + * The version of the OpenAPI document: 2.0.0-rc.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; + diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/index.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/index.ts new file mode 100755 index 00000000000..87cb558397c --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/index.web.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/index.web.ts new file mode 100755 index 00000000000..bdf54028d23 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export * from "./generated/openapi/typescript-axios/index"; diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/plugin-consortium-static.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/plugin-consortium-static.ts new file mode 100644 index 00000000000..402d37e6017 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/plugin-consortium-static.ts @@ -0,0 +1,515 @@ +import { Express } from "express"; +import { + importPKCS8, + GeneralSign, + SignJWT, + compactVerify, + importSPKI, + JWK, +} from "jose"; +import { v4 as uuidv4 } from "uuid"; + +import OAS from "../json/openapi.json"; + +import { + BroadcastRequestV1, + DefaultApi as ConsortiumManagerApi, +} from "./generated/openapi/typescript-axios"; + +import { + ConsortiumDatabase, + IPluginWebService, + IWebServiceEndpoint, + ICactusPlugin, + ICactusPluginOptions, + JWSGeneral, + JWSRecipient, + CactusNode, + Ledger, + PluginInstance, +} from "@hyperledger/cactus-core-api"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { stringify as safeStableStringify } from "safe-stable-stringify"; +import { + Checks, + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { GetConsortiumEndpointV1 } from "./consortium/get-consortium-jws-endpoint-v1"; +import { GetNodeJwsEndpoint } from "./consortium/get-node-jws-endpoint-v1"; + +import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter"; + +import { + IGetPrometheusExporterMetricsEndpointV1Options, + GetPrometheusExporterMetricsEndpointV1, +} from "./consortium/get-prometheus-exporter-metrics-endpoint-v1"; + +import { + Configuration, + DefaultApi, + NewNodeRequestV1, +} from "./generated/openapi/typescript-axios"; +import { StaticConsortiumRepository } from "./repository/static-consortium-repository"; +import { AddNewNodeEndpoint } from "./consortium/add-new-node-endpoint-v1"; +import { BadRequestError, InternalServerError } from "http-errors-enhanced-cjs"; +import { ProcessBroadcastEndpoint } from "./consortium/process-broadcast-endpoint-v1"; +import { PolicyGroup } from "./policy-model/policy-group"; +export interface IWebAppOptions { + port: number; + hostname: string; +} + +export interface IKeyPair { + publicKey: Uint8Array; + privateKey: Uint8Array; +} + +export interface IPluginConsortiumStaticOptions extends ICactusPluginOptions { + keyPairPem: string; + keyPairPub: string; + consortiumDatabase: ConsortiumDatabase; + node: CactusNode; + ledgers: Ledger[]; + pluginInstances: PluginInstance[]; + memberId: string; + entitiesJWK: { [key: string]: JWK }; + rootPolicyGroup?: PolicyGroup; + packageConfigs?: { [key: string]: unknown }; + prometheusExporter?: PrometheusExporter; + pluginRegistry?: PluginRegistry; + logLevel?: LogLevelDesc; + ctorArgs?: Record; +} + +export class PluginConsortiumStatic + implements ICactusPlugin, IPluginWebService +{ + public static readonly CLASS_NAME = "PluginConsortiumStatic"; + public prometheusExporter: PrometheusExporter; + private readonly log: Logger; + private readonly instanceId: string; + private readonly repo: StaticConsortiumRepository; + private endpoints: IWebServiceEndpoint[] | undefined; + + public get className(): string { + return PluginConsortiumStatic.CLASS_NAME; + } + + constructor(public readonly options: IPluginConsortiumStaticOptions) { + const fnTag = `PluginConsortiumStatic#constructor()`; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); + Checks.truthy( + options.consortiumDatabase, + `${fnTag} options.consortiumDatabase`, + ); + + this.log = LoggerProvider.getOrCreate({ + label: "plugin-consortium-static", + level: options.logLevel ? options.logLevel : "INFO", + }); + + this.instanceId = this.options.instanceId; + this.repo = new StaticConsortiumRepository({ + db: options.consortiumDatabase, + node: options.node, + ledgers: options.ledgers, + pluginInstances: options.pluginInstances, + memberId: options.memberId, + rootPolicyGroup: options.rootPolicyGroup, + packageConfigs: options.packageConfigs, + entitiesJWK: options.entitiesJWK, + }); + + this.prometheusExporter = + options.prometheusExporter || + new PrometheusExporter({ pollingIntervalInMin: 1 }); + + Checks.truthy( + this.prometheusExporter, + `${fnTag} options.prometheusExporter`, + ); + this.prometheusExporter.startMetricsCollection(); + this.prometheusExporter.setNodeCount(this.getNodeCount()); + } + + public getOpenApiSpec(): unknown { + return OAS; + } + + public getInstanceId(): string { + return this.instanceId; + } + + public async onPluginInit(): Promise { + return; + } + + public getPrometheusExporter(): PrometheusExporter { + return this.prometheusExporter; + } + + public async shutdown(): Promise { + return; + } + + public async getPrometheusExporterMetrics(): Promise { + const res: string = await this.prometheusExporter.getPrometheusMetrics(); + this.log.debug(`getPrometheusExporterMetrics() response: %o`, res); + return res; + } + + public getNodeCount(): number { + Checks.truthy(this.repo, `${this.className}.this.repo`); + return this.repo.allNodes.length; + } + /** + * Updates the Node count Prometheus metric of the plugin. + * Note: This does not change the underlying consortium database at all, + * only affects **the metrics**. + */ + public updateMetricNodeCount(): void { + const nodeCount = this.getNodeCount(); + this.prometheusExporter.setNodeCount(nodeCount); + } + + public async registerWebServices( + app: Express, + ): Promise { + const webServices = await this.getOrCreateWebServices(); + webServices.forEach((ws) => ws.registerExpress(app)); + return webServices; + } + + public async getOrCreateWebServices(): Promise { + const { log } = this; + const pkgName = this.getPackageName(); + + if (this.endpoints) { + return this.endpoints; + } + log.info(`Creating web services for plugin ${pkgName}...`); + // presence of webAppOptions implies that caller wants the plugin to configure it's own express instance on a custom + // host/port to listen on + + const { keyPairPem } = this.options; + const consortiumRepo = this.repo; + + const endpoints: IWebServiceEndpoint[] = []; + { + const options = { keyPairPem, consortiumRepo, plugin: this }; + const endpoint = new AddNewNodeEndpoint(options); + endpoints.push(endpoint); + const path = endpoint.getPath(); + this.log.info(`Instantiated AddNodeEndpoint at ${path}`); + } + { + const options = { keyPairPem, consortiumRepo, plugin: this }; + const endpoint = new ProcessBroadcastEndpoint(options); + endpoints.push(endpoint); + const path = endpoint.getPath(); + this.log.info(`Instantiated AddNodeEndpoint at ${path}`); + } + { + const options = { keyPairPem, consortiumRepo, plugin: this }; + const endpoint = new GetConsortiumEndpointV1(options); + endpoints.push(endpoint); + const path = endpoint.getPath(); + this.log.info(`Instantiated GetConsortiumEndpointV1 at ${path}`); + } + { + const options = { keyPairPem, consortiumRepo, plugin: this }; + const endpoint = new GetNodeJwsEndpoint(options); + const path = endpoint.getPath(); + endpoints.push(endpoint); + this.log.info(`Instantiated GetNodeJwsEndpoint at ${path}`); + } + { + const opts: IGetPrometheusExporterMetricsEndpointV1Options = { + plugin: this, + logLevel: this.options.logLevel, + }; + const endpoint = new GetPrometheusExporterMetricsEndpointV1(opts); + const path = endpoint.getPath(); + endpoints.push(endpoint); + this.log.info(`Instantiated GetNodeJwsEndpoint at ${path}`); + } + this.endpoints = endpoints; + + log.info(`Instantiated web svcs for plugin ${pkgName} OK`, { endpoints }); + return endpoints; + } + + public getPackageName(): string { + return `@hyperledger/cacti-plugin-consortium-static`; + } + + public async getNodeJws(): Promise { + Checks.truthy(this.repo, `${this.className}.this.repo`); + const { keyPairPem } = this.options; + + this.updateMetricNodeCount(); + const keyPair = await importPKCS8(keyPairPem, "ES256K"); + const payloadObject = { consortiumDatabase: this.repo.consortiumDatabase }; + const payloadJson = safeStableStringify(payloadObject); + const _protected = { + iat: Date.now(), + jti: uuidv4(), + iss: "Hyperledger Cactus", + }; + // TODO: double check if this casting is safe (it is supposed to be) + const encoder = new TextEncoder(); + const sign = new GeneralSign(encoder.encode(payloadJson)); + sign + .addSignature(keyPair) + .setProtectedHeader({ alg: "ES256K", _protected }); + const jwsGeneral = await sign.sign(); + return jwsGeneral as JWSGeneral; + } + + public broadcastJoinRequest(req: NewNodeRequestV1) { + const nodes = this.repo.allNodes.filter((node) => { + return ( + node.id !== this.repo.getSelfData().node.id && node.id !== req.node.id + ); + }); + this.log.info( + "Will broadcast to nodes: ", + nodes.map((node) => { + return node.id; + }), + ); + nodes.forEach(async (node) => { + const api = new ConsortiumManagerApi( + new Configuration({ basePath: node.nodeApiHost }), + ); + const message = { + message: req, + pubKey: this.options.keyPairPub, + }; + const msgStringified = safeStableStringify(message); + if (typeof msgStringified !== "string") { + throw new InternalServerError( + "getNodeJws#safeStableStringify() returned with non-string value.", + ); + } + const sig = await this.sign(msgStringified); + + const broadcast: BroadcastRequestV1 = { + message, + signature: sig, + }; + api.receiveBroadcastV1(broadcast); + }); + } + + public async getConsortiumJws(): Promise { + const nodes = this.repo.allNodes; + + const ctorArgs = this.options.ctorArgs || {}; + + const requests = nodes + .map((cnm) => cnm.nodeApiHost) + .map(function (host) { + // overwrite basePath with node api host + ctorArgs.basePath = host; + // return the ApiClient configuration object + return new Configuration(ctorArgs); + }) + .map((configuration) => new DefaultApi(configuration)) + .map((apiClient) => apiClient.getNodeJwsV1()); + + const responses = await Promise.all(requests); + + const signatures: JWSRecipient[] = []; + + responses + .map((apiResponse) => apiResponse.data) + .map((getNodeJwsResponse) => getNodeJwsResponse.jws) + .forEach((aJws: JWSGeneral) => + aJws.signatures.forEach((signature) => signatures.push(signature)), + ); + + const [response] = responses; + const jws = response.data.jws; + jws.signatures = signatures; + return jws; + } + + public async processNewNodeRequest(req: NewNodeRequestV1) { + const fn = `${PluginConsortiumStatic.CLASS_NAME}#processNewNodeRequest()`; + const identity = req.identity; + + Checks.nonBlankString(identity.pubKey); + const msg = { + identity, + treeHash: req.treeHash, + node: req.node, + ledger: req.ledger, + pluginInstance: req.pluginInstance, + }; + + const msgStringified = safeStableStringify(msg); + if (typeof msgStringified !== "string") { + throw new InternalServerError( + "processNodeRequest#safeStableStringify() returned with non-string value.", + ); + } + if ( + !(await this.verifySignature( + req.signature, + msgStringified, + identity.pubKey, + )) + ) { + throw new BadRequestError(fn + ": Signature is invalid."); + } + + // TODO: make verification of publicKey process configurable! + // Ideally, we want to allow for entities to customize their identification process + // It would allow for integrations with different methods, serving different requirements + + // What is done is: the request to join consortium includes a JWT token that must be signed by one of the + // organizations that already belongs to the consortium. + // Basically we support adding new nodes, but not new organizations + if (!(await this.repo.verifyJWT(identity.proof, req.node.memberId))) { + throw new BadRequestError( + fn + + ": Your token from organization " + + req.node.memberId + + " is invalid.", + ); + } + + const policyAssertion = req.treeHash; + try { + if (!this.verifySettings(policyAssertion)) { + throw new BadRequestError( + fn + + ": Your policy & configuration settings do not match the ones used in this consortium.", + ); + } + } catch (err) { + throw new BadRequestError(fn + err.message); + } + + this.repo.addNode(req.node, req.pluginInstance, req.ledger); + this.updateMetricNodeCount(); + } + + private verifySettings(assert: string): boolean { + const self = this.repo.getPolicyTreeProof() + this.repo.getConfigsProof(); + this.log.info(self); + this.log.info(assert); + return self === assert; + } + + async sign(msg: string): Promise { + const encoder = new TextEncoder(); + const data = encoder.encode(msg); + const privateKey = await importPKCS8(this.options.keyPairPem, "ES256K"); + const _protected = { + iat: Date.now(), + jti: uuidv4(), + iss: "Hyperledger Cactus", + }; + const signature = await new SignJWT({ data: Array.from(data) }) + .setProtectedHeader({ alg: "ES256K", _protected }) + .sign(privateKey); + return signature; + } + + async verifySignature( + signature: string, + msg: string, + pubKey: string, + ): Promise { + const publicKey = await importSPKI(pubKey, "ES256K"); + const { payload } = await compactVerify(signature, publicKey); + + // Extract the original message + const parsedPayload = JSON.parse(payload.toString()); + const signedData = new Uint8Array(parsedPayload.data); + const decodedMessage = new TextDecoder().decode(signedData); + return decodedMessage === msg; + } + + public async joinConsortium(managerApi: ConsortiumManagerApi, jwt: string) { + const fn = `${PluginConsortiumStatic.CLASS_NAME}#joinConsortium()`; + const nodeInfo = this.repo.getSelfData(); + const treeHash = + this.repo.getPolicyTreeProof() + this.repo.getConfigsProof(); + + this.log.info(treeHash); + const msg = safeStableStringify({ + identity: { + pubKey: this.options.keyPairPub, + memberId: nodeInfo.memberId, + proof: jwt, //token signed by the organization identified by nodeInfo.memberId + }, + treeHash, + node: nodeInfo.node, + ledger: nodeInfo.ledgers, + pluginInstance: nodeInfo.pluginInstances, + }) as string; + + const request: NewNodeRequestV1 = { + identity: { + pubKey: this.options.keyPairPub, + memberId: nodeInfo.memberId, + proof: jwt, + }, + treeHash, + node: nodeInfo.node, + ledger: nodeInfo.ledgers, + pluginInstance: nodeInfo.pluginInstances, + signature: await this.sign(msg), + }; + + const response = await managerApi.addNodeToConsortiumV1(request); + + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + // We log the error here on the debug level so that later on we can inspect the contents + // of it in the logs if we need to. The reason that this is important is because we do not + // want to dump the full response onto our own error response that is going back to the caller + // due to that potentially being a security issue that we are exposing internal data via the + // error responses. + // With that said, we still need to make sure that we can determine the root cause of any + // issues after the fact and therefore we must save the error response details somewhere (the logs) + this.log.debug( + "ConsortiumApi non-2xx HTTP response:", + data, + status, + config, + ); + + // For the caller/client we just send back a generic error admitting that we somehow messed up: + const errorMessage = `${fn} ConsortiumStaticApi error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + Checks.truthy(response.data.jws, `${fn}::response.jws`); + Checks.truthy(response.data.jws.payload, `${fn}::response.jws.payload`); + const json = Buffer.from(response.data.jws.payload, "base64").toString(); + const body = JSON.parse(json); + const { consortiumDatabase }: { consortiumDatabase: ConsortiumDatabase } = + body; + + this.repo.populateDb(consortiumDatabase); + this.updateMetricNodeCount(); + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/plugin-factory-consortium-static.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/plugin-factory-consortium-static.ts new file mode 100644 index 00000000000..1382b31839b --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/plugin-factory-consortium-static.ts @@ -0,0 +1,20 @@ +import { + IPluginFactoryOptions, + PluginFactory, +} from "@hyperledger/cactus-core-api"; +import { + IPluginConsortiumStaticOptions, + PluginConsortiumStatic, +} from "./plugin-consortium-static"; + +export class PluginFactoryWebService extends PluginFactory< + PluginConsortiumStatic, + IPluginConsortiumStaticOptions, + IPluginFactoryOptions +> { + async create( + pluginOptions: IPluginConsortiumStaticOptions, + ): Promise { + return new PluginConsortiumStatic(pluginOptions); + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/i-policy.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/i-policy.ts new file mode 100644 index 00000000000..2fd8a3f6281 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/i-policy.ts @@ -0,0 +1,10 @@ +/* +This is an interface to represent the base components of a policy +*/ + +export interface IPolicy { + name: string; + caption: string; + description: string; + id: string; +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-action.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-action.ts new file mode 100644 index 00000000000..4e9a83c5450 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-action.ts @@ -0,0 +1,4 @@ +import { IPolicy } from "./i-policy"; +export interface PolicyAction extends IPolicy { + execute(...args: unknown[]): Promise; +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-condition.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-condition.ts new file mode 100644 index 00000000000..4170c30e035 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-condition.ts @@ -0,0 +1,5 @@ +import { IPolicy } from "./i-policy"; + +export interface PolicyCondition extends IPolicy { + evaluate(...args: unknown[]): Promise; +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-group.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-group.ts new file mode 100644 index 00000000000..adff1ad9f82 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-group.ts @@ -0,0 +1,153 @@ +import { Checks } from "@hyperledger/cactus-common"; +import { PolicyRule } from "./policy-rule"; +import { IPolicyItemOptions, PolicyItem } from "./polity-item"; +import MerkleTree from "merkletreejs"; +import { stringify as safeStableStringify } from "safe-stable-stringify"; + +export interface IPolicyGroupOptions extends IPolicyItemOptions { + role: string; +} + +export class PolicyGroup extends PolicyItem { + private policyGroups: Map; + private policyRules: Map; + private role: string; + private inheritedRoles: string[]; + + constructor(options: IPolicyGroupOptions) { + Checks.nonBlankString(options.role); + super(options); + this.role = options.role; + this.policyGroups = new Map(); + this.policyRules = new Map(); + this.inheritedRoles = []; + } + + public setInheritedRoles(roles: string[]) { + this.inheritedRoles = roles; + } + public getRoles(): string[] { + return Array.prototype.concat(this.inheritedRoles, [this.role]); + } + + public addGroup(group: PolicyGroup) { + if (group.getId() === this.getId()) { + throw Error( + "Can't add subgroup" + group.getId() + " to group " + this.getId(), + ); + } + if (this.policyGroups.get(group.getId())) { + throw Error( + "Group " + group.getId() + " already exists in " + this.getId(), + ); + } + + //TODO: verify role (must be unique) + + group.setInheritedRoles(this.getRoles()); + this.policyGroups.set(group.getId(), group); + } + + public addRule(rule: PolicyRule) { + if (!rule.getRoles().includes(this.role)) { + throw Error( + "PolicyRule" + + rule.getId() + + " is not targeted to policy group with role " + + this.role, + ); + } + if (this.policyRules.get(rule.getId())) { + throw Error( + "PolicyRule " + + rule.getId() + + " already exists in Group " + + this.getId(), + ); + } + + this.policyRules.set(rule.getId(), rule); + } + + public getPolicySubGroupById(id: string): PolicyGroup | undefined { + if (this.policyGroups.get(id)) { + return this.policyGroups.get(id); + } + + for (const group of this.policyGroups.values()) { + const g = group.getPolicySubGroupById(id); + if (g) { + return g; + } + } + + return undefined; + } + + public getPolicySubGroupByRole(role: string): PolicyGroup | undefined { + for (const group of this.policyGroups.values()) { + if (group.getRoles()[group.getRoles().length - 1] === role) { + return group; + } + } + } + public getPolicySubGroupByRoles(roles: string[]): PolicyGroup | undefined { + if (roles.length < 1) return undefined; + let group = this.getPolicySubGroupByRole(roles[0]); + if (!group) { + return undefined; + } + for (const role of roles) { + group = group?.getPolicySubGroupByRole(role); + if (!group) { + return undefined; + } + } + return group; + } + + public buildTreeProof(tree?: { rules: string[]; groups: string[] }): string { + if (!tree) { + tree = this.buildTree(); + } + return new MerkleTree( + Array.prototype.concat(tree.rules, tree.groups), + undefined, + { + sort: true, + hashLeaves: true, + }, + ) + .getRoot() + .toString("hex"); + } + + public buildTree(): { + rules: string[]; + groups: string[]; + } { + const tree: { + rules: string[]; + groups: string[]; + } = { + rules: [], + groups: [], + }; + for (const group of this.policyGroups.values()) { + const tr = group.buildTree(); + tree.rules = Array.prototype.concat(tree.rules, tr.rules); + tree.groups = Array.prototype.concat(tree.groups, tr.groups); + } + const rules = Array.from(this.policyRules.values()).map((rule) => { + return safeStableStringify(rule.asIPolicy()); + }); + const groups = Array.from(this.policyRules.values()).map((rule) => { + return safeStableStringify(rule.asIPolicy()); + }); + + tree.rules = Array.prototype.concat(tree.rules, rules); + tree.groups = Array.prototype.concat(tree.groups, groups); + + return tree; + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-rule.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-rule.ts new file mode 100644 index 00000000000..114cd08d7cd --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/policy-rule.ts @@ -0,0 +1,31 @@ +import { PolicyAction } from "./policy-action"; +import { PolicyCondition } from "./policy-condition"; +import { IPolicyItemOptions, PolicyItem } from "./polity-item"; +export interface IPolicyRuleOptions extends IPolicyItemOptions { + policyAction: PolicyAction; + policyCondition: PolicyCondition; + roles: string[]; +} +export class PolicyRule extends PolicyItem { + private policyCondition: PolicyCondition; + private policyAction: PolicyAction; + private roles: string[]; + constructor(options: IPolicyRuleOptions) { + super(options); + this.policyAction = options.policyAction; + this.policyCondition = options.policyCondition; + this.roles = options.roles; + } + + public async evaluateCondition(): Promise { + return this.policyCondition.evaluate(); + } + + public async executePolicyAction(): Promise { + return this.policyAction.execute(); + } + + public getRoles(): string[] { + return this.roles; + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/polity-item.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/polity-item.ts new file mode 100644 index 00000000000..59b97c08c46 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/policy-model/polity-item.ts @@ -0,0 +1,47 @@ +import { IPolicy } from "./i-policy"; +import { Checks } from "@hyperledger/cactus-common"; +export interface IPolicyItemOptions { + name: string; + caption: string; + description: string; + id: string; +} +export class PolicyItem implements IPolicy { + name: string; + caption: string; + description: string; + id: string; + + constructor(options: IPolicyItemOptions) { + Checks.nonBlankString(options.name); + Checks.nonBlankString(options.caption); + Checks.nonBlankString(options.description); + Checks.nonBlankString(options.id); + this.name = options.name; + this.caption = options.caption; + this.description = options.description; + this.id = options.id; + } + + public getName(): string { + return this.name; + } + public getCaption(): string { + return this.caption; + } + public getDescription(): string { + return this.description; + } + public getId(): string { + return this.id; + } + + public asIPolicy(): IPolicy { + return { + name: this.getName(), + caption: this.getCaption(), + description: this.getDescription(), + id: this.getId(), + }; + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/data-fetcher.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/data-fetcher.ts new file mode 100644 index 00000000000..55869bf1cfe --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/data-fetcher.ts @@ -0,0 +1,12 @@ +import { NodeCount } from "./response.type"; + +import { + totalTxCount, + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT, +} from "./metrics"; + +export async function collectMetrics(nodeCount: NodeCount): Promise { + totalTxCount + .labels(K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT) + .set(nodeCount.counter); +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/metrics.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/metrics.ts new file mode 100644 index 00000000000..b965dd39f41 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/metrics.ts @@ -0,0 +1,11 @@ +import { Gauge } from "prom-client"; + +export const K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT = + "cactus_consortium_manual_total_node_count"; + +export const totalTxCount = new Gauge({ + registers: [], + name: K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT, + help: "Total cactus node count", + labelNames: ["type"], +}); diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/prometheus-exporter.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/prometheus-exporter.ts new file mode 100644 index 00000000000..6c07c56008e --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/prometheus-exporter.ts @@ -0,0 +1,43 @@ +import promClient, { Registry } from "prom-client"; +import { NodeCount } from "./response.type"; +import { + totalTxCount, + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT, +} from "./metrics"; + +export interface IPrometheusExporterOptions { + pollingIntervalInMin?: number; +} + +export class PrometheusExporter { + public readonly metricsPollingIntervalInMin: number; + public readonly nodeCount: NodeCount = { counter: 0 }; + public readonly registry: Registry; + + constructor( + public readonly prometheusExporterOptions: IPrometheusExporterOptions, + ) { + this.metricsPollingIntervalInMin = + prometheusExporterOptions.pollingIntervalInMin || 1; + this.registry = new Registry(); + } + + public setNodeCount(nodeCount: number): void { + this.nodeCount.counter = nodeCount; + totalTxCount + .labels(K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT) + .set(this.nodeCount.counter); + } + + public async getPrometheusMetrics(): Promise { + const result = await this.registry.getSingleMetricAsString( + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT, + ); + return result; + } + + public startMetricsCollection(): void { + this.registry.registerMetric(totalTxCount); + promClient.collectDefaultMetrics({ register: this.registry }); + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/response.type.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/response.type.ts new file mode 100644 index 00000000000..4f2cf2ac0c9 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/prometheus-exporter/response.type.ts @@ -0,0 +1,3 @@ +export type NodeCount = { + counter: number; +}; diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/public-api.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/public-api.ts new file mode 100755 index 00000000000..a0c7e6ca2cf --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/public-api.ts @@ -0,0 +1,31 @@ +import * as OpenApiJson from "../json/openapi.json"; +export { OpenApiJson }; + +export { + GetConsortiumEndpointV1, + IGetConsortiumJwsEndpointOptions, +} from "./consortium/get-consortium-jws-endpoint-v1"; + +export { + GetNodeJwsEndpoint, + IGetNodeJwsEndpointOptions, +} from "./consortium/get-node-jws-endpoint-v1"; + +export { + PluginConsortiumStatic, + IPluginConsortiumStaticOptions, + IWebAppOptions, +} from "./plugin-consortium-static"; + +export * from "./generated/openapi/typescript-axios/index"; + +export { PluginFactoryWebService } from "./plugin-factory-consortium-static"; + +import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; +import { PluginFactoryWebService } from "./plugin-factory-consortium-static"; + +export async function createPluginFactory( + pluginFactoryOptions: IPluginFactoryOptions, +): Promise { + return new PluginFactoryWebService(pluginFactoryOptions); +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/repository/static-consortium-provider.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/repository/static-consortium-provider.ts new file mode 100644 index 00000000000..9775ad5c424 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/repository/static-consortium-provider.ts @@ -0,0 +1,72 @@ +import { + Logger, + LogLevelDesc, + LoggerProvider, +} from "@hyperledger/cactus-common"; + +import { Checks, IAsyncProvider } from "@hyperledger/cactus-common"; +import { ConsortiumDatabase } from "@hyperledger/cactus-core-api"; + +import { + DefaultApi as ConsortiumStaticApi, + GetConsortiumJwsResponse, +} from "../generated/openapi/typescript-axios/api"; + +export interface IStaticConsortiumProviderOptions { + logLevel?: LogLevelDesc; + apiClient: ConsortiumStaticApi; +} + +export class StaticConsortiumProvider + implements IAsyncProvider +{ + public static readonly CLASS_NAME = "StaticConsortiumProvider"; + + private readonly log: Logger; + + public get className(): string { + return StaticConsortiumProvider.CLASS_NAME; + } + + constructor(public readonly options: IStaticConsortiumProviderOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + parseConsortiumJws(response: GetConsortiumJwsResponse): ConsortiumDatabase { + const fnTag = `StaticConsortiumProvider#parseConsortiumJws()`; + + Checks.truthy(response, `${fnTag}::response`); + Checks.truthy(response.jws, `${fnTag}::response.jws`); + Checks.truthy(response.jws.payload, `${fnTag}::response.jws.payload`); + + const json = Buffer.from(response.jws.payload, "base64").toString(); + const body = JSON.parse(json); + const { consortiumDatabase }: { consortiumDatabase: ConsortiumDatabase } = + body; + + Checks.truthy(consortiumDatabase, `${fnTag}::consortiumDatabase`); + + // FIXME Ideally there would be an option here to validate the JWS based on + // all the signatures and the corresponding public keys (which the caller + // would have to be able to supply). + // We do not yet have this crypto functions available in a cross platform + // manner so it is omitted for now but much needed prior to any GA release. + return consortiumDatabase; + } + + public async get(): Promise { + try { + const res = await this.options.apiClient.getConsortiumJwsV1(); + return this.parseConsortiumJws(res.data); + } catch (ex) { + const innerException = (ex.toJSON && ex.toJSON()) || ex; + this.log.error(`Request for Consortium JWS failed: `, innerException); + throw ex; + } + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/repository/static-consortium-repository.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/repository/static-consortium-repository.ts new file mode 100644 index 00000000000..b75925af74b --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/repository/static-consortium-repository.ts @@ -0,0 +1,199 @@ +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, +} from "@hyperledger/cactus-common"; + +import { + CactusNode, + ConsortiumDatabase, + ICactusPluginOptions, + Ledger, + PluginInstance, +} from "@hyperledger/cactus-core-api"; +import { PolicyGroup } from "../policy-model/policy-group"; +import MerkleTree from "merkletreejs"; +import { JWK } from "jose"; +import { verifyOrganization } from "../utils"; + +export interface IStaticConsortiumRepositoryOptions { + logLevel?: LogLevelDesc; + db: ConsortiumDatabase; + entitiesJWK: { [key: string]: JWK }; + rootPolicyGroup?: PolicyGroup; + packageConfigs?: { [key: string]: unknown }; + + //data about self + node: CactusNode; + ledgers: Ledger[]; + pluginInstances: PluginInstance[]; + memberId: string; +} + +/** + * Class responsible for making it convenient for developers to query the + * `ConsortiumDatabase` model type which is a flat data structure storing + * all the different types of entities for Consortium representation such as + * `CactusNode`, `ConsortiumMember`, `Ledger` etc.. + */ +export class StaticConsortiumRepository { + public static readonly CLASS_NAME = "StaticConsortiumRepository"; + + private readonly log: Logger; + private readonly db: ConsortiumDatabase; + + //data about self + private readonly node: CactusNode; + private readonly ledgers: Ledger[]; + private readonly pluginInstances: PluginInstance[]; + private readonly memberId: string; + + private readonly rootPolicyGroup: PolicyGroup | undefined; + + // map of package names to configs + // The idea is that we can assert configurations (security, etc) of some essential packages + // Possibly in the future we may want to share them (?) + private readonly packageConfigs: Map; + + private readonly entitiesJWK: Map; + + public get className(): string { + return StaticConsortiumRepository.CLASS_NAME; + } + + constructor(public readonly options: IStaticConsortiumRepositoryOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.db, `${fnTag} arg options.db`); + Checks.truthy(options.db.cactusNode, `${fnTag} arg options.db.cactusNode`); + this.db = options.db; + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + + this.node = options.node; + this.ledgers = options.ledgers; + this.pluginInstances = options.pluginInstances; + this.memberId = options.memberId; + + this.rootPolicyGroup = options.rootPolicyGroup; + + this.packageConfigs = new Map(); + if (options.packageConfigs) { + for (const config in options.packageConfigs) { + if (!this.packageConfigs.get(config)) { + //unique configs for each package name + this.packageConfigs.set(config, options.packageConfigs[config]); + } + } + } + + //this map stores the JWK of each entity that belongs to the consortium + //the map cannot be updated (we do not add organizations) + this.entitiesJWK = new Map(); + for (const entity in options.entitiesJWK) { + if (!this.entitiesJWK.get(entity)) { + //unique jwk for each entity + this.entitiesJWK.set(entity, options.entitiesJWK[entity]); + } + } + } + + public get consortiumDatabase(): ConsortiumDatabase { + return this.options.db; + } + + public get allNodes(): CactusNode[] { + return this.options.db.cactusNode; + } + + public getPolicyTreeProof(): string { + if (!this.rootPolicyGroup) { + return ""; + } + return this.rootPolicyGroup.buildTreeProof(); + } + public getConfigsProof(): string { + if (this.packageConfigs.size === 0) { + return ""; + } + return new MerkleTree( + Array.from(this.packageConfigs.entries()), + undefined, + { + sort: true, + hashLeaves: true, + }, + ) + .getRoot() + .toString("hex"); + } + + /** + * Queries the complete list of nodes within the consortium to obtain a sub- + * set of `CactusNode`s which are connected to a `Ledger` with the given + * `ledgerId`. + * @param ledgerId The ID of the ledger to filter nodes based on. + * @throws {Error} If `ledgerId` is falsy or blank. + */ + public nodesWithLedger(ledgerId: string): CactusNode[] { + const fnTag = `${this.className}#nodesWithLedger()`; + Checks.nonBlankString(ledgerId, `${fnTag}:ledgerId`); + + return this.allNodes.filter((cn) => cn.ledgerIds.includes(ledgerId)); + } + public addNode( + node: CactusNode, + pluginInstance: PluginInstance[], + ledger: Ledger[], + ) { + const member = this.db.consortiumMember.find((m) => m.id === node.memberId); + if (!member) { + throw new Error( + "New node does not belong to an organization of this consortium", + ); + } + this.db.cactusNode.push(node); + this.db.pluginInstance.push(...pluginInstance); + this.db.ledger.push(...ledger); + member.nodeIds.push(node.id); + } + + public populateDb(newDb: ConsortiumDatabase) { + this.db.cactusNode = newDb.cactusNode; + this.db.consortium = newDb.consortium; + this.db.consortiumMember = newDb.consortiumMember; + this.db.ledger = newDb.ledger; + this.db.pluginInstance = newDb.pluginInstance; + } + + public getSelfData() { + return { + node: this.node, + ledgers: this.ledgers, + pluginInstances: this.pluginInstances, + memberId: this.memberId, + }; + } + + public async verifyJWT(jwt: string, memberId: string): Promise { + const fn = this.className + "#verifyJWT"; + const jwk = this.entitiesJWK.get(memberId); + const member = this.db.consortiumMember.find((member) => { + return member.id === memberId; + }); + if (!jwk || !member) { + this.log.debug( + fn + ": memberID:" + memberId + "is not part of this consortium.", + ); + return false; + } + const result = await verifyOrganization(jwt, jwk, member.name); + if (typeof result === "string") { + throw new Error(fn + result); + } + return result; + } +} diff --git a/packages/cacti-plugin-consortium-static/src/main/typescript/utils.ts b/packages/cacti-plugin-consortium-static/src/main/typescript/utils.ts new file mode 100644 index 00000000000..fae90251b03 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/main/typescript/utils.ts @@ -0,0 +1,91 @@ +import { + generateKeyPair, + exportJWK, + jwtVerify, + importJWK, + JWK, + SignJWT, + KeyLike, +} from "jose"; +import { stringify as safeStableStringify } from "safe-stable-stringify"; + +import { v4 as uuidv4 } from "uuid"; + +export async function generateES256JWK(): Promise<{ pub: JWK; priv: JWK }> { + // Generate an ES256 (Elliptic Curve using P-256) key pair + const { publicKey, privateKey } = await generateKeyPair("ES256K"); + + // Export the public key as a JWK + const publicJWK: JWK = await exportJWK(publicKey); + + // Export the private key as a JWK + const privateJWK: JWK = await exportJWK(privateKey); + + return { pub: publicJWK, priv: privateJWK }; +} + +export async function verifyOrganization( + token: string, + jwk: JWK, + expectedOrganization: string, +): Promise { + try { + // Import the JWK as a public key + const publicKey: Uint8Array | KeyLike = await importJWK(jwk, "ES256K"); + + // Verify the JWT using the public key + const { payload } = await jwtVerify(token, publicKey); + + // Check if the organization name matches + if (payload.iss !== expectedOrganization) { + return false; + } + + //TODO: verify payload.jti against remote log of the organization. + if (!payload.exp) { + return "Your token does not have an expiration date."; + } + if (payload.exp < Date.now()) { + return "The token has expired"; + } + + return true; + } catch (err) { + console.error(err); + return false; + } +} + +interface TokenPayload { + iss: string; // Issuer (e.g., the issuing authority) + exp: number; // Expiration time (UNIX timestamp) + jti?: string; // id of token +} +export async function issueOrgToken( + jwk: JWK, + parameters: TokenPayload, + memberId: string, + pubKey: string, +): Promise { + // Import the JWK to get the key object + const key = await importJWK(jwk, "ES256K"); + const payloadObject = { pubKey, memberId }; + const payload = safeStableStringify(payloadObject); + const encoder = new TextEncoder(); + const data = encoder.encode(payload); + const _protected = { + iat: Date.now(), + jti: parameters.jti ? parameters.jti : uuidv4(), + iss: parameters.iss, + exp: parameters.exp, + }; + // Create and sign the JWT + const jwt = await new SignJWT({ data: Array.from(data) }) + .setProtectedHeader({ alg: "ES256K", typ: "JWT" }) // Set the algorithm and token type + .setExpirationTime(_protected.exp) + .setIssuer(_protected.iss) + .setJti(_protected.jti) + .setIssuedAt(_protected.iat) + .sign(key); // Sign the JWT with the JWK + return jwt; +} diff --git a/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-routing-node-to-node.test.ts b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-routing-node-to-node.test.ts new file mode 100644 index 00000000000..909f17ef101 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-routing-node-to-node.test.ts @@ -0,0 +1,396 @@ +import { AddressInfo } from "net"; + +import "jest-extended"; +import { v4 as uuidV4 } from "uuid"; +import { generateKeyPair, exportSPKI, exportPKCS8 } from "jose"; +import Web3 from "web3"; + +import { ApiClient } from "@hyperledger/cactus-api-client"; +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + CactusNode, + Configuration, + Consortium, + ConsortiumDatabase, + ConsortiumMember, + Ledger, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { DefaultApi as ConsortiumStaticApi } from "../../../main/typescript"; +import { + DefaultApi as BesuApi, + PluginLedgerConnectorBesu, + ReceiptType, + Web3SigningCredentialType, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + pruneDockerAllIfGithubAction, + BesuTestLedger, +} from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc, Servers } from "@hyperledger/cactus-common"; + +import { Account } from "web3-core"; +import { + IPluginConsortiumStaticOptions, + PluginConsortiumStatic, +} from "../../../main/typescript"; +import { StaticConsortiumProvider } from "../../../main/typescript/repository/static-consortium-provider"; +import { + generateES256JWK, + issueOrgToken, +} from "../../../main/typescript/utils"; + +const logLevel: LogLevelDesc = "TRACE"; +const testCase = "Routes to correct node based on ledger ID"; +const testCase1 = "Set Up Test ledgers, Consortium, Cactus Nodes"; +const testCase2 = "ApiClient #1 Routes based on Ledger ID #1"; +const testCase3 = "ApiClient #1 Routes based on Ledger ID #2"; + +describe(testCase, () => { + const besuTestLedger1 = new BesuTestLedger(); + const besuTestLedger2 = new BesuTestLedger(); + let consortiumDatabase: ConsortiumDatabase; + let mainApiClient: ApiClient; + + let initialFundsAccount1: string; + let initialFundsAccount2: string; + let keyPair1: any; + let keyPair2: any; + let addressInfo1: AddressInfo; + let addressInfo2: AddressInfo; + let httpServer1: any; + let httpServer2: any; + + let node1: CactusNode; + let node2: CactusNode; + let consortium: Consortium; + let member1: ConsortiumMember; + let member2: ConsortiumMember; + + let apiServer1: ApiServer; + let apiServer2: ApiServer; + let testEthAccount1: Account; + + let entitiesJWK: any; + let entity1JWK: any; + let entity2JWK: any; + + const ledger1: Ledger = { + id: "my_cool_ledger_that_i_want_to_transact_on", + ledgerType: LedgerType.Besu2X, + }; + const ledger2: Ledger = { + id: "other_ledger_that_is_just_taking_up_space", + ledgerType: LedgerType.Besu2X, + }; + + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy; + }); + + beforeAll(async () => { + await besuTestLedger1.start(); + await besuTestLedger2.start(); + + testEthAccount1 = await besuTestLedger1.createEthTestAccount(); + + httpServer1 = await Servers.startOnPreferredPort(4050); + addressInfo1 = httpServer1.address() as AddressInfo; + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + + httpServer2 = await Servers.startOnPreferredPort(4100); + addressInfo2 = httpServer2.address() as AddressInfo; + const node2Host = `http://${addressInfo2.address}:${addressInfo2.port}`; + + keyPair1 = await generateKeyPair("ES256K"); + const pubKeyPem1 = await exportSPKI(keyPair1.publicKey); + + keyPair2 = await generateKeyPair("ES256K"); + const pubKeyPem2 = await exportSPKI(keyPair2.publicKey); + + const consortiumId = uuidV4(); + const consortiumName = "Example Corp. & Friends Crypto Consortium"; + const memberId1 = uuidV4(); + const memberId2 = uuidV4(); + + node1 = { + nodeApiHost: node1Host, + publicKeyPem: pubKeyPem1, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger1.id], + memberId: memberId1, + pluginInstanceIds: [], + }; + + member1 = { + id: memberId1, + name: "Example Corp 1", + nodeIds: [node1.id], + }; + entity1JWK = await generateES256JWK(); + + node2 = { + nodeApiHost: node2Host, + publicKeyPem: pubKeyPem2, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger2.id], + memberId: memberId2, + pluginInstanceIds: [], + }; + + member2 = { + id: memberId2, + name: "Example Corp 2", + nodeIds: [], + }; + + entity2JWK = await generateES256JWK(); + entitiesJWK = { [memberId1]: entity1JWK.pub, [memberId2]: entity2JWK.pub }; + consortium = { + id: consortiumId, + mainApiHost: node1Host, + name: consortiumName, + memberIds: [member1.id, member2.id], + }; + + consortiumDatabase = { + cactusNode: [node1], + consortium: [consortium], + consortiumMember: [member1, member2], + ledger: [ledger1], + pluginInstance: [], + }; + + const config = new Configuration({ basePath: consortium.mainApiHost }); + mainApiClient = new ApiClient(config); + }); + + afterAll(async () => { + await besuTestLedger1.stop(); + await besuTestLedger1.destroy(); + await besuTestLedger2.stop(); + await besuTestLedger2.destroy(); + await apiServer1.shutdown(); + await apiServer2.shutdown(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + + test(testCase1, async () => { + const rpcApiHttpHost1 = await besuTestLedger1.getRpcApiHttpHost(); + const rpcApiWsHost1 = await besuTestLedger1.getRpcApiWsHost(); + + initialFundsAccount1 = besuTestLedger1.getGenesisAccountPubKey(); + + const rpcApiHttpHost2 = await besuTestLedger2.getRpcApiHttpHost(); + const rpcApiWsHost2 = await besuTestLedger2.getRpcApiWsHost(); + + initialFundsAccount2 = besuTestLedger2.getGenesisAccountPubKey(); + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost1, + rpcApiWsHost: rpcApiWsHost1, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const keyPairPem = await exportPKCS8(keyPair1.privateKey); + const pub = await exportSPKI(keyPair2.publicKey); + console.log(pub); + + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase, + node: node1, + ledgers: [ledger1], + pluginInstances: [], + memberId: member1.id, + logLevel, + entitiesJWK, + }; + const pluginConsortiumStatic = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic); + pluginRegistry.add(pluginBesuConnector); + + apiServer1 = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer1.start(); + } + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost2, + rpcApiWsHost: rpcApiWsHost2, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const consortiumDatabase1 = { + cactusNode: [], + consortium: [], + consortiumMember: [], + ledger: [], + pluginInstance: [], + }; + + const keyPairPem = await exportPKCS8(keyPair2.privateKey); + const pub = await exportSPKI(keyPair2.publicKey); + console.log(pub); + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase1, + node: node2, + ledgers: [ledger2], + pluginInstances: [], + memberId: member2.id, + logLevel, + entitiesJWK, + }; + const pluginConsortiumStatic = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo2.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.plugins = []; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic); + pluginRegistry.add(pluginBesuConnector); + + apiServer2 = new ApiServer({ + httpServerApi: httpServer2, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer2.start(); + + const consortApi = mainApiClient.extendWith(ConsortiumStaticApi); + //node asks to join consortium, not manual anymore + const jwt = await issueOrgToken( + entity2JWK.priv, + { + iss: member2.name, + exp: Date.now() + 5 * 60 * 1000, //expires in 5min + }, + member2.id, + pub, + ); + await pluginConsortiumStatic.joinConsortium(consortApi, jwt); + // test.onFinish(() => apiServer.shutdown()); + // afterAll(async () => await apiServer.shutdown()); + } + }); + test(testCase2, async () => { + const apiClient1 = await mainApiClient.ofLedger( + ledger1.id, + BesuApi, + {}, + new StaticConsortiumProvider({ + apiClient: mainApiClient.extendWith(ConsortiumStaticApi), + }), + ); + const testAccount1 = new Web3().eth.accounts.create(uuidV4()); + const res = await apiClient1.runTransactionV1({ + transactionConfig: { + from: initialFundsAccount1, + to: testAccount1.address, + value: 10e6, + gas: 1000000, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + web3SigningCredential: { + ethAccount: testEthAccount1.address, + secret: testEthAccount1.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + + expect(res).toBeTruthy(); + expect(res.status).toBeGreaterThan(199); + expect(res.status).toBeLessThan(300); + }); + + test(testCase3, async () => { + const apiClient2 = await mainApiClient.ofLedger( + ledger2.id, + BesuApi, + {}, + new StaticConsortiumProvider({ + apiClient: mainApiClient.extendWith(ConsortiumStaticApi), + }), + ); + const testAccount2 = new Web3().eth.accounts.create(uuidV4()); + const res = await apiClient2.runTransactionV1({ + transactionConfig: { + from: initialFundsAccount2, + to: testAccount2.address, + value: 10e6, + gas: 1000000, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + web3SigningCredential: { + ethAccount: initialFundsAccount2, + secret: besuTestLedger2.getGenesisAccountPrivKey(), + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(res).toBeTruthy(); + expect(res.status).toBeGreaterThan(199); + expect(res.status).toBeLessThan(300); + }); +}); diff --git a/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-surface.test.ts b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-surface.test.ts new file mode 100644 index 00000000000..34aba3a0aea --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-surface.test.ts @@ -0,0 +1,6 @@ +import * as apiSurface from "../../../main/typescript/public-api"; +import "jest-extended"; + +test("Library can be loaded", async () => { + expect(apiSurface).toBeTruthy(); +}); diff --git a/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-test-new-node-broadcast-with-proofs.test.ts b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-test-new-node-broadcast-with-proofs.test.ts new file mode 100644 index 00000000000..545e68aebe5 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-test-new-node-broadcast-with-proofs.test.ts @@ -0,0 +1,510 @@ +import { AddressInfo } from "net"; + +import "jest-extended"; +import { v4 as uuidV4 } from "uuid"; +import { generateKeyPair, exportSPKI, exportPKCS8 } from "jose"; + +import { ApiClient } from "@hyperledger/cactus-api-client"; +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + CactusNode, + Configuration, + Consortium, + ConsortiumDatabase, + ConsortiumMember, + Ledger, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { DefaultApi as ConsortiumStaticApi } from "../../../main/typescript"; +import { PluginLedgerConnectorBesu } from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + pruneDockerAllIfGithubAction, + BesuTestLedger, +} from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc, Servers } from "@hyperledger/cactus-common"; + +import { + IPluginConsortiumStaticOptions, + PluginConsortiumStatic, +} from "../../../main/typescript"; +import { K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT } from "../../../main/typescript/prometheus-exporter/metrics"; +import { + IPolicyGroupOptions, + PolicyGroup, +} from "../../../main/typescript/policy-model/policy-group"; +import { + generateES256JWK, + issueOrgToken, +} from "../../../main/typescript/utils"; + +const logLevel: LogLevelDesc = "TRACE"; +const testCase = "Broadcasts new node request to all consortium"; +const testCase1 = "Set Up Test ledgers, Consortium, Cactus Nodes"; + +describe(testCase, () => { + const besuTestLedger1 = new BesuTestLedger(); + const besuTestLedger2 = new BesuTestLedger(); + let consortiumDatabase: ConsortiumDatabase; + let mainApiClient: ApiClient; + + let keyPair1: any; + let keyPair2: any; + let keyPair3: any; + let addressInfo1: AddressInfo; + let addressInfo2: AddressInfo; + let addressInfo3: AddressInfo; + let httpServer1: any; + let httpServer2: any; + let httpServer3: any; + + let node1: CactusNode; + let node2: CactusNode; + let node3: CactusNode; + let consortium: Consortium; + let member1: ConsortiumMember; + let member2: ConsortiumMember; + + let apiServer1: ApiServer; + let apiServer2: ApiServer; + let apiServer3: ApiServer; + + let pluginConsortiumStatic1: PluginConsortiumStatic; + let pluginConsortiumStatic2: PluginConsortiumStatic; + let pluginConsortiumStatic3: PluginConsortiumStatic; + + let entitiesJWK: any; + let entity1JWK: any; + let entity2JWK: any; + + const sourcePolicyOptions: IPolicyGroupOptions = { + role: "Base", + name: "Root Policy group", + caption: "P Root", + description: "This is a mock policy group with no use", + id: "someId", + }; + const secondPolicyOptions: IPolicyGroupOptions = { + role: "second", + name: "second Policy group", + caption: "lvl 2", + description: "This is a mock policy group with no use", + id: "someId2", + }; + const thirdPolicyOptions: IPolicyGroupOptions = { + role: "third", + name: "third Policy group", + caption: "lvl 3", + description: "This is a mock policy group with no use", + id: "someId3", + }; + const sourcePolicy1: PolicyGroup = new PolicyGroup(sourcePolicyOptions); + const sourcePolicy2: PolicyGroup = new PolicyGroup(sourcePolicyOptions); + const sourcePolicy3: PolicyGroup = new PolicyGroup(sourcePolicyOptions); + const secondPolicy1: PolicyGroup = new PolicyGroup(secondPolicyOptions); + const secondPolicy2: PolicyGroup = new PolicyGroup(secondPolicyOptions); + const secondPolicy3: PolicyGroup = new PolicyGroup(secondPolicyOptions); + const thirdPolicy1: PolicyGroup = new PolicyGroup(thirdPolicyOptions); + const thirdPolicy2: PolicyGroup = new PolicyGroup(thirdPolicyOptions); + const thirdPolicy3: PolicyGroup = new PolicyGroup(thirdPolicyOptions); + + secondPolicy1.addGroup(thirdPolicy1); + sourcePolicy1.addGroup(secondPolicy1); + + secondPolicy2.addGroup(thirdPolicy2); + sourcePolicy2.addGroup(secondPolicy2); + + secondPolicy3.addGroup(thirdPolicy3); + sourcePolicy3.addGroup(secondPolicy3); + + const packageConfigs = { + "@hyperledger/cactus-api-server": { + apiTlsEnabled: false, + }, + }; + + const ledger1: Ledger = { + id: "my_cool_ledger_that_i_want_to_transact_on", + ledgerType: LedgerType.Besu2X, + }; + const ledger2: Ledger = { + id: "other_ledger_that_is_just_taking_up_space", + ledgerType: LedgerType.Besu2X, + }; + + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy; + }); + + beforeAll(async () => { + await besuTestLedger1.start(); + await besuTestLedger2.start(); + + httpServer1 = await Servers.startOnPreferredPort(4050); + addressInfo1 = httpServer1.address() as AddressInfo; + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + + httpServer2 = await Servers.startOnPreferredPort(4100); + addressInfo2 = httpServer2.address() as AddressInfo; + const node2Host = `http://${addressInfo2.address}:${addressInfo2.port}`; + + httpServer3 = await Servers.startOnPreferredPort(4150); + addressInfo3 = httpServer2.address() as AddressInfo; + const node3Host = `http://${addressInfo2.address}:${addressInfo2.port}`; + + keyPair1 = await generateKeyPair("ES256K"); + const pubKeyPem1 = await exportSPKI(keyPair1.publicKey); + + keyPair2 = await generateKeyPair("ES256K"); + const pubKeyPem2 = await exportSPKI(keyPair2.publicKey); + + keyPair3 = await generateKeyPair("ES256K"); + const pubKeyPem3 = await exportSPKI(keyPair3.publicKey); + + const consortiumId = uuidV4(); + const consortiumName = "Example Corp. & Friends Crypto Consortium"; + const memberId1 = uuidV4(); + const memberId2 = uuidV4(); + + node1 = { + nodeApiHost: node1Host, + publicKeyPem: pubKeyPem1, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger1.id], + memberId: memberId1, + pluginInstanceIds: [], + }; + + member1 = { + id: memberId1, + name: "Example Corp 1", + nodeIds: [node1.id], + }; + + entity1JWK = await generateES256JWK(); + + node2 = { + nodeApiHost: node2Host, + publicKeyPem: pubKeyPem2, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger2.id], + memberId: memberId2, + pluginInstanceIds: [], + }; + + member2 = { + id: memberId2, + name: "Example Corp 2", + nodeIds: [], + }; + + entity2JWK = await generateES256JWK(); + entitiesJWK = { [memberId1]: entity1JWK.pub, [memberId2]: entity2JWK.pub }; + + node3 = { + nodeApiHost: node3Host, + publicKeyPem: pubKeyPem3, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger2.id], + memberId: memberId2, + pluginInstanceIds: [], + }; + + consortium = { + id: consortiumId, + mainApiHost: node1Host, + name: consortiumName, + memberIds: [member1.id, member2.id], + }; + + consortiumDatabase = { + cactusNode: [node1], + consortium: [consortium], + consortiumMember: [member1, member2], + ledger: [ledger1], + pluginInstance: [], + }; + + const config = new Configuration({ basePath: consortium.mainApiHost }); + mainApiClient = new ApiClient(config); + }); + + afterAll(async () => { + await besuTestLedger1.stop(); + await besuTestLedger1.destroy(); + await besuTestLedger2.stop(); + await besuTestLedger2.destroy(); + await apiServer1.shutdown(); + await apiServer2.shutdown(); + await apiServer3.shutdown(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + + test(testCase1, async () => { + const rpcApiHttpHost1 = await besuTestLedger1.getRpcApiHttpHost(); + const rpcApiWsHost1 = await besuTestLedger1.getRpcApiWsHost(); + + const rpcApiHttpHost2 = await besuTestLedger2.getRpcApiHttpHost(); + const rpcApiWsHost2 = await besuTestLedger2.getRpcApiWsHost(); + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost1, + rpcApiWsHost: rpcApiWsHost1, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const keyPairPem = await exportPKCS8(keyPair1.privateKey); + const pub = await exportSPKI(keyPair1.publicKey); + + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase, + node: node1, + ledgers: [ledger1], + pluginInstances: [], + rootPolicyGroup: sourcePolicy1, + packageConfigs, + memberId: member1.id, + logLevel, + entitiesJWK, + }; + pluginConsortiumStatic1 = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic1); + pluginRegistry.add(pluginBesuConnector); + + apiServer1 = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer1.start(); + } + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost2, + rpcApiWsHost: rpcApiWsHost2, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const consortiumDatabase1 = { + cactusNode: [], + consortium: [], + consortiumMember: [], + ledger: [], + pluginInstance: [], + }; + + const keyPairPem = await exportPKCS8(keyPair2.privateKey); + const pub = await exportSPKI(keyPair2.publicKey); + + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase1, + node: node2, + ledgers: [ledger2], + rootPolicyGroup: sourcePolicy2, + packageConfigs, + pluginInstances: [], + memberId: member2.id, + logLevel, + entitiesJWK, + }; + pluginConsortiumStatic2 = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo2.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.plugins = []; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic2); + pluginRegistry.add(pluginBesuConnector); + + apiServer2 = new ApiServer({ + httpServerApi: httpServer2, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer2.start(); + + const consortApi = mainApiClient.extendWith(ConsortiumStaticApi); + + const jwt = await issueOrgToken( + entity2JWK.priv, + { + iss: member2.name, + exp: Date.now() + 5 * 60 * 1000, //expires in 5min + }, + member2.id, + pub, + ); + await pluginConsortiumStatic2.joinConsortium(consortApi, jwt); + + const promMetricsOutput = + "# HELP " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " Total cactus node count\n" + + "# TYPE " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " gauge\n" + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '{type="' + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '"} 2'; + const res1 = await pluginConsortiumStatic1.getPrometheusExporterMetrics(); + const res2 = await pluginConsortiumStatic2.getPrometheusExporterMetrics(); + expect(res1).toEqual(promMetricsOutput); + expect(res2).toEqual(promMetricsOutput); + } + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost2, + rpcApiWsHost: rpcApiWsHost2, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const consortiumDatabase1 = { + cactusNode: [], + consortium: [], + consortiumMember: [], + ledger: [], + pluginInstance: [], + }; + + const keyPairPem = await exportPKCS8(keyPair3.privateKey); + const pub = await exportSPKI(keyPair3.publicKey); + + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase1, + node: node3, + rootPolicyGroup: sourcePolicy3, + packageConfigs, + ledgers: [ledger2], + pluginInstances: [], + memberId: member2.id, + logLevel, + entitiesJWK, + }; + pluginConsortiumStatic3 = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo3.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.plugins = []; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic3); + pluginRegistry.add(pluginBesuConnector); + + apiServer3 = new ApiServer({ + httpServerApi: httpServer3, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer3.start(); + + const consortApi = mainApiClient.extendWith(ConsortiumStaticApi); + const jwt = await issueOrgToken( + entity2JWK.priv, + { + iss: member2.name, + exp: Date.now() + 5 * 60 * 1000, //expires in 5min + }, + member2.id, + pub, + ); + await pluginConsortiumStatic3.joinConsortium(consortApi, jwt); + } + { + //final test + const res1 = await pluginConsortiumStatic1.getPrometheusExporterMetrics(); + const res2 = await pluginConsortiumStatic2.getPrometheusExporterMetrics(); + const res3 = await pluginConsortiumStatic3.getPrometheusExporterMetrics(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " Total cactus node count\n" + + "# TYPE " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " gauge\n" + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '{type="' + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '"} 3'; + + //all plugins have consortium updated + expect(res1.includes(promMetricsOutput)).toBeTruthy(); + expect(res2.includes(promMetricsOutput)).toBeTruthy(); + expect(res3.includes(promMetricsOutput)).toBeTruthy(); + } + }); +}); diff --git a/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-test-new-node-broadcast.test.ts b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-test-new-node-broadcast.test.ts new file mode 100644 index 00000000000..d4407077347 --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/test/typescript/integration/api-test-new-node-broadcast.test.ts @@ -0,0 +1,452 @@ +import { AddressInfo } from "net"; + +import "jest-extended"; +import { v4 as uuidV4 } from "uuid"; +import { generateKeyPair, exportSPKI, exportPKCS8 } from "jose"; + +import { ApiClient } from "@hyperledger/cactus-api-client"; +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + CactusNode, + Configuration, + Consortium, + ConsortiumDatabase, + ConsortiumMember, + Ledger, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { DefaultApi as ConsortiumStaticApi } from "../../../main/typescript"; +import { PluginLedgerConnectorBesu } from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + pruneDockerAllIfGithubAction, + BesuTestLedger, +} from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc, Servers } from "@hyperledger/cactus-common"; + +import { + IPluginConsortiumStaticOptions, + PluginConsortiumStatic, +} from "../../../main/typescript"; +import { K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT } from "../../../main/typescript/prometheus-exporter/metrics"; +import { + generateES256JWK, + issueOrgToken, +} from "../../../main/typescript/utils"; + +const logLevel: LogLevelDesc = "TRACE"; +const testCase = "Broadcasts new node request to all consortium"; +const testCase1 = "Set Up Test ledgers, Consortium, Cactus Nodes"; + +describe(testCase, () => { + const besuTestLedger1 = new BesuTestLedger(); + const besuTestLedger2 = new BesuTestLedger(); + let consortiumDatabase: ConsortiumDatabase; + let mainApiClient: ApiClient; + + let keyPair1: any; + let keyPair2: any; + let keyPair3: any; + let addressInfo1: AddressInfo; + let addressInfo2: AddressInfo; + let addressInfo3: AddressInfo; + let httpServer1: any; + let httpServer2: any; + let httpServer3: any; + + let node1: CactusNode; + let node2: CactusNode; + let node3: CactusNode; + let consortium: Consortium; + let member1: ConsortiumMember; + let member2: ConsortiumMember; + + let apiServer1: ApiServer; + let apiServer2: ApiServer; + let apiServer3: ApiServer; + + let pluginConsortiumStatic1: PluginConsortiumStatic; + let pluginConsortiumStatic2: PluginConsortiumStatic; + let pluginConsortiumStatic3: PluginConsortiumStatic; + + let entitiesJWK: any; + let entity1JWK: any; + let entity2JWK: any; + + const ledger1: Ledger = { + id: "my_cool_ledger_that_i_want_to_transact_on", + ledgerType: LedgerType.Besu2X, + }; + const ledger2: Ledger = { + id: "other_ledger_that_is_just_taking_up_space", + ledgerType: LedgerType.Besu2X, + }; + + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy; + }); + + beforeAll(async () => { + await besuTestLedger1.start(); + await besuTestLedger2.start(); + + httpServer1 = await Servers.startOnPreferredPort(4050); + addressInfo1 = httpServer1.address() as AddressInfo; + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + + httpServer2 = await Servers.startOnPreferredPort(4100); + addressInfo2 = httpServer2.address() as AddressInfo; + const node2Host = `http://${addressInfo2.address}:${addressInfo2.port}`; + + httpServer3 = await Servers.startOnPreferredPort(4150); + addressInfo3 = httpServer2.address() as AddressInfo; + const node3Host = `http://${addressInfo2.address}:${addressInfo2.port}`; + + keyPair1 = await generateKeyPair("ES256K"); + const pubKeyPem1 = await exportSPKI(keyPair1.publicKey); + + keyPair2 = await generateKeyPair("ES256K"); + const pubKeyPem2 = await exportSPKI(keyPair2.publicKey); + + keyPair3 = await generateKeyPair("ES256K"); + const pubKeyPem3 = await exportSPKI(keyPair3.publicKey); + + const consortiumId = uuidV4(); + const consortiumName = "Example Corp. & Friends Crypto Consortium"; + const memberId1 = uuidV4(); + const memberId2 = uuidV4(); + + node1 = { + nodeApiHost: node1Host, + publicKeyPem: pubKeyPem1, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger1.id], + memberId: memberId1, + pluginInstanceIds: [], + }; + + member1 = { + id: memberId1, + name: "Example Corp 1", + nodeIds: [node1.id], + }; + entity1JWK = await generateES256JWK(); + + node2 = { + nodeApiHost: node2Host, + publicKeyPem: pubKeyPem2, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger2.id], + memberId: memberId2, + pluginInstanceIds: [], + }; + + member2 = { + id: memberId2, + name: "Example Corp 2", + nodeIds: [], + }; + entity2JWK = await generateES256JWK(); + entitiesJWK = { [memberId1]: entity1JWK.pub, [memberId2]: entity2JWK.pub }; + + node3 = { + nodeApiHost: node3Host, + publicKeyPem: pubKeyPem3, + consortiumId, + id: uuidV4(), + ledgerIds: [ledger2.id], + memberId: memberId2, + pluginInstanceIds: [], + }; + + consortium = { + id: consortiumId, + mainApiHost: node1Host, + name: consortiumName, + memberIds: [member1.id, member2.id], + }; + + consortiumDatabase = { + cactusNode: [node1], + consortium: [consortium], + consortiumMember: [member1, member2], + ledger: [ledger1], + pluginInstance: [], + }; + + const config = new Configuration({ basePath: consortium.mainApiHost }); + mainApiClient = new ApiClient(config); + }); + + afterAll(async () => { + await besuTestLedger1.stop(); + await besuTestLedger1.destroy(); + await besuTestLedger2.stop(); + await besuTestLedger2.destroy(); + await apiServer1.shutdown(); + await apiServer2.shutdown(); + await apiServer3.shutdown(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + + test(testCase1, async () => { + const rpcApiHttpHost1 = await besuTestLedger1.getRpcApiHttpHost(); + const rpcApiWsHost1 = await besuTestLedger1.getRpcApiWsHost(); + + const rpcApiHttpHost2 = await besuTestLedger2.getRpcApiHttpHost(); + const rpcApiWsHost2 = await besuTestLedger2.getRpcApiWsHost(); + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost1, + rpcApiWsHost: rpcApiWsHost1, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const keyPairPem = await exportPKCS8(keyPair1.privateKey); + const pub = await exportSPKI(keyPair1.publicKey); + + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase, + node: node1, + ledgers: [ledger1], + pluginInstances: [], + memberId: member1.id, + logLevel, + entitiesJWK, + }; + pluginConsortiumStatic1 = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic1); + pluginRegistry.add(pluginBesuConnector); + + apiServer1 = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer1.start(); + } + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost2, + rpcApiWsHost: rpcApiWsHost2, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const consortiumDatabase1 = { + cactusNode: [], + consortium: [], + consortiumMember: [], + ledger: [], + pluginInstance: [], + }; + + const keyPairPem = await exportPKCS8(keyPair2.privateKey); + const pub = await exportSPKI(keyPair2.publicKey); + + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase1, + node: node2, + ledgers: [ledger2], + pluginInstances: [], + memberId: member2.id, + logLevel, + entitiesJWK, + }; + pluginConsortiumStatic2 = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo2.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.plugins = []; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic2); + pluginRegistry.add(pluginBesuConnector); + + apiServer2 = new ApiServer({ + httpServerApi: httpServer2, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer2.start(); + + const consortApi = mainApiClient.extendWith(ConsortiumStaticApi); + const jwt = await issueOrgToken( + entity2JWK.priv, + { + iss: member2.name, + exp: Date.now() + 5 * 60 * 1000, //expires in 5min + }, + member2.id, + pub, + ); + await pluginConsortiumStatic2.joinConsortium(consortApi, jwt); + // test.onFinish(() => apiServer.shutdown()); + // afterAll(async () => await apiServer.shutdown()); + const promMetricsOutput = + "# HELP " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " Total cactus node count\n" + + "# TYPE " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " gauge\n" + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '{type="' + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '"} 2'; + const res1 = await pluginConsortiumStatic1.getPrometheusExporterMetrics(); + const res2 = await pluginConsortiumStatic2.getPrometheusExporterMetrics(); + expect(res1).toEqual(promMetricsOutput); + expect(res2).toEqual(promMetricsOutput); + } + + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const pluginBesuConnector = new PluginLedgerConnectorBesu({ + instanceId: uuidV4(), + rpcApiHttpHost: rpcApiHttpHost2, + rpcApiWsHost: rpcApiWsHost2, + logLevel, + pluginRegistry: new PluginRegistry(), + }); + + const consortiumDatabase1 = { + cactusNode: [], + consortium: [], + consortiumMember: [], + ledger: [], + pluginInstance: [], + }; + + const keyPairPem = await exportPKCS8(keyPair3.privateKey); + const pub = await exportSPKI(keyPair3.publicKey); + + const options: IPluginConsortiumStaticOptions = { + instanceId: uuidV4(), + pluginRegistry, + keyPairPem: keyPairPem, + keyPairPub: pub, + consortiumDatabase: consortiumDatabase1, + node: node3, + ledgers: [ledger2], + pluginInstances: [], + memberId: member2.id, + logLevel, + entitiesJWK, + }; + pluginConsortiumStatic3 = new PluginConsortiumStatic(options); + + const configService = new ConfigService(); + const apiServerOptions = await configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo3.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; + apiServerOptions.crpcPort = 0; + apiServerOptions.apiTlsEnabled = false; + apiServerOptions.plugins = []; + apiServerOptions.crpcPort = 0; + const config = + await configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumStatic3); + pluginRegistry.add(pluginBesuConnector); + + apiServer3 = new ApiServer({ + httpServerApi: httpServer3, + config: config.getProperties(), + pluginRegistry, + }); + + await apiServer3.start(); + + const consortApi = mainApiClient.extendWith(ConsortiumStaticApi); + const jwt = await issueOrgToken( + entity2JWK.priv, + { + iss: member2.name, + exp: Date.now() + 5 * 60 * 1000, //expires in 5min + }, + member2.id, + pub, + ); + await pluginConsortiumStatic3.joinConsortium(consortApi, jwt); + } + { + //final test + const res1 = await pluginConsortiumStatic1.getPrometheusExporterMetrics(); + const res2 = await pluginConsortiumStatic2.getPrometheusExporterMetrics(); + const res3 = await pluginConsortiumStatic3.getPrometheusExporterMetrics(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " Total cactus node count\n" + + "# TYPE " + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + " gauge\n" + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '{type="' + + K_CACTUS_CONSORTIUM_MANUAL_TOTAL_NODE_COUNT + + '"} 3'; + + //all plugins have consortium updated + expect(res1.includes(promMetricsOutput)).toBeTruthy(); + expect(res2.includes(promMetricsOutput)).toBeTruthy(); + expect(res3.includes(promMetricsOutput)).toBeTruthy(); + } + }); +}); diff --git a/packages/cacti-plugin-consortium-static/src/test/typescript/unit/api-surface.test.ts b/packages/cacti-plugin-consortium-static/src/test/typescript/unit/api-surface.test.ts new file mode 100644 index 00000000000..34aba3a0aea --- /dev/null +++ b/packages/cacti-plugin-consortium-static/src/test/typescript/unit/api-surface.test.ts @@ -0,0 +1,6 @@ +import * as apiSurface from "../../../main/typescript/public-api"; +import "jest-extended"; + +test("Library can be loaded", async () => { + expect(apiSurface).toBeTruthy(); +}); diff --git a/packages/cacti-plugin-consortium-static/tsconfig.json b/packages/cacti-plugin-consortium-static/tsconfig.json new file mode 100644 index 00000000000..c47050d1c6a --- /dev/null +++ b/packages/cacti-plugin-consortium-static/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib/", + "declarationDir": "dist/lib", + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-consortium-new.tsbuildinfo" + }, + "include": [ + "./src", + "**/openapi.json" + ], + "references": [ + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-core/tsconfig.json" + }, + { + "path": "../cactus-core-api/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/packages/cactus-api-client/src/main/typescript/api-client.ts b/packages/cactus-api-client/src/main/typescript/api-client.ts index df344f0f204..634b3f3a4b4 100644 --- a/packages/cactus-api-client/src/main/typescript/api-client.ts +++ b/packages/cactus-api-client/src/main/typescript/api-client.ts @@ -83,6 +83,7 @@ export class ApiClient extends BaseAPI { ledgerOrId: string | Ledger, ctor: new (configuration?: Configuration) => T, ctorArgs: Record, + consortiumDbProvider?: IAsyncProvider, ): Promise; /** * Constructs a new `ApiClient` object that is tied to whichever Cactus node diff --git a/tsconfig.json b/tsconfig.json index ce7a9f42f89..ff0e6964d92 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,6 +28,9 @@ { "path": "./packages/cactus-plugin-consortium-manual/tsconfig.json" }, + { + "path": "./packages/cacti-plugin-consortium-static/tsconfig.json" + }, { "path": "./packages/cactus-plugin-htlc-eth-besu/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 9d84bf537df..dd1314fab0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9071,6 +9071,34 @@ __metadata: languageName: unknown linkType: soft +"@hyperledger/cacti-plugin-consortium-static@workspace:packages/cacti-plugin-consortium-static": + version: 0.0.0-use.local + resolution: "@hyperledger/cacti-plugin-consortium-static@workspace:packages/cacti-plugin-consortium-static" + dependencies: + "@hyperledger/cactus-api-client": "npm:2.0.0-rc.3" + "@hyperledger/cactus-cmd-api-server": "npm:2.0.0-rc.3" + "@hyperledger/cactus-common": "npm:2.0.0-rc.3" + "@hyperledger/cactus-core": "npm:2.0.0-rc.3" + "@hyperledger/cactus-core-api": "npm:2.0.0-rc.3" + "@hyperledger/cactus-plugin-ledger-connector-besu": "npm:2.0.0-rc.3" + "@hyperledger/cactus-test-tooling": "npm:2.0.0-rc.3" + "@types/express": "npm:4.17.21" + "@types/json-stable-stringify": "npm:1.0.33" + "@types/uuid": "npm:10.0.0" + axios: "npm:1.6.0" + body-parser: "npm:1.20.2" + express: "npm:4.19.2" + http-errors-enhanced-cjs: "npm:2.0.1" + jose: "npm:4.15.5" + merkletreejs: "npm:0.4.0" + prom-client: "npm:15.1.3" + safe-stable-stringify: "npm:2.4.3" + typescript-optional: "npm:2.0.1" + uuid: "npm:10.0.0" + web3: "npm:1.6.1" + languageName: unknown + linkType: soft + "@hyperledger/cacti-plugin-ledger-connector-stellar@workspace:packages/cacti-plugin-ledger-connector-stellar": version: 0.0.0-use.local resolution: "@hyperledger/cacti-plugin-ledger-connector-stellar@workspace:packages/cacti-plugin-ledger-connector-stellar" @@ -38834,6 +38862,19 @@ __metadata: languageName: node linkType: hard +"merkletreejs@npm:0.4.0": + version: 0.4.0 + resolution: "merkletreejs@npm:0.4.0" + dependencies: + bignumber.js: "npm:^9.0.1" + buffer-reverse: "npm:^1.0.1" + crypto-js: "npm:^4.2.0" + treeify: "npm:^1.1.0" + web3-utils: "npm:^1.3.4" + checksum: 10/ebf8bd2ebb42b7f489c8da8ed39e230fa131c0a5b23a2e3be667a0589671df118f996b73d44e5b83f2c70a3c956515328386e94b2841cd279033eca8caf71aee + languageName: node + linkType: hard + "methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2"