From 5c4a646b0b8bcadb7375aa90c519aa4ff0ea4a45 Mon Sep 17 00:00:00 2001 From: Rafael Belchior Date: Mon, 8 Aug 2022 15:45:17 +0000 Subject: [PATCH] feat(connector-ubiquity): initial implementation This plugin defines interfaces for developers to use a wrapped version of the Ubiquity SDK. Ubiquity is a high performance, multi-chain API for accessing blockchain data. This API complements current connectors by allowing to connect to a public blockchains. Signed-off-by: Rafael Belchior --- .cspell.json | 3 + .github/workflows/ci.yml | 30 +++ .../.env.example | 1 + .../.gitignore | 1 + .../README.md | 43 +++ .../openapitools.json | 7 + .../package.json | 102 ++++++++ .../src/main/json/openapi.json | 64 +++++ .../transaction/get-transactions-endpoint.ts | 109 ++++++++ .../.openapi-generator-ignore | 27 ++ .../typescript-axios/.openapi-generator/FILES | 5 + .../.openapi-generator/VERSION | 1 + .../generated/openapi/typescript-axios/api.ts | 150 +++++++++++ .../openapi/typescript-axios/base.ts | 71 +++++ .../openapi/typescript-axios/common.ts | 138 ++++++++++ .../openapi/typescript-axios/configuration.ts | 101 ++++++++ .../openapi/typescript-axios/index.ts | 18 ++ .../main/typescript/helpers/api-parameters.ts | 19 ++ .../src/main/typescript/index.ts | 1 + .../src/main/typescript/index.web.ts | 1 + .../plugin-ledger-connector-ubiquity.ts | 244 ++++++++++++++++++ .../prometheus-exporter/data-fetcher.ts | 7 + .../typescript/prometheus-exporter/metrics.ts | 10 + .../prometheus-exporter.ts | 40 +++ .../prometheus-exporter/response.type.ts | 3 + .../src/main/typescript/public-api.ts | 5 + .../test/typescript/integration/init.test.ts | 51 ++++ .../integration/obtain-all-txs.test.ts | 82 ++++++ .../test/typescript/unit/api-surface.test.ts | 6 + .../tsconfig.json | 29 +++ tsconfig.json | 3 + 31 files changed, 1372 insertions(+) create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/.env.example create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/.gitignore create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/README.md create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/openapitools.json create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/package.json create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/json/openapi.json create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/endpoints/transaction/get-transactions-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/api.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/base.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/common.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/configuration.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/index.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/helpers/api-parameters.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.ts create mode 100755 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.web.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/plugin-ledger-connector-ubiquity.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/data-fetcher.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/metrics.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/prometheus-exporter.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/response.type.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/public-api.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/init.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/obtain-all-txs.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/unit/api-surface.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ubiquity/tsconfig.json diff --git a/.cspell.json b/.cspell.json index ea9bf98ec54..9a9619e8e29 100644 --- a/.cspell.json +++ b/.cspell.json @@ -21,6 +21,7 @@ "caio", "cccs", "ccid", + "celo", "cids", "commenceack", "configtx", @@ -122,6 +123,8 @@ "supervisord", "svcs", "sykesm", + "TEZOS", + "tezos", "thream", "tlsca", "tlscacerts", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93092c3e0ac..494ac634c78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -961,6 +961,36 @@ jobs: restore-keys: | ${{ runner.os }}-yarn- - run: ./tools/ci.sh + cactus-plugin-connector-ubiquity: + continue-on-error: false + env: + FULL_BUILD_DISABLED: true + JEST_TEST_PATTERN: packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts + JEST_TEST_RUNNER_DISABLED: false + TAPE_TEST_RUNNER_DISABLED: true + UBIQUITY_AUTH_TOKEN: ${{ secrets.UBIQUITY_AUTH_TOKEN }} + needs: build-dev + runs-on: ubuntu-20.04 + steps: + - name: Use Node.js v16.14.2 + uses: actions/setup-node@v2.1.2 + with: + node-version: v16.14.2 + - uses: actions/checkout@v2.3.4 + - id: yarn-cache-dir-path + name: Get yarn cache directory path + run: echo "::set-output name=dir::$(yarn cache dir)" + - id: yarn-cache + name: Restore Yarn Cache + uses: actions/cache@v3.0.4 + with: + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + restore-keys: | + ${{ runner.os }}-yarn- + - run: ./tools/ci.sh + env: + UBIQUITY_AUTH_TOKEN: ${{ secrets.UBIQUITY_AUTH_TOKEN }} cactus-test-api-client: continue-on-error: false env: diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/.env.example b/packages/cactus-plugin-ledger-connector-ubiquity/.env.example new file mode 100644 index 00000000000..0ea9cdedbc5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/.env.example @@ -0,0 +1 @@ +UBIQUITY_AUTH_TOKEN=xpto \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/.gitignore b/packages/cactus-plugin-ledger-connector-ubiquity/.gitignore new file mode 100644 index 00000000000..4c49bd78f1d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/.gitignore @@ -0,0 +1 @@ +.env diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/README.md b/packages/cactus-plugin-ledger-connector-ubiquity/README.md new file mode 100644 index 00000000000..2f61f6c9f0e --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/README.md @@ -0,0 +1,43 @@ +# `@hyperledger/cactus-plugin-ledger-connector-ubiquity +This plugin defines interfaces for developers to use a wrapped version of the Ubiquity SDK. Ubiquity is a high performance, multi-chain API for accessing blockchain data, i.e., provides one API to access multiple protocols: https://ubiquity.docs.blockdaemon.com/swagger-ui + +This API complements Cactus current connector offering by allowing to connect seamlessly to a multitude of public blockchains. Although it can be considered a ledger connector, for now + +## Supported Functionality +-Read from smart contracts and addresses from 10+ different blockchains. + +## Usage +TBD + +## Installation + +**npm** + +```sh +npm install @hyperledger/cactus-plugin-ledger-connector-ubiquity +``` + +**yarn** + +```sh +yarn add @hyperledger/cactus-plugin-ledger-connector-ubiquity +``` + +Rename .env.example to .env and poopulate the environment variables. Alternatively, setup the AUTH_TOKEN environment variable (will be used to set up the auth token for the Ubiquity client). + +### Using as a Library +TBD + +## TODO +- Implement IPluginLedgerConnectorInterface with perhaps State pattern +- Containerize the plugin +- Add unit and integration tests +- Support full historical data across all Ubiquity supported protocols. +- Deploy public blockchain nodes on-the-go + +## License + +This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file. + +## Acknowledgments +The development of this plugin is supported by Blockdaemon \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/openapitools.json b/packages/cactus-plugin-ledger-connector-ubiquity/openapitools.json new file mode 100644 index 00000000000..54d00804a3d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.0.1" + } +} diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/package.json b/packages/cactus-plugin-ledger-connector-ubiquity/package.json new file mode 100644 index 00000000000..98fe30ac682 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/package.json @@ -0,0 +1,102 @@ +{ + "name": "@hyperledger/cactus-plugin-ledger-connector-ubiquity", + "version": "1.1.2", + "description": "Allows Cacti nodes to connect to a set of public blockchains.", + "keywords": [ + "Hyperledger", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology", + "Ubiquity" + ], + "homepage": "https://github.com/hyperledger/cactus#readme", + "bugs": { + "url": "https://github.com/hyperledger/cactus/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cactus.git" + }, + "license": "Apache-2.0", + "author": { + "name": "Hyperledger Cactus Contributors", + "email": "cactus@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cactus" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Rafael Belchior", + "email": "rbelchior@blockdaemon.com", + "url": "https://blockdaemon.com" + } + ], + "main": "dist/lib/main/typescript/index.js", + "module": "dist/lib/main/typescript/index.js", + "browser": "dist/cactus-plugin-ledger-connector-ubiquity.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": "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", + "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", + "tsc": "tsc --project ./tsconfig.json" + }, + "dependencies": { + "@hyperledger/cactus-core": "1.1.2", + "@hyperledger/cactus-core-api": "1.1.2", + "@ubiquity/ubiquity-ts-client-modified": "https://github.com/RafaelAPB/ubiquity-ts-client-mirror.git", + "typescript-optional": "2.0.1", + "dotenv": "16.0.1" + }, + "devDependencies": { + "@types/express": "4.17.8", + "@hyperledger/cactus-test-tooling": "1.1.2", + "@hyperledger/cactus-common": "1.1.2" + + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "publishConfig": { + "access": "public" + }, + "browserMinified": "dist/cactus-plugin-ledger-connector-ubiquity.web.umd.min.js", + "mainMinified": "dist/cactus-plugin-ledger-connector-ubiquity.node.umd.min.js", + "watch": { + "codegen:openapi": { + "patterns": [ + "./src/main/json/openapi.json" + ] + }, + "tsc": { + "patterns": [ + "src/", + "src/*/json/**/openapi*" + ], + "ignore": [ + "src/**/generated/*" + ], + "extensions": [ + "ts", + "json" + ], + "quiet": true, + "verbose": false, + "runOnChangeOnly": true + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/json/openapi.json new file mode 100644 index 00000000000..dbccabe28d5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/json/openapi.json @@ -0,0 +1,64 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cactus Plugin - Ubiquity", + "description": "Ubiquity wrapper for Hyperledger Cactus", + "version": "0.0.1", + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "components": { + "schemas": { + "GetTransactionsByAddressEndpoint": { + "type": "object", + "description": "Gets transactions that an address was involved with, from newest to oldest. This call uses pagination. Source: https://ubiquity.docs.blockdaemon.com/swagger-ui/#/Accounts/GetTxsByAddress", + "properties": { + "protocol": { + "type": "string" + }, + "network": { + "type": "string" + }, + "address": { + "type": "string" + } + }, + "required": [ + "protocol", + "network", + "address" + ] + } + } + }, + "paths": { + "/api/v1/@hyperledger/cactus-plugin-ledger-connector-ubiquity/GetTransactionByAddress": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/@hyperledger/cactus-plugin-ledger-connector-ubiquity/GetTransactionByAddress" + } + }, + "operationId": "GetTransactionByAddressV1", + "description": "", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTransactionsByAddressEndpoint" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/endpoints/transaction/get-transactions-endpoint.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/endpoints/transaction/get-transactions-endpoint.ts new file mode 100644 index 00000000000..bd532039600 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/endpoints/transaction/get-transactions-endpoint.ts @@ -0,0 +1,109 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorUbiquity } from "../../plugin-ledger-connector-ubiquity"; + +import OAS from "../../../json/openapi.json"; + +export interface IGetTransactionsByAddressEndpointOptions { + logLevel?: LogLevelDesc; + ubiquity: PluginLedgerConnectorUbiquity; +} + +export class GetTransactionsByAddressEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "GetTransactionsByAddressEndpointOptions"; + + private readonly log: Logger; + + public get className(): string { + return GetTransactionsByAddressEndpoint.CLASS_NAME; + } + + constructor( + public readonly options: IGetTransactionsByAddressEndpointOptions, + ) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.ubiquity, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getPath(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-ledger-connector-ubiquity/GetTransactionByAddress" + ]; + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-ledger-connector-ubiquity/GetTransactionByAddress" + ]; + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-ledger-connector-ubiquity/GetTransactionByAddress" + ].post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + await this.options.ubiquity.getTxsByAddress( + req.body.protocol, + req.body.network, + req.body.address, + ); + res.status(200).json("OK"); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore new file mode 100644 index 00000000000..ecd97ff37fe --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore @@ -0,0 +1,27 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +git_push.sh +.npmignore +.gitignore \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES new file mode 100644 index 00000000000..53250c02696 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/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/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION new file mode 100644 index 00000000000..6d54bbd7751 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.0.1 \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 00000000000..b487c2e5795 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Ubiquity + * Ubiquity wrapper for Hyperledger Cactus + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { Configuration } from './configuration'; +import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } 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'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base'; + +/** + * Gets transactions that an address was involved with, from newest to oldest. This call uses pagination. Source: https://ubiquity.docs.blockdaemon.com/swagger-ui/#/Accounts/GetTxsByAddress + * @export + * @interface GetTransactionsByAddressEndpoint + */ +export interface GetTransactionsByAddressEndpoint { + /** + * + * @type {string} + * @memberof GetTransactionsByAddressEndpoint + */ + 'protocol': string; + /** + * + * @type {string} + * @memberof GetTransactionsByAddressEndpoint + */ + 'network': string; + /** + * + * @type {string} + * @memberof GetTransactionsByAddressEndpoint + */ + 'address': string; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {GetTransactionsByAddressEndpoint} [getTransactionsByAddressEndpoint] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTransactionByAddressV1: async (getTransactionsByAddressEndpoint?: GetTransactionsByAddressEndpoint, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-ledger-connector-ubiquity/GetTransactionByAddress`; + // 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(getTransactionsByAddressEndpoint, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @param {GetTransactionsByAddressEndpoint} [getTransactionsByAddressEndpoint] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTransactionByAddressV1(getTransactionsByAddressEndpoint?: GetTransactionsByAddressEndpoint, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTransactionByAddressV1(getTransactionsByAddressEndpoint, 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 { + /** + * + * @param {GetTransactionsByAddressEndpoint} [getTransactionsByAddressEndpoint] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTransactionByAddressV1(getTransactionsByAddressEndpoint?: GetTransactionsByAddressEndpoint, options?: any): AxiosPromise { + return localVarFp.getTransactionByAddressV1(getTransactionsByAddressEndpoint, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * + * @param {GetTransactionsByAddressEndpoint} [getTransactionsByAddressEndpoint] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getTransactionByAddressV1(getTransactionsByAddressEndpoint?: GetTransactionsByAddressEndpoint, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getTransactionByAddressV1(getTransactionsByAddressEndpoint, options).then((request) => request(this.axios, this.basePath)); + } +} + + diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/base.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/base.ts new file mode 100644 index 00000000000..3a29f50c207 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/base.ts @@ -0,0 +1,71 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Ubiquity + * Ubiquity wrapper for Hyperledger Cactus + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { Configuration } from "./configuration"; +// Some imports not used depending on template conditions +// @ts-ignore +import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } 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 { + name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/common.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/common.ts new file mode 100644 index 00000000000..319f2b5d774 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/common.ts @@ -0,0 +1,138 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Ubiquity + * Ubiquity wrapper for Hyperledger Cactus + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { Configuration } from "./configuration"; +import { RequiredError, RequestArgs } from "./base"; +import { AxiosInstance, AxiosResponse } from 'axios'; + +/** + * + * @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; + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + for (const object of objects) { + for (const key in object) { + if (Array.isArray(object[key])) { + searchParams.delete(key); + for (const item of object[key]) { + searchParams.append(key, item); + } + } else { + searchParams.set(key, object[key]); + } + } + } + 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/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/configuration.ts new file mode 100644 index 00000000000..ae974ed03ac --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/configuration.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Ubiquity + * Ubiquity wrapper for Hyperledger Cactus + * + * The version of the OpenAPI document: 0.0.1 + * + * + * 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/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/index.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/index.ts new file mode 100644 index 00000000000..eb5e424f785 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/generated/openapi/typescript-axios/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Ubiquity + * Ubiquity wrapper for Hyperledger Cactus + * + * The version of the OpenAPI document: 0.0.1 + * + * + * 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/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/helpers/api-parameters.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/helpers/api-parameters.ts new file mode 100644 index 00000000000..d77be434ccc --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/helpers/api-parameters.ts @@ -0,0 +1,19 @@ +export type PROTOCOL_TYPE = + | "algorand" + | "bitcoin" + | "bitcoincash" + | "celo" + | "diem" + | "dogecoin" + | "ethereum" + | "litecoin" + | "near" + | "oasis" + | "polkadot" + | "xrp" + | "solana" + | "stellar" + | "terra" + | "tezos"; + +export type NETWORK_TYPE = "mainnet" | "testnet" | "ropsten"; diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.ts new file mode 100644 index 00000000000..87cb558397c --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.web.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.web.ts new file mode 100755 index 00000000000..cb0ff5c3b54 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/plugin-ledger-connector-ubiquity.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/plugin-ledger-connector-ubiquity.ts new file mode 100644 index 00000000000..3eee9bf5526 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/plugin-ledger-connector-ubiquity.ts @@ -0,0 +1,244 @@ +import type { Express } from "express"; +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, +} from "@hyperledger/cactus-common"; +import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter"; +import { v4 as uuidV4 } from "uuid"; +import type { AxiosResponse } from "axios"; +import fetch from "node-fetch"; +import dotenv from "dotenv"; +import path from "path"; + +import type { Response } from "node-fetch"; +import OAS from "../json/openapi.json"; + +import { NETWORK_TYPE, PROTOCOL_TYPE } from "./helpers/api-parameters"; +import { + ConsensusAlgorithmFamily, + ICactusPlugin, + IPluginLedgerConnector, + IPluginWebService, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { GetTransactionsByAddressEndpoint } from "./endpoints/transaction/get-transactions-endpoint"; +import { + UbiquityClient, + TxPage, + Balance, + Tx, +} from "@ubiquity/ubiquity-ts-client-modified"; + +export interface IPluginLedgerConnectorUbiquity { + logLevel?: LogLevelDesc; + authToken: string; + instanceId?: string; + basePath?: string; + prometheusExporter?: PrometheusExporter; +} + +/** + * Note: The plugin connector interface methods are not provided by the initial + * implementation, and because of that the generic type parameters are set to + * `never` + */ + +export class PluginLedgerConnectorUbiquity + implements + IPluginLedgerConnector, + ICactusPlugin, + IPluginWebService { + public static readonly CLASS_NAME = "PluginLedgerConnectorUbiquity"; + private readonly instanceId: string; + private readonly log: Logger; + private client: UbiquityClient; + private authToken: string; + public prometheusExporter: PrometheusExporter; + private endpoints: IWebServiceEndpoint[] | undefined; + + public get className(): string { + return PluginLedgerConnectorUbiquity.CLASS_NAME; + } + + constructor(public readonly opts: IPluginLedgerConnectorUbiquity) { + const fnTag = `${this.className}#constructor()`; + const envPath = path.join(__dirname, "../.env"); + dotenv.config({ path: envPath }); + expect(process.env.UBIQUITY_AUTH_TOKEN).toBeTruthy(); + const authToken = process.env.UBIQUITY_AUTH_TOKEN; + if (!authToken) { + throw new Error("Auth token not defined"); + } + this.authToken = authToken; + Checks.truthy(opts, `${fnTag} arg options`); + Checks.truthy(opts.authToken, `${fnTag} options.authToken`); + Checks.truthy(opts.logLevel, `${fnTag} options.logLevel`); + Checks.truthy(opts.instanceId, `${fnTag} options.instanceId`); + this.log = LoggerProvider.getOrCreate({ + label: this.className, + level: opts.logLevel, + }); + + this.instanceId = opts.instanceId || uuidV4(); + this.log.debug("Initialized"); + this.client = new UbiquityClient( + opts.authToken, + opts.basePath || "https://ubiquity.api.blockdaemon.com/v1", + ); + this.prometheusExporter = + opts.prometheusExporter || + new PrometheusExporter({ pollingIntervalInMin: 1 }); + + this.prometheusExporter.startMetricsCollection(); + } + deployContract(options?: string): Promise { + throw new Error(`Method not implemented. ${options}`); + } + transact(options?: string): Promise { + throw new Error(`Method not implemented. ${options}`); + } + getConsensusAlgorithmFamily(): Promise { + throw new Error("Method not implemented."); + } + + async hasTransactionFinality(): Promise { + return false; + } + + // Documentation: https://ubiquity.docs.blockdaemon.com/swagger-ui/#/Accounts/GetTxsByAddress + public async getTxsByAddress( + protocol: PROTOCOL_TYPE, + network: NETWORK_TYPE, + address: string, + continuation?: string, + limit?: number, + ): Promise { + const fnTag = `${this.className}:GetTxsByAddress`; + this.log.debug("enter ", fnTag); + const response = await this.client.accountsApi.getTxsByAddress( + protocol, + network, + address, + "asc", + continuation, + limit, + ); + this.prometheusExporter.addMethodCall(); + return response.data; + } + + // Documentation: https://ubiquity.docs.blockdaemon.com/swagger-ui/#/Accounts/GetBalancesByAddresses + public getBalancesByAddresses( + protocol: PROTOCOL_TYPE, + network: NETWORK_TYPE, + address: string, + ): Promise> { + const fnTag = `${this.className}:GetBalancesByAddresses`; + this.log.debug("enter ", fnTag); + const accounts = this.client.accountsApi.getListOfBalancesByAddress( + protocol, + network, + address, + ); + this.prometheusExporter.addMethodCall(); + return accounts; + } + + // Documentation: https://ubiquity.docs.blockdaemon.com/swagger-ui/#/Transactions/V2GetTx + public getTx( + protocol: PROTOCOL_TYPE, + network: NETWORK_TYPE, + id: string, + ): Promise> { + const fnTag = `${this.className}:GetTx`; + this.log.debug("enter ", fnTag); + + const transactions = this.client.transactionsApi.getTx( + protocol, + network, + id, + ); + this.prometheusExporter.addMethodCall(); + return transactions; + } + + // Documentation: https://ethereum.org/en/developers/docs/apis/json-rpc/ + public async nativeGetTx( + protocol: PROTOCOL_TYPE, + network: NETWORK_TYPE, + ): Promise { + const fnTag = `${this.className}:NativeGetTx`; + this.log.debug("enter ", fnTag); + const ethereumUrl = `https://svc.blockdaemon.com/${protocol}/${network}/native`; + + const body = ""; + const resp = fetch(ethereumUrl, { + method: "POST", + body: JSON.stringify(body), + headers: { + Authorization: this.authToken, + "Content-Type": "application/json", + }, + }); + + const data = await (await resp).text(); + this.log.debug(data); + this.prometheusExporter.addMethodCall(); + return resp; + } + + public getInstanceId(): string { + return this.instanceId; + } + + public getPackageName(): string { + return `@hyperledger/cactus-plugin-ledger-connector-ubiquity`; + } + + public async shutdown(): Promise { + return; + } + + 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 async onPluginInit(): Promise { + return; + } + + async registerWebServices(app: Express): Promise { + const webServices = await this.getOrCreateWebServices(); + await Promise.all(webServices.map((ws) => ws.registerExpress(app))); + return webServices; + } + + public async getOrCreateWebServices(): Promise { + if (Array.isArray(this.endpoints)) { + return this.endpoints; + } + + // Server endpoints + const getTransactionsByAddressEndpoint = new GetTransactionsByAddressEndpoint( + { + logLevel: "DEBUG", + ubiquity: this, + }, + ); + + this.endpoints = [getTransactionsByAddressEndpoint]; + return this.endpoints; + } + + public getOpenApiSpec(): unknown { + return OAS; + } +} diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/data-fetcher.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/data-fetcher.ts new file mode 100644 index 00000000000..47cca225a10 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/data-fetcher.ts @@ -0,0 +1,7 @@ +import { Calls } from "./response.type"; + +import { totalTxCount, K_CACTUS_UBIQUITY_TOTAL_METHOD_CALLS } from "./metrics"; + +export async function collectMetrics(calls: Calls): Promise { + totalTxCount.labels(K_CACTUS_UBIQUITY_TOTAL_METHOD_CALLS).set(calls.counter); +} diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/metrics.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/metrics.ts new file mode 100644 index 00000000000..992cb30fa6d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/metrics.ts @@ -0,0 +1,10 @@ +import { Gauge } from "prom-client"; + +export const K_CACTUS_UBIQUITY_TOTAL_METHOD_CALLS = + "ubiquity_total_method_calls"; + +export const totalTxCount = new Gauge({ + name: K_CACTUS_UBIQUITY_TOTAL_METHOD_CALLS, + help: "Total method calls", + labelNames: ["type"], +}); diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/prometheus-exporter.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/prometheus-exporter.ts new file mode 100644 index 00000000000..f32247cb1b3 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/prometheus-exporter.ts @@ -0,0 +1,40 @@ +import promClient, { Registry } from "prom-client"; +import { Calls } from "./response.type"; +import { totalTxCount, K_CACTUS_UBIQUITY_TOTAL_METHOD_CALLS } from "./metrics"; + +export interface IPrometheusExporterOptions { + pollingIntervalInMin?: number; +} + +export class PrometheusExporter { + public readonly metricsPollingIntervalInMin: number; + public readonly calls: Calls = { counter: 0 }; + public readonly registry: Registry; + + constructor( + public readonly prometheusExporterOptions: IPrometheusExporterOptions, + ) { + this.metricsPollingIntervalInMin = + prometheusExporterOptions.pollingIntervalInMin || 1; + this.registry = new Registry(); + } + + public addMethodCall(): void { + this.calls.counter++; + totalTxCount + .labels(K_CACTUS_UBIQUITY_TOTAL_METHOD_CALLS) + .set(this.calls.counter); + } + + public async getPrometheusMetrics(): Promise { + const result = await this.registry.getSingleMetricAsString( + K_CACTUS_UBIQUITY_TOTAL_METHOD_CALLS, + ); + return result; + } + + public startMetricsCollection(): void { + this.registry.registerMetric(totalTxCount); + promClient.collectDefaultMetrics({ register: this.registry }); + } +} diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/response.type.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/response.type.ts new file mode 100644 index 00000000000..30765197791 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/prometheus-exporter/response.type.ts @@ -0,0 +1,3 @@ +export type Calls = { + counter: number; +}; diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/public-api.ts new file mode 100644 index 00000000000..e66304cc20c --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/main/typescript/public-api.ts @@ -0,0 +1,5 @@ +export { + PluginLedgerConnectorUbiquity, + IPluginLedgerConnectorUbiquity, +} from "./plugin-ledger-connector-ubiquity"; +export * from "./generated/openapi/typescript-axios/index"; diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/init.test.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/init.test.ts new file mode 100644 index 00000000000..658573acbc0 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/init.test.ts @@ -0,0 +1,51 @@ +import dotenv from "dotenv"; +import path from "path"; +const envPath = path.join(__dirname, "../../../../.env"); +dotenv.config({ path: envPath }); + +import "jest-extended"; +import { v4 as uuidv4 } from "uuid"; +import { + Containers, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc, Checks } from "@hyperledger/cactus-common"; + +import { + PluginLedgerConnectorUbiquity, + IPluginLedgerConnectorUbiquity, +} from "../../../main/typescript/public-api"; + +const testCase = "initialize ubiquity plugin"; +const logLevel: LogLevelDesc = "TRACE"; +expect(process.env.UBIQUITY_AUTH_TOKEN).toBeTruthy(); +const authToken = process.env.UBIQUITY_AUTH_TOKEN; +if (!authToken) { + throw new Error("Auth token not defined"); +} + +describe(testCase, () => { + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + + afterAll(async () => { + await Containers.logDiagnostics({ logLevel }); + }); + + afterAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + + test(testCase, async () => { + const options: IPluginLedgerConnectorUbiquity = { + logLevel: logLevel, + authToken: authToken, + instanceId: uuidv4(), + }; + const api = new PluginLedgerConnectorUbiquity(options); + Checks.truthy(api); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/obtain-all-txs.test.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/obtain-all-txs.test.ts new file mode 100644 index 00000000000..c751088624d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/integration/obtain-all-txs.test.ts @@ -0,0 +1,82 @@ +import "jest-extended"; +import dotenv from "dotenv"; +import path from "path"; +const envPath = path.join(__dirname, "../../../../.env"); +dotenv.config({ path: envPath }); +import { v4 as uuidv4 } from "uuid"; +import { + Containers, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc, Checks } from "@hyperledger/cactus-common"; + +import { + PluginLedgerConnectorUbiquity, + IPluginLedgerConnectorUbiquity, +} from "../../../main/typescript/public-api"; + +const testCase = "obtain all txs"; +const logLevel: LogLevelDesc = "TRACE"; +expect(process.env.UBIQUITY_AUTH_TOKEN).toBeTruthy(); +const authToken = process.env.UBIQUITY_AUTH_TOKEN; +if (!authToken) { + throw new Error( + "Auth token not defined. Please generate a token at https://app.blockdaemon.com/", + ); +} + +describe(testCase, () => { + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + + afterAll(async () => { + await Containers.logDiagnostics({ logLevel }); + }); + + afterAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + + const options: IPluginLedgerConnectorUbiquity = { + logLevel: logLevel, + authToken: authToken, + instanceId: uuidv4(), + basePath: "https://svc.blockdaemon.com/universal/v1/", + }; + + const api = new PluginLedgerConnectorUbiquity(options); + Checks.truthy(api); + + // Rainbow bridge ethereum custodian contract + const address = "0x6BFaD42cFC4EfC96f529D786D643Ff4A8B89FA52"; + + test("GetTxsByAddress", async () => { + const transactions = await api.getTxsByAddress( + "ethereum", + "mainnet", + address, + ); + Checks.truthy(transactions); + Checks.truthy(transactions.total === 25); + + const tx = await api.getTx( + "ethereum", + "mainnet", + "0x78df45ed70b64e08105dea46f762faa5680342a40259eb29006f1ab6ca38f05a", + ); + Checks.truthy(tx); + Checks.truthy(tx.data); + }); + + test("GetBalancesByAddresses", async () => { + const response = await api.getBalancesByAddresses( + "ethereum", + "mainnet", + address, + ); + Checks.truthy(response); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/unit/api-surface.test.ts b/packages/cactus-plugin-ledger-connector-ubiquity/src/test/typescript/unit/api-surface.test.ts new file mode 100644 index 00000000000..34aba3a0aea --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/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/cactus-plugin-ledger-connector-ubiquity/tsconfig.json b/packages/cactus-plugin-ledger-connector-ubiquity/tsconfig.json new file mode 100644 index 00000000000..3bed67f1e55 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ubiquity/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib/", + "declarationDir": "dist/lib", + "resolveJsonModule": true, + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-ledger-connector-ubiquity.tsbuildinfo" + }, + "include": [ + "./src", + "src/**/*.json" + ], + "references": [ + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-test-tooling/tsconfig.json" + }, + { + "path": "../cactus-core-api/tsconfig.json" + }, + { + "path": "../cactus-core/tsconfig.json" + } + ] + } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 0cc496a24af..3eb5852e7c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -132,6 +132,9 @@ }, { "path": "./extensions/cactus-plugin-htlc-coordinator-besu/tsconfig.json" + }, + { + "path": "./packages/cactus-plugin-ledger-connector-ubiquity/tsconfig.json" } ], "compilerOptions": {