diff --git a/.cspell.json b/.cspell.json index 1aecf659be..0b83f409ef 100644 --- a/.cspell.json +++ b/.cspell.json @@ -44,6 +44,7 @@ "couchdb", "COUCHDBADDRESS", "COUCHDBCONFIG", + "cplcs", "Creds", "Crpc", "data", diff --git a/packages/cacti-plugin-ledger-connector-stellar/Dockerfile b/packages/cacti-plugin-ledger-connector-stellar/Dockerfile new file mode 100644 index 0000000000..daa74e2fa5 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/Dockerfile @@ -0,0 +1,5 @@ +FROM ghcr.io/hyperledger/cactus-cmd-api-server:2024-03-18-8ddc02d + +ARG NPM_PKG_VERSION=latest + +RUN npm i @hyperledger/cacti-plugin-ledger-connector-stellar@${NPM_PKG_VERSION} --production diff --git a/packages/cacti-plugin-ledger-connector-stellar/README.md b/packages/cacti-plugin-ledger-connector-stellar/README.md new file mode 100644 index 0000000000..13ccc7ff20 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/README.md @@ -0,0 +1,168 @@ +# `@hyperledger/cacti-plugin-ledger-connector-stellar` + + +## 🚧 **Package Under Construction** 🚧 + +This package is a skeleton for now with no exported modules at all. +Stay tuned for later improvements. + +## 🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧 + +This plugin provides `Cacti` a way to interact with Stellar networks. Using this we can perform: +* Deploy Smart-contracts over the network. +* Build and sign transactions using different keystores. +* Invoke smart-contract functions that we have deployed on the network. +## Summary + + - [Getting Started](#getting-started) + - [Architecture](#architecture) + - [Usage](#usage) + - [Prometheus Exporter](#prometheus-exporter) + - [Runing the tests](#running-the-tests) + - [Built With](#built-with) + - [Contributing](#contributing) + - [License](#license) + - [Acknowledgments](#acknowledgments) + +## Getting Started + +Clone the git repository on your local machine. Follow these instructions that will get you a copy of the project up and running on +your local machine for development and testing purposes. + +### Prerequisites + +In the root of the project to install the dependencies execute the command: +```sh +npm run enable-corepack +yarn configure +``` + +### Compiling + +In the project root folder, run this command to compile the plugin and create the dist directory: +```sh +yarn tsc +``` + +### Architecture + +TODO + +### Usage + +TODO + +### Building/running the container image locally + +In the Cacti project root say: + +```sh +DOCKER_BUILDKIT=1 docker build -f ./packages/cacti-plugin-ledger-connector-stellar/Dockerfile . --tag cplcs --tag cacti-plugin-ledger-connector-stellar +``` + +Build with a specific version of the npm package: +```sh +DOCKER_BUILDKIT=1 docker build --build-arg NPM_PKG_VERSION=2.0.0-alpha.2 -f ./packages/cacti-plugin-ledger-connector-stellar/Dockerfile . --tag cplcs --tag cacti-plugin-ledger-connector-stellar +``` + +#### Running the container + +Launch container with plugin configuration as an **environment variable**: +```sh +docker run \ + --rm \ + --publish 3000:3000 \ + --publish 4000:4000 \ + --env PLUGINS='[{"packageName": "@hyperledger/cacti-plugin-ledger-connector-stellar", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": { "instanceId": "some-unique-stellar-connector-instance-id"}}]' \ + cplcs +``` + +Launch container with plugin configuration as a **CLI argument**: +```sh +docker run \ + --rm \ + --publish 3000:3000 \ + --publish 4000:4000 \ + cplcs \ + ./node_modules/.bin/cactusapi \ + --plugins='[{"packageName": "@hyperledger/cacti-plugin-ledger-connector-stellar", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": { "instanceId": "some-unique-stellar-connector-instance-id"}}]' +``` + +Launch container with **configuration file** mounted from host machine: +```sh + +echo '[{"packageName": "@hyperledger/cacti-plugin-ledger-connector-stellar", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": { "instanceId": "some-unique-stellar-connector-instance-id"}}]' > cactus.json + +docker run \ + --rm \ + --publish 3000:3000 \ + --publish 4000:4000 \ + --mount type=bind,source="$(pwd)"/cactus.json,target=/cactus.json \ + cplcs \ + ./node_modules/.bin/cactusapi \ + --config-file=/cactus.json +``` + +#### Testing API calls with the container + +Don't have a Stellar network on hand to test with? Test or develop against our Stellar All-In-One container! + +TODO + + +## Prometheus Exporter + +This class creates a prometheus exporter, which scrapes the transactions (total transaction count) for the use cases incorporating the use of Stellar connector plugin. + +### Prometheus Exporter Usage + +The prometheus exporter object is initialized in the `PluginLedgerConnectorStellar` class constructor itself, so instantiating the object of the `PluginLedgerConnectorStellar` class, gives access to the exporter object. +You can also initialize the prometheus exporter object seperately and then pass it to the `IPluginLedgerConnectorStellarOptions` interface for `PluginLedgerConnectorStellar` constructor. + +`getPrometheusMetricsV1` function returns the prometheus exporter metrics, currently displaying the total transaction count, which currently increments everytime the `transact()` method of the `PluginLedgerConnectorStellar` class is called. + +### Prometheus Integration + +To use Prometheus with this exporter make sure to install [Prometheus main component](https://prometheus.io/download/). +Once Prometheus is setup, the corresponding scrape_config needs to be added to the prometheus.yml + +```(yaml) +- job_name: 'stellar_ledger_connector_exporter' + metrics_path: api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-prometheus-exporter-metrics + scrape_interval: 5s + static_configs: + - targets: ['{host}:{port}'] +``` + +Here the `host:port` is where the prometheus exporter metrics are exposed. The test cases (For example, `packages/cacti-plugin-ledger-connector-stellar/src/test/typescript/unit/get-open-api-spec-v1-connector-stellar.test.ts`) exposes it over `0.0.0.0` and a random port(). The random port can be found in the running logs of the test case and looks like (42379 in the below mentioned URL) +`Metrics URL: http://0.0.0.0:42379/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-prometheus-exporter-metrics` + +Once edited, you can start the prometheus service by referencing the above edited prometheus.yml file. +On the prometheus graphical interface (defaulted to http://localhost:9090), choose **Graph** from the menu bar, then select the **Console** tab. From the **Insert metric at cursor** drop down, select **cacti_stellar_total_tx_count** and click **execute** + +### Helper code + +###### response.type.ts +This file contains the various responses of the metrics. + +###### data-fetcher.ts +This file contains functions encasing the logic to process the data points + +###### metrics.ts +This file lists all the prometheus metrics and what they are used for. + +## Running the tests + +TODO + +## Contributing + +We welcome contributions to Hyperledger Cactus in many forms, and there’s always plenty to do! + +Please review [CONTIRBUTING.md](../../CONTRIBUTING.md) to get started. + +## License + +This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file. + +## Acknowledgments diff --git a/packages/cacti-plugin-ledger-connector-stellar/openapitools.json b/packages/cacti-plugin-ledger-connector-stellar/openapitools.json new file mode 100644 index 0000000000..225ff1aaae --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/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-ledger-connector-stellar/package.json b/packages/cacti-plugin-ledger-connector-stellar/package.json new file mode 100644 index 0000000000..76090d0799 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/package.json @@ -0,0 +1,100 @@ +{ + "name": "@hyperledger/cacti-plugin-ledger-connector-stellar", + "version": "2.0.0-alpha.2", + "private": true, + "description": "Allows Cacti nodes to connect to a Stellar ledger.", + "keywords": [ + "Hyperledger", + "Cacti", + "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 Cacti Contributors", + "email": "cactus@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cacti" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "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-ledger-connector-stellar.web.umd.js", + "types": "dist/lib/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "codegen": "yarn run --top-level run-s 'codegen:*'", + "codegen:openapi": "npm run generate-sdk", + "generate-sdk": "run-p 'generate-sdk:*'", + "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-alpha.2", + "@hyperledger/cactus-core": "2.0.0-alpha.2", + "@hyperledger/cactus-core-api": "2.0.0-alpha.2", + "axios": "1.6.0", + "express": "4.19.2", + "http-errors-enhanced-cjs": "2.0.1", + "joi": "17.9.1", + "openapi-types": "12.1.3", + "prom-client": "13.2.0", + "run-time-error-cjs": "1.4.0", + "rxjs": "7.8.1", + "socket.io-client-fixed-types": "4.5.4", + "typescript-optional": "2.0.1" + }, + "devDependencies": { + "@hyperledger/cactus-plugin-keychain-memory": "2.0.0-alpha.2", + "@hyperledger/cactus-test-tooling": "2.0.0-alpha.2", + "@types/body-parser": "1.19.4", + "@types/express": "4.17.19", + "@types/http-errors": "2.0.4", + "@types/uuid": "9.0.8", + "body-parser": "1.20.2", + "npm-run-all2": "6.1.2", + "socket.io": "4.5.4", + "uuid": "9.0.1" + }, + "engines": { + "node": ">=18", + "npm": ">=8" + }, + "publishConfig": { + "access": "restricted" + }, + "browserMinified": "dist/cacti-plugin-ledger-connector-stellar.web.umd.min.js", + "mainMinified": "dist/cacti-plugin-ledger-connector-stellar.node.umd.min.js", + "watch": { + "codegen:openapi": { + "patterns": [ + "./src/main/json/openapi.json" + ] + } + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/json/openapi.json b/packages/cacti-plugin-ledger-connector-stellar/src/main/json/openapi.json new file mode 100644 index 0000000000..7c4d1b367c --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/json/openapi.json @@ -0,0 +1,190 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cacti Plugin - Connector Stellar", + "description": "Can perform basic tasks on a Stellar ledger", + "version": "v2.0.0-alpha.2", + "license": { + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "components": { + "schemas": { + "WatchBlocksV1": { + "type": "string", + "enum": [ + "org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Subscribe", + "org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Next", + "org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Unsubscribe", + "org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Error", + "org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Complete" + ], + "x-enum-varnames": [ + "Subscribe", + "Next", + "Unsubscribe", + "Error", + "Complete" + ] + }, + "WatchBlocksV1Progress": { + "type": "object", + "required": ["blockHeader"], + "properties": { + "blockHeader": { + "type": "object", + "additionalProperties": true + } + } + }, + "RunTransactionRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + + } + }, + "RunTransactionResponse": { + "type": "object", + "properties": { + } + }, + "DeployContractV1Request": { + "type": "object", + "additionalProperties": false, + "properties": { + } + }, + "DeployContractV1Response": { + "type": "object", + "properties": { + } + }, + "PrometheusExporterMetricsResponse": { + "type": "string", + "nullable": false + } + } + }, + "paths": { + "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-open-api-spec": { + "get": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "get", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-open-api-spec" + } + }, + "operationId": "getOpenApiSpecV1", + "summary": "Retrieves the .json file that contains the OpenAPI specification for the plugin.", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/deploy-contract": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/deploy-contract" + } + }, + "operationId": "deployContractV1", + "summary": "Deploys a smart contract to the Stellar ledger associated with the connector.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractV1Response" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/run-transaction": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/run-transaction" + } + }, + "operationId": "runTransactionV1", + "summary": "Executes a transaction on a stellar ledger", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunTransactionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunTransactionResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-prometheus-exporter-metrics": { + "get": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "get", + "path": "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-prometheus-exporter-metrics" + } + }, + "operationId": "getPrometheusMetricsV1", + "summary": "Get the Prometheus Metrics", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PrometheusExporterMetricsResponse" + } + } + } + } + } + } + } + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/api-client/stellar-api-client.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/api-client/stellar-api-client.ts new file mode 100644 index 0000000000..9844c1f803 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/api-client/stellar-api-client.ts @@ -0,0 +1,78 @@ +import { Observable, ReplaySubject } from "rxjs"; +import { finalize } from "rxjs/operators"; +import { Socket, io } from "socket.io-client-fixed-types"; +import { Logger, Checks } from "@hyperledger/cactus-common"; +import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common"; +import { Constants, ISocketApiClient } from "@hyperledger/cactus-core-api"; +import { + DefaultApi, + WatchBlocksV1, + WatchBlocksV1Progress, +} from "../generated/openapi/typescript-axios"; +import { Configuration } from "../generated/openapi/typescript-axios/configuration"; + +export class StellarApiClientOptions extends Configuration { + readonly logLevel?: LogLevelDesc; + readonly wsApiHost?: string; + readonly wsApiPath?: string; +} + +export class StellarApiClient + extends DefaultApi + implements ISocketApiClient +{ + public static readonly CLASS_NAME = "StellarApiClient"; + + private readonly log: Logger; + private readonly wsApiHost: string; + private readonly wsApiPath: string; + + public get className(): string { + return StellarApiClient.CLASS_NAME; + } + + constructor(public readonly options: StellarApiClientOptions) { + super(options); + 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 }); + + this.wsApiHost = options.wsApiHost || options.basePath || location.host; + this.wsApiPath = options.wsApiPath || Constants.SocketIoConnectionPathV1; + this.log.debug(`Created ${this.className} OK.`); + this.log.debug(`wsApiHost=${this.wsApiHost}`); + this.log.debug(`wsApiPath=${this.wsApiPath}`); + this.log.debug(`basePath=${this.options.basePath}`); + } + + public watchBlocksV1(): Observable { + const socket: Socket = io(this.wsApiHost, { path: this.wsApiPath }); + const subject = new ReplaySubject(0); + + socket.on(WatchBlocksV1.Next, (data: WatchBlocksV1Progress) => { + subject.next(data); + }); + + socket.on("connect", () => { + console.log("connected OK..."); + socket.emit(WatchBlocksV1.Subscribe); + }); + + socket.connect(); + + return subject.pipe( + finalize(() => { + console.log("FINALIZE - unsubscribing from the stream..."); + socket.emit(WatchBlocksV1.Unsubscribe); + socket.disconnect(); + }), + // TODO: Investigate if we need these below - in theory without these + // it could happen that only the fist subscriber gets the last emitted value + // publishReplay(1), + // refCount(), + ); + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES new file mode 100644 index 0000000000..53250c0269 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/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-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION new file mode 100644 index 0000000000..cd802a1ec4 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/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-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 0000000000..bc9085aec2 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,348 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector Stellar + * Can perform basic tasks on a Stellar ledger + * + * The version of the OpenAPI document: v2.0.0-alpha.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 + * @enum {string} + */ + +export const WatchBlocksV1 = { + Subscribe: 'org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Subscribe', + Next: 'org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Next', + Unsubscribe: 'org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Unsubscribe', + Error: 'org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Error', + Complete: 'org.hyperledger.cacti.api.async.stellar.WatchBlocksV1.Complete' +} as const; + +export type WatchBlocksV1 = typeof WatchBlocksV1[keyof typeof WatchBlocksV1]; + + +/** + * + * @export + * @interface WatchBlocksV1Progress + */ +export interface WatchBlocksV1Progress { + /** + * + * @type {{ [key: string]: any; }} + * @memberof WatchBlocksV1Progress + */ + 'blockHeader': { [key: string]: any; }; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @summary Deploys a smart contract to the Stellar ledger associated with the connector. + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractV1: async (body?: object, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/deploy-contract`; + // 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 .json file that contains the OpenAPI specification for the plugin. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getOpenApiSpecV1: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-open-api-spec`; + // 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 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-ledger-connector-stellar/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 Executes a transaction on a stellar ledger + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runTransactionV1: async (body?: object, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/run-transaction`; + // 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, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @summary Deploys a smart contract to the Stellar ledger associated with the connector. + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deployContractV1(body?: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deployContractV1(body, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Retrieves the .json file that contains the OpenAPI specification for the plugin. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getOpenApiSpecV1(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getOpenApiSpecV1(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 Executes a transaction on a stellar ledger + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async runTransactionV1(body?: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.runTransactionV1(body, 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 Deploys a smart contract to the Stellar ledger associated with the connector. + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractV1(body?: object, options?: any): AxiosPromise { + return localVarFp.deployContractV1(body, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Retrieves the .json file that contains the OpenAPI specification for the plugin. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getOpenApiSpecV1(options?: any): AxiosPromise { + return localVarFp.getOpenApiSpecV1(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 Executes a transaction on a stellar ledger + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runTransactionV1(body?: object, options?: any): AxiosPromise { + return localVarFp.runTransactionV1(body, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * + * @summary Deploys a smart contract to the Stellar ledger associated with the connector. + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public deployContractV1(body?: object, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).deployContractV1(body, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Retrieves the .json file that contains the OpenAPI specification for the plugin. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getOpenApiSpecV1(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getOpenApiSpecV1(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 Executes a transaction on a stellar ledger + * @param {object} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public runTransactionV1(body?: object, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).runTransactionV1(body, options).then((request) => request(this.axios, this.basePath)); + } +} + + diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/base.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/base.ts new file mode 100644 index 0000000000..a5466bb109 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/base.ts @@ -0,0 +1,72 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector Stellar + * Can perform basic tasks on a Stellar ledger + * + * The version of the OpenAPI document: v2.0.0-alpha.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-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/common.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/common.ts new file mode 100644 index 0000000000..eef217fde3 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector Stellar + * Can perform basic tasks on a Stellar ledger + * + * The version of the OpenAPI document: v2.0.0-alpha.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-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/configuration.ts new file mode 100644 index 0000000000..c5aafd4cd7 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/configuration.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector Stellar + * Can perform basic tasks on a Stellar ledger + * + * The version of the OpenAPI document: v2.0.0-alpha.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-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/index.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/index.ts new file mode 100644 index 0000000000..fb171dce81 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/generated/openapi/typescript-axios/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector Stellar + * Can perform basic tasks on a Stellar ledger + * + * The version of the OpenAPI document: v2.0.0-alpha.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-ledger-connector-stellar/src/main/typescript/index.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/index.ts new file mode 100755 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/index.web.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/index.web.ts new file mode 100755 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/plugin-factory-ledger-connector.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/plugin-factory-ledger-connector.ts new file mode 100644 index 0000000000..7f7698196f --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/plugin-factory-ledger-connector.ts @@ -0,0 +1,20 @@ +import { + IPluginFactoryOptions, + PluginFactory, +} from "@hyperledger/cactus-core-api"; +import { + IPluginLedgerConnectorStellarOptions, + PluginLedgerConnectorStellar, +} from "./plugin-ledger-connector-stellar"; + +export class PluginFactoryLedgerConnector extends PluginFactory< + PluginLedgerConnectorStellar, + IPluginLedgerConnectorStellarOptions, + IPluginFactoryOptions +> { + async create( + pluginOptions: IPluginLedgerConnectorStellarOptions, + ): Promise { + return new PluginLedgerConnectorStellar(pluginOptions); + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/plugin-ledger-connector-stellar.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/plugin-ledger-connector-stellar.ts new file mode 100644 index 0000000000..cfb5142bc6 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/plugin-ledger-connector-stellar.ts @@ -0,0 +1,183 @@ +import type { Server as SocketIoServer } from "socket.io"; +import type { Socket as SocketIoSocket } from "socket.io"; +import type { Express } from "express"; +import { InternalServerError } from "http-errors-enhanced-cjs"; + +import OAS from "../json/openapi.json"; + +import { + ConsensusAlgorithmFamily, + IPluginLedgerConnector, + IWebServiceEndpoint, + IPluginWebService, + ICactusPlugin, + ICactusPluginOptions, +} from "@hyperledger/cactus-core-api"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import { + Checks, + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { WatchBlocksV1 } from "./generated/openapi/typescript-axios"; + +import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter"; +import { WatchBlocksV1Endpoint } from "./web-services/watch-blocks-v1-endpoint"; +import { + GetOpenApiSpecV1Endpoint, + IGetOpenApiSpecV1EndpointOptions, +} from "./web-services/get-open-api-spec-v1-endpoint"; + +export const E_KEYCHAIN_NOT_FOUND = + "cacti.connector.stellar.keychain_not_found"; + +export interface IPluginLedgerConnectorStellarOptions + extends ICactusPluginOptions { + rpcApiHttpHost: string; + rpcApiWsHost: string; + pluginRegistry: PluginRegistry; + prometheusExporter?: PrometheusExporter; + logLevel?: LogLevelDesc; +} + +export class PluginLedgerConnectorStellar + implements + IPluginLedgerConnector, + ICactusPlugin, + IPluginWebService +{ + private readonly instanceId: string; + public prometheusExporter: PrometheusExporter; + private readonly log: Logger; + private readonly pluginRegistry: PluginRegistry; + + private endpoints: IWebServiceEndpoint[] | undefined; + + public static readonly CLASS_NAME = "PluginLedgerConnectorStellar"; + + public get className(): string { + return PluginLedgerConnectorStellar.CLASS_NAME; + } + + constructor(public readonly options: IPluginLedgerConnectorStellarOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.rpcApiHttpHost, `${fnTag} options.rpcApiHttpHost`); + Checks.truthy(options.rpcApiWsHost, `${fnTag} options.rpcApiWsHost`); + Checks.truthy(options.pluginRegistry, `${fnTag} options.pluginRegistry`); + Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + + this.instanceId = options.instanceId; + this.pluginRegistry = options.pluginRegistry; + this.prometheusExporter = + options.prometheusExporter || + new PrometheusExporter({ pollingIntervalInMin: 1 }); + Checks.truthy( + this.prometheusExporter, + `${fnTag} options.prometheusExporter`, + ); + + this.prometheusExporter.startMetricsCollection(); + } + + public async deployContract(): Promise { + throw new InternalServerError("Method not implemented."); + } + public async transact(): Promise { + throw new InternalServerError("Method not implemented."); + } + public async getConsensusAlgorithmFamily(): Promise { + throw new InternalServerError("Method not implemented."); + } + public async hasTransactionFinality(): Promise { + throw new InternalServerError("Method not implemented."); + } + + public getOpenApiSpec(): unknown { + return OAS; + } + + public getPrometheusExporter(): PrometheusExporter { + return this.prometheusExporter; + } + + public async getPrometheusExporterMetrics(): Promise { + const res: string = await this.prometheusExporter.getPrometheusMetrics(); + this.log.debug(`getPrometheusExporterMetrics() response: %o`, res); + return res; + } + + public getInstanceId(): string { + return this.instanceId; + } + + public async onPluginInit(): Promise { + this.log.info("ENTER onPluginInit();"); + this.log.info("EXIT onPluginInit();"); + } + + public async shutdown(): Promise { + this.log.info(`Shutting down ${this.className}...`); + } + + async registerWebServices( + app: Express, + wsApi: SocketIoServer, + ): Promise { + const { logLevel } = this.options; + const webServices = await this.getOrCreateWebServices(); + await Promise.all(webServices.map((ws) => ws.registerExpress(app))); + + wsApi.on("connection", (socket: SocketIoSocket) => { + this.log.debug(`New Socket connected. ID=${socket.id}`); + + socket.on(WatchBlocksV1.Subscribe, () => { + new WatchBlocksV1Endpoint({ socket, logLevel }).subscribe(); + }); + }); + return webServices; + } + + public async getOrCreateWebServices(): Promise { + if (Array.isArray(this.endpoints)) { + return this.endpoints; + } + + const endpoints: IWebServiceEndpoint[] = []; + + { + const oasPath = + OAS.paths[ + "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-open-api-spec" + ]; + + const operationId = oasPath.get.operationId; + const opts: IGetOpenApiSpecV1EndpointOptions = { + oas: OAS, + oasPath, + operationId, + path: oasPath.get["x-hyperledger-cacti"].http.path, + pluginRegistry: this.pluginRegistry, + verbLowerCase: oasPath.get["x-hyperledger-cacti"].http.verbLowerCase, + logLevel: this.options.logLevel, + }; + const endpoint = new GetOpenApiSpecV1Endpoint(opts); + endpoints.push(endpoint); + } + + this.endpoints = endpoints; + return endpoints; + } + + public getPackageName(): string { + return `@hyperledger/cacti-plugin-ledger-connector-stellar`; + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/data-fetcher.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/data-fetcher.ts new file mode 100644 index 0000000000..2392c7f762 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/data-fetcher.ts @@ -0,0 +1,12 @@ +import { Transactions } from "./response.type"; + +import { totalTxCount, K_CACTUS_STELLAR_TOTAL_TX_COUNT } from "./metrics"; + +export async function collectMetrics( + transactions: Transactions, +): Promise { + transactions.counter++; + totalTxCount + .labels(K_CACTUS_STELLAR_TOTAL_TX_COUNT) + .set(transactions.counter); +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/metrics.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/metrics.ts new file mode 100644 index 0000000000..dcf801d865 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/metrics.ts @@ -0,0 +1,10 @@ +import { Gauge } from "prom-client"; + +export const K_CACTUS_STELLAR_TOTAL_TX_COUNT = "cacti_stellar_total_tx_count"; + +export const totalTxCount = new Gauge({ + registers: [], + name: "cacti_stellar_total_tx_count", + help: "Total transactions executed", + labelNames: ["type"], +}); diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/prometheus-exporter.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/prometheus-exporter.ts new file mode 100644 index 0000000000..b145708eee --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/prometheus-exporter.ts @@ -0,0 +1,39 @@ +import promClient, { Registry } from "prom-client"; +import { Transactions } from "./response.type"; +import { collectMetrics } from "./data-fetcher"; +import { K_CACTUS_STELLAR_TOTAL_TX_COUNT } from "./metrics"; +import { totalTxCount } from "./metrics"; + +export interface IPrometheusExporterOptions { + pollingIntervalInMin?: number; +} + +export class PrometheusExporter { + public readonly metricsPollingIntervalInMin: number; + public readonly transactions: Transactions = { counter: 0 }; + public readonly registry: Registry; + + constructor( + public readonly prometheusExporterOptions: IPrometheusExporterOptions, + ) { + this.metricsPollingIntervalInMin = + prometheusExporterOptions.pollingIntervalInMin || 1; + this.registry = new Registry(); + } + + public addCurrentTransaction(): void { + collectMetrics(this.transactions); + } + + public async getPrometheusMetrics(): Promise { + const result = await this.registry.getSingleMetricAsString( + K_CACTUS_STELLAR_TOTAL_TX_COUNT, + ); + return result; + } + + public startMetricsCollection(): void { + this.registry.registerMetric(totalTxCount); + promClient.collectDefaultMetrics({ register: this.registry }); + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/response.type.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/response.type.ts new file mode 100644 index 0000000000..3f1bc7f491 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/prometheus-exporter/response.type.ts @@ -0,0 +1,3 @@ +export type Transactions = { + counter: number; +}; diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/public-api.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..b430869147 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/public-api.ts @@ -0,0 +1,24 @@ +// export { +// E_KEYCHAIN_NOT_FOUND, +// IPluginLedgerConnectorStellarOptions, +// PluginLedgerConnectorStellar, +// } from "./plugin-ledger-connector-stellar"; +// export { PluginFactoryLedgerConnector } from "./plugin-factory-ledger-connector"; + +// import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; +// import { PluginFactoryLedgerConnector } from "./plugin-factory-ledger-connector"; + +// export { +// StellarApiClient, +// StellarApiClientOptions, +// } from "./api-client/stellar-api-client"; + +// export * from "./generated/openapi/typescript-axios/api"; + +// export async function createPluginFactory( +// pluginFactoryOptions: IPluginFactoryOptions, +// ): Promise { +// return new PluginFactoryLedgerConnector(pluginFactoryOptions); +// } + +export {}; diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/web-services/get-open-api-spec-v1-endpoint.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/web-services/get-open-api-spec-v1-endpoint.ts new file mode 100644 index 0000000000..5e205aa192 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/web-services/get-open-api-spec-v1-endpoint.ts @@ -0,0 +1,39 @@ +import { + GetOpenApiSpecV1EndpointBase, + IGetOpenApiSpecV1EndpointBaseOptions, +} from "@hyperledger/cactus-core"; + +import { Checks, LogLevelDesc } from "@hyperledger/cactus-common"; +import { IWebServiceEndpoint } from "@hyperledger/cactus-core-api"; + +import OAS from "../../json/openapi.json"; + +export const OasPathGetOpenApiSpecV1 = + OAS.paths[ + "/api/v1/plugins/@hyperledger/cacti-plugin-ledger-connector-stellar/get-open-api-spec" + ]; + +export type OasPathTypeGetOpenApiSpecV1 = typeof OasPathGetOpenApiSpecV1; + +export interface IGetOpenApiSpecV1EndpointOptions + extends IGetOpenApiSpecV1EndpointBaseOptions< + typeof OAS, + OasPathTypeGetOpenApiSpecV1 + > { + readonly logLevel?: LogLevelDesc; +} + +export class GetOpenApiSpecV1Endpoint + extends GetOpenApiSpecV1EndpointBase + implements IWebServiceEndpoint +{ + public get className(): string { + return GetOpenApiSpecV1Endpoint.CLASS_NAME; + } + + constructor(public readonly options: IGetOpenApiSpecV1EndpointOptions) { + super(options); + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts new file mode 100644 index 0000000000..304a10f9f8 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts @@ -0,0 +1,43 @@ +import { Socket as SocketIoSocket } from "socket.io"; + +import { Logger, Checks } from "@hyperledger/cactus-common"; +import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common"; +import { WatchBlocksV1Progress } from "../generated/openapi/typescript-axios"; +import { WatchBlocksV1 } from "../generated/openapi/typescript-axios"; + +export interface IWatchBlocksV1EndpointOptions { + logLevel?: LogLevelDesc; + socket: SocketIoSocket; +} + +export class WatchBlocksV1Endpoint { + public static readonly CLASS_NAME = "WatchBlocksV1Endpoint"; + + private readonly log: Logger; + private readonly socket: SocketIoSocket< + Record void>, + Record void> + >; + + public get className(): string { + return WatchBlocksV1Endpoint.CLASS_NAME; + } + + constructor(public readonly options: IWatchBlocksV1EndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.socket, `${fnTag} arg options.socket`); + + this.socket = options.socket; + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public async subscribe(): Promise { + const { socket, log } = this; + log.debug(`${WatchBlocksV1.Subscribe} => ${socket.id}`); + log.error("FIXME - this is not yet implemented"); + } +} diff --git a/packages/cacti-plugin-ledger-connector-stellar/src/test/typescript/integration/api-surface.test.ts b/packages/cacti-plugin-ledger-connector-stellar/src/test/typescript/integration/api-surface.test.ts new file mode 100644 index 0000000000..34aba3a0ae --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/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-ledger-connector-stellar/src/test/typescript/unit/api-surface.test.ts b/packages/cacti-plugin-ledger-connector-stellar/src/test/typescript/unit/api-surface.test.ts new file mode 100644 index 0000000000..34aba3a0ae --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/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-ledger-connector-stellar/src/test/typescript/unit/get-open-api-spec-v1-connector-stellar.test.ts b/packages/cacti-plugin-ledger-connector-stellar/src/test/typescript/unit/get-open-api-spec-v1-connector-stellar.test.ts new file mode 100644 index 0000000000..16136b158a --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/src/test/typescript/unit/get-open-api-spec-v1-connector-stellar.test.ts @@ -0,0 +1,91 @@ +// FIXME - uncomment this once we have the public-api.ts exports uncommented. + +// import { +// IListenOptions, +// LogLevelDesc, +// LoggerProvider, +// Servers, +// } from "@hyperledger/cactus-common"; +// import { PluginRegistry } from "@hyperledger/cactus-core"; +// import { Constants, PluginImportType } from "@hyperledger/cactus-core-api"; +// import bodyParser from "body-parser"; +// import express from "express"; +// import http from "http"; +// import "jest-extended"; +// import { AddressInfo } from "net"; +// import { Server as SocketIoServer } from "socket.io"; +// import { v4 as uuidv4 } from "uuid"; +// import { +// StellarApiClient, +// StellarApiClientOptions, +// PluginFactoryLedgerConnector, +// PluginLedgerConnectorStellar, +// } from "../../../main/typescript/public-api"; + +// describe(__filename, () => { +// const logLevel: LogLevelDesc = "INFO"; + +// const log = LoggerProvider.getOrCreate({ +// label: __filename, +// level: logLevel, +// }); + +// const rpcApiHttpHost = "http://127.0.0.1:8000"; +// const rpcApiWsHost = "ws://127.0.0.1:9000"; + +// const expressApp = express(); +// expressApp.use(bodyParser.json({ limit: "250mb" })); +// const server = http.createServer(expressApp); +// let apiClient: StellarApiClient; + +// afterAll(async () => { +// await Servers.shutdown(server); +// }); + +// beforeAll(async () => { +// const factory = new PluginFactoryLedgerConnector({ +// pluginImportType: PluginImportType.Local, +// }); + +// const connector: PluginLedgerConnectorStellar = await factory.create({ +// rpcApiHttpHost, +// rpcApiWsHost, +// logLevel, +// instanceId: uuidv4(), +// pluginRegistry: new PluginRegistry({ plugins: [] }), +// }); + +// const wsApi = new SocketIoServer(server, { +// path: Constants.SocketIoConnectionPathV1, +// }); + +// await connector.registerWebServices(expressApp, wsApi); + +// const listenOptions: IListenOptions = { +// hostname: "127.0.0.1", +// port: 0, +// server, +// }; +// const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; +// const { address, port } = addressInfo; +// const apiHost = `http://${address}:${port}`; + +// const stellarApiClientOptions = new StellarApiClientOptions({ +// basePath: apiHost, +// }); +// apiClient = new StellarApiClient(stellarApiClientOptions); +// log.debug("Instantiated StellarApiClient OK"); +// }); + +// it("Returns a JSON document containing the Open API specification of the plugin.", async () => { +// const res1Promise = apiClient.getOpenApiSpecV1(); +// await expect(res1Promise).resolves.not.toThrow(); +// const res1 = await res1Promise; +// expect(res1.status).toEqual(200); +// expect(res1.data).toBeTruthy(); +// expect(res1.config).toBeTruthy(); +// expect(res1.config.url).toBeString(); +// log.debug("Fetched URL OK=%s", res1.config.url); +// expect(res1.data).toBeObject(); +// }); +// }); diff --git a/packages/cacti-plugin-ledger-connector-stellar/tsconfig.json b/packages/cacti-plugin-ledger-connector-stellar/tsconfig.json new file mode 100644 index 0000000000..0fc75b46d7 --- /dev/null +++ b/packages/cacti-plugin-ledger-connector-stellar/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib/", + "declarationDir": "dist/lib", + "resolveJsonModule": true, + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cacti-plugin-ledger-connector-stellar.tsbuildinfo" + }, + "include": [ + "./src", + "src/**/*.json" + ], + "references": [ + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-core/tsconfig.json" + }, + { + "path": "../cactus-core-api/tsconfig.json" + }, + { + "path": "../cactus-plugin-keychain-memory/tsconfig.json" + }, + { + "path": "../cactus-test-tooling/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 723f9335db..3bef46b01f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -64,6 +64,9 @@ { "path": "./packages/cactus-plugin-ledger-connector-corda/tsconfig.json" }, + { + "path": "./packages/cacti-plugin-ledger-connector-stellar/tsconfig.json" + }, { "path": "./packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 239d4b4a0a..14acbac5d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7350,6 +7350,36 @@ __metadata: 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" + dependencies: + "@hyperledger/cactus-common": "npm:2.0.0-alpha.2" + "@hyperledger/cactus-core": "npm:2.0.0-alpha.2" + "@hyperledger/cactus-core-api": "npm:2.0.0-alpha.2" + "@hyperledger/cactus-plugin-keychain-memory": "npm:2.0.0-alpha.2" + "@hyperledger/cactus-test-tooling": "npm:2.0.0-alpha.2" + "@types/body-parser": "npm:1.19.4" + "@types/express": "npm:4.17.19" + "@types/http-errors": "npm:2.0.4" + "@types/uuid": "npm:9.0.8" + axios: "npm:1.6.0" + body-parser: "npm:1.20.2" + express: "npm:4.19.2" + http-errors-enhanced-cjs: "npm:2.0.1" + joi: "npm:17.9.1" + npm-run-all2: "npm:6.1.2" + openapi-types: "npm:12.1.3" + prom-client: "npm:13.2.0" + run-time-error-cjs: "npm:1.4.0" + rxjs: "npm:7.8.1" + socket.io: "npm:4.5.4" + socket.io-client-fixed-types: "npm:4.5.4" + typescript-optional: "npm:2.0.1" + uuid: "npm:9.0.1" + languageName: unknown + linkType: soft + "@hyperledger/cacti-weaver-besu-cli@workspace:weaver/samples/besu/besu-cli": version: 0.0.0-use.local resolution: "@hyperledger/cacti-weaver-besu-cli@workspace:weaver/samples/besu/besu-cli"