diff --git a/backend/src/apis/dltConnector/DltConnectorClient.ts b/backend/src/apis/dltConnector/DltConnectorClient.ts index 2533e7509..4fef742ed 100644 --- a/backend/src/apis/dltConnector/DltConnectorClient.ts +++ b/backend/src/apis/dltConnector/DltConnectorClient.ts @@ -1,11 +1,11 @@ -import { GraphQLClient, gql } from 'graphql-request' - import { CONFIG } from '@/config' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { getLogger } from 'log4js' import { TransactionDraft } from './model/TransactionDraft' import { TransactionResult } from './model/TransactionResult' +import { GraphQLClient } from 'graphql-request' +import { gql } from 'graphql-request' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.dltConnector`) diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts index ac77f4195..5e756fb7d 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts @@ -1,10 +1,10 @@ // eslint-disable-next-line import/no-extraneous-dependencies -import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from '@dbTools/typeorm' -import { DltTransaction } from '@entity/DltTransaction' +import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from 'typeorm' +import { DltTransaction } from 'database' import { TransactionDraft } from '@dltConnector/model/TransactionDraft' - -import { backendLogger as logger } from '@/server/logger' +import { getLogger, Logger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' export abstract class AbstractTransactionToDltRole { protected self: T | null @@ -14,6 +14,9 @@ export abstract class AbstractTransactionToDltRole { public abstract getTimestamp(): number public abstract convertToGraphqlInput(): TransactionDraft + + public constructor(protected logger: Logger) {} + public getEntity(): T | null { return this.self } @@ -25,11 +28,11 @@ export abstract class AbstractTransactionToDltRole { this.setJoinIdAndType(dltTransaction) await DltTransaction.save(dltTransaction) if (dltTransaction.error) { - logger.error( + this.logger.error( `Store dltTransaction with error: id=${dltTransaction.id}, error=${dltTransaction.error}`, ) } else { - logger.info( + this.logger.info( `Store dltTransaction: messageId=${dltTransaction.messageId}, id=${dltTransaction.id}`, ) } diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts index b761b71c6..b8eaa633a 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts @@ -1,5 +1,4 @@ -import { DltTransaction } from '@entity/DltTransaction' -import { TransactionLink } from '@entity/TransactionLink' +import { DltTransaction, TransactionLink } from 'database' import { DltTransactionType } from '@dltConnector/enum/DltTransactionType' import { TransactionType } from '@dltConnector/enum/TransactionType' diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionToDlt.role.ts index de96d38cc..9e078e874 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionToDlt.role.ts @@ -1,5 +1,4 @@ -import { DltTransaction } from '@entity/DltTransaction' -import { Transaction } from '@entity/Transaction' +import { DltTransaction, Transaction } from 'database' import { DltTransactionType } from '@dltConnector/enum/DltTransactionType' import { TransactionType } from '@dltConnector/enum/TransactionType' diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/UserToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/UserToDlt.role.ts index 6319880fe..94b95ccdd 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/UserToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/UserToDlt.role.ts @@ -1,5 +1,4 @@ -import { DltTransaction } from '@entity/DltTransaction' -import { User } from '@entity/User' +import { DltTransaction, User } from 'database' import { AccountType } from '@dltConnector/enum/AccountType' import { DltTransactionType } from '@dltConnector/enum/DltTransactionType' diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts index febb3b5ae..b03bd5300 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts @@ -1,16 +1,15 @@ -import { Transaction } from '@entity/Transaction' -import { TransactionLink } from '@entity/TransactionLink' -import { User } from '@entity/User' +import { Transaction, TransactionLink, User } from 'database' import { DltConnectorClient } from '@/apis/dltConnector/DltConnectorClient' import { TransactionResult } from '@/apis/dltConnector/model/TransactionResult' -import { backendLogger as logger } from '@/server/logger' import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role' import { TransactionLinkDeleteToDltRole } from './TransactionLinkDeleteToDlt.role' import { TransactionLinkToDltRole } from './TransactionLinkToDlt.role' import { TransactionToDltRole } from './TransactionToDlt.role' import { UserToDltRole } from './UserToDlt.role' +import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' /** * @DCI-Context @@ -20,12 +19,13 @@ export async function transactionToDlt(dltConnector: DltConnectorClient): Promis async function findNextPendingTransaction(): Promise< AbstractTransactionToDltRole > { + const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}/apis/dltConnector/interaction/transactionToDlt`) // collect each oldest not sended entity from db and choose oldest const results = await Promise.all([ - new TransactionToDltRole().initWithLast(), - new UserToDltRole().initWithLast(), - new TransactionLinkToDltRole().initWithLast(), - new TransactionLinkDeleteToDltRole().initWithLast(), + new TransactionToDltRole(logger).initWithLast(), + new UserToDltRole(logger).initWithLast(), + new TransactionLinkToDltRole(logger).initWithLast(), + new TransactionLinkDeleteToDltRole(logger).initWithLast(), ]) // sort array to get oldest at first place diff --git a/backend/src/apis/dltConnector/sendTransactionsToDltConnector.ts b/backend/src/apis/dltConnector/sendTransactionsToDltConnector.ts index 73dd6cbab..3b0bfbc27 100644 --- a/backend/src/apis/dltConnector/sendTransactionsToDltConnector.ts +++ b/backend/src/apis/dltConnector/sendTransactionsToDltConnector.ts @@ -1,7 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { CONFIG } from '@/config' -import { backendLogger as logger } from '@/server/logger' -import { TypeORMError } from '@dbTools/typeorm' +import { TypeORMError } from 'typeorm' // eslint-disable-next-line import/named, n/no-extraneous-import import { FetchError } from 'node-fetch' @@ -14,6 +13,10 @@ import { } from '@/util/InterruptiveSleepManager' import { transactionToDlt } from './interaction/transactionToDlt/transactionToDlt.context' +import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' + +const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}/apis/dltConnector/sendTransactionsToDltConnector`) let isLoopRunning = true @@ -25,11 +28,11 @@ export async function sendTransactionsToDltConnector(): Promise { const dltConnector = DltConnectorClient.getInstance() if (!dltConnector) { - logger.info('Sending to DltConnector currently not configured...') + logger.info('currently not configured...') isLoopRunning = false return } - logger.info('Starting sendTransactionsToDltConnector task') + logger.info('task started') // define outside of loop for reuse and reducing gb collection // const queries = getFindNextPendingTransactionQueries() diff --git a/backend/src/auth/DLT_CONNECTOR_RIGHTS.ts b/backend/src/auth/DLT_CONNECTOR_RIGHTS.ts index 399b7c2d4..e7cdc3806 100644 --- a/backend/src/auth/DLT_CONNECTOR_RIGHTS.ts +++ b/backend/src/auth/DLT_CONNECTOR_RIGHTS.ts @@ -1,3 +1,3 @@ import { RIGHTS } from './RIGHTS' -export const DLT_CONNECTOR_RIGHTS = [RIGHTS.COMMUNITY_BY_IDENTIFIER, RIGHTS.HOME_COMMUNITY] +export const DLT_CONNECTOR_RIGHTS = [RIGHTS.COMMUNITY_BY_IDENTIFIER, RIGHTS.HOME_COMMUNITY, RIGHTS.COMMUNITY_UPDATE] diff --git a/dlt-connector/bun.lock b/dlt-connector/bun.lock index b300ad89a..05852adda 100644 --- a/dlt-connector/bun.lock +++ b/dlt-connector/bun.lock @@ -25,13 +25,11 @@ }, }, "packages": { - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], + "@babel/compat-data": ["@babel/compat-data@7.28.4", "", {}, "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="], - "@babel/core": ["@babel/core@7.28.3", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.3", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ=="], + "@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], @@ -51,9 +49,9 @@ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - "@babel/helpers": ["@babel/helpers@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.2" } }, "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw=="], + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - "@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], @@ -85,15 +83,15 @@ "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], - "@babel/runtime": ["@babel/runtime@7.28.3", "", {}, "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA=="], + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - "@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], + "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], - "@babel/traverse--for-generate-function-map": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], + "@babel/traverse--for-generate-function-map": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], - "@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], "@biomejs/biome": ["@biomejs/biome@2.0.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.0.0", "@biomejs/cli-darwin-x64": "2.0.0", "@biomejs/cli-linux-arm64": "2.0.0", "@biomejs/cli-linux-arm64-musl": "2.0.0", "@biomejs/cli-linux-x64": "2.0.0", "@biomejs/cli-linux-x64-musl": "2.0.0", "@biomejs/cli-win32-arm64": "2.0.0", "@biomejs/cli-win32-x64": "2.0.0" }, "bin": { "biome": "bin/biome" } }, "sha512-BlUoXEOI/UQTDEj/pVfnkMo8SrZw3oOWBDrXYFT43V7HTkIUDkBRY53IC5Jx1QkZbaB+0ai1wJIfYwp9+qaJTQ=="], @@ -183,6 +181,8 @@ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], @@ -265,7 +265,7 @@ "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], - "@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="], + "@types/node": ["@types/node@24.3.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="], "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], @@ -359,7 +359,7 @@ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001739", "", {}, "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001741", "", {}, "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -425,7 +425,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.213", "", {}, "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q=="], + "electron-to-chromium": ["electron-to-chromium@1.5.214", "", {}, "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q=="], "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], @@ -535,7 +535,7 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "gradido-blockchain-js": ["gradido-blockchain-js@github:gradido/gradido-blockchain-js#eccade8", { "dependencies": { "bindings": "^1.5.0", "nan": "^2.20.0", "node-addon-api": "^7.1.1", "node-gyp-build": "^4.8.1", "prebuildify": "git+https://github.com/einhornimmond/prebuildify#65d94455fab86b902c0d59bb9c06ac70470e56b2" } }, "gradido-gradido-blockchain-js-eccade8"], + "gradido-blockchain-js": ["gradido-blockchain-js@github:gradido/gradido-blockchain-js#f1c4bbc", { "dependencies": { "bindings": "^1.5.0", "nan": "^2.20.0", "node-addon-api": "^7.1.1", "node-gyp-build": "^4.8.1", "prebuildify": "git+https://github.com/einhornimmond/prebuildify#65d94455fab86b902c0d59bb9c06ac70470e56b2" } }, "gradido-gradido-blockchain-js-f1c4bbc"], "graphql": ["graphql@16.11.0", "", {}, "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw=="], @@ -729,7 +729,7 @@ "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], - "node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="], + "node-abi": ["node-abi@3.77.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ=="], "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], @@ -739,7 +739,7 @@ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + "node-releases": ["node-releases@2.0.20", "", {}, "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], @@ -785,7 +785,7 @@ "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "pino": ["pino@9.9.1", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-40SszWPOPwGhUIJ3zj0PsbMNV1bfg8nw5Qp/tP2FE2p3EuycmhDeYimKOMBAu6rtxcSw2QpjJsuK5A6v+en8Yw=="], + "pino": ["pino@9.9.4", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-d1XorUQ7sSKqVcYdXuEYs2h1LKxejSorMEJ76XoZ0pPDf8VzJMe7GlPXpMBZeQ9gE4ZPIp5uGD+5Nw7scxiigg=="], "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], diff --git a/dlt-connector/log4js-config.json b/dlt-connector/log4js-config.json index 6d96cd326..a6a33686b 100644 --- a/dlt-connector/log4js-config.json +++ b/dlt-connector/log4js-config.json @@ -8,7 +8,7 @@ "pattern": "yyyy-MM-dd", "layout": { - "type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{url}] [%f : %l] - %m" + "type": "pattern", "pattern": "%d{ISO8601} %p %c [%f : %l] - %m" }, "compress": true, "keepFileExt" : true, diff --git a/dlt-connector/src/client/GradidoNode/GradidoNodeClient.ts b/dlt-connector/src/client/GradidoNode/GradidoNodeClient.ts new file mode 100644 index 000000000..69609657e --- /dev/null +++ b/dlt-connector/src/client/GradidoNode/GradidoNodeClient.ts @@ -0,0 +1,251 @@ +import { ConfirmedTransaction } from 'gradido-blockchain-js' +import JsonRpcClient from 'jsonrpc-ts-client' +import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc' +import { getLogger, Logger } from 'log4js' +import { parse } from 'valibot' +import { CONFIG } from '../../config' +import { LOG4JS_BASE_CATEGORY } from '../../config/const' +import { Uuidv4Hash } from '../../data/Uuidv4Hash' +import { AddressType } from '../../enum/AddressType' +import { GradidoNodeErrorCodes } from '../../enum/GradidoNodeErrorCodes' +import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema' +import { Hex32, Hex32Input, HieroId, hex32Schema } from '../../schemas/typeGuard.schema' +import { isPortOpenRetry } from '../../utils/network' +import { + TransactionIdentifierInput, + TransactionsRangeInput, + transactionIdentifierSchema, + transactionsRangeSchema, +} from './input.schema' + +export class GradidoNodeRequestError extends Error { + private response?: JsonRpcEitherResponse + constructor(message: string, response?: JsonRpcEitherResponse) { + super(message) + this.name = 'GradidoNodeRequestError' + this.response = response + } + getResponse(): JsonRpcEitherResponse | undefined { + return this.response + } +} + +type WithTimeUsed = T & { timeUsed?: string } + +export class GradidoNodeClient { + private static instance: GradidoNodeClient + client: JsonRpcClient + logger: Logger + + private constructor() { + this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNodeClient`) + this.client = new JsonRpcClient({ + url: CONFIG.NODE_SERVER_URL, + }) + } + public static getInstance(): GradidoNodeClient { + if (!GradidoNodeClient.instance) { + GradidoNodeClient.instance = new GradidoNodeClient() + } + return GradidoNodeClient.instance + } + + /** + * getTransaction + * get a specific confirmed transaction from a specific community + * @param transactionIdentifier + * @returns the confirmed transaction or undefined if transaction is not found + * @throws GradidoNodeRequestError + */ + public async getTransaction( + transactionIdentifier: TransactionIdentifierInput, + ): Promise { + const parameter = { + ...parse(transactionIdentifierSchema, transactionIdentifier), + format: 'base64', + } + const response = await this.rpcCall<{ transaction: string }>('gettransaction', parameter) + if (response.isSuccess()) { + this.logger.debug('result: ', response.result.transaction) + return parse(confirmedTransactionSchema, response.result.transaction) + } + if (response.isError()) { + if (response.error.code === GradidoNodeErrorCodes.TRANSACTION_NOT_FOUND) { + return undefined + } + } + throw new GradidoNodeRequestError(response.error.message, response) + } + + /** + * getLastTransaction + * get the last confirmed transaction from a specific community + * @param hieroTopic the community hiero topic id + * @returns the last confirmed transaction or undefined if blockchain for community is empty or not found + * @throws GradidoNodeRequestError + */ + + public async getLastTransaction(hieroTopic: HieroId): Promise { + const parameter = { + format: 'base64', + topic: hieroTopic, + } + const response = await this.rpcCall<{ transaction: string }>('getlasttransaction', parameter) + if (response.isSuccess()) { + return parse(confirmedTransactionSchema, response.result.transaction) + } + if (response.isError()) { + if (response.error.code === GradidoNodeErrorCodes.GRADIDO_NODE_ERROR) { + return undefined + } + } + throw new GradidoNodeRequestError(response.error.message, response) + } + + /** + * getTransactions + * get list of confirmed transactions from a specific community + * @param input fromTransactionId is the id of the first transaction to return + * @param input maxResultCount is the max number of transactions to return + * @param input topic is the community hiero topic id + * @returns list of confirmed transactions + * @throws GradidoNodeRequestError + * @example + * ``` + * const transactions = await getTransactions({ + * fromTransactionId: 1, + * maxResultCount: 100, + * topic: communityUuid, + * }) + * ``` + */ + public async getTransactions(input: TransactionsRangeInput): Promise { + const parameter = { + ...parse(transactionsRangeSchema, input), + format: 'base64', + } + const result = await this.rpcCallResolved<{ transactions: string[] }>( + 'getTransactions', + parameter, + ) + return result.transactions.map((transactionBase64) => + parse(confirmedTransactionSchema, transactionBase64), + ) + } + + /** + * getTransactionsForAccount + * get list of confirmed transactions for a specific account + * @param transactionRange the range of transactions to return + * @param pubkey the public key of the account + * @returns list of confirmed transactions + * @throws GradidoNodeRequestError + */ + public async getTransactionsForAccount( + transactionRange: TransactionsRangeInput, + pubkey: Hex32Input, + ): Promise { + const parameter = { + ...parse(transactionsRangeSchema, transactionRange), + pubkey: parse(hex32Schema, pubkey), + format: 'base64', + } + const response = await this.rpcCallResolved<{ transactions: string[] }>( + 'listtransactionsforaddress', + parameter, + ) + return response.transactions.map((transactionBase64) => + parse(confirmedTransactionSchema, transactionBase64), + ) + } + + /** + * getAddressType + * get the address type of a specific user + * can be used to check if user/account exists on blockchain + * look also for gmw, auf and deferred transfer accounts + * @param pubkey the public key of the user or account + * @param hieroTopic the community hiero topic id + * @returns the address type of the user/account, AddressType.NONE if not found + * @throws GradidoNodeRequestError + */ + + public async getAddressType(pubkey: Hex32Input, hieroTopic: HieroId): Promise { + const parameter = { + pubkey: parse(hex32Schema, pubkey), + topic: hieroTopic, + } + const response = await this.rpcCallResolved<{ addressType: string }>( + 'getaddresstype', + parameter, + ) + return parse(addressTypeSchema, response.addressType) + } + + /** + * findUserByNameHash + * find a user by name hash + * @param nameHash the name hash of the user + * @param hieroTopic the community hiero topic id + * @returns the public key of the user as hex32 string or undefined if user is not found + * @throws GradidoNodeRequestError + */ + public async findUserByNameHash( + nameHash: Uuidv4Hash, + hieroTopic: HieroId, + ): Promise { + const parameter = { + nameHash: nameHash.getAsHexString(), + topic: hieroTopic, + } + const response = await this.rpcCall<{ pubkey: string; timeUsed: string }>( + 'findUserByNameHash', + parameter, + ) + if (response.isSuccess()) { + this.logger.info(`call findUserByNameHash, used ${response.result.timeUsed}`) + return parse(hex32Schema, response.result.pubkey) + } + if ( + response.isError() && + response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND + ) { + this.logger.debug(`call findUserByNameHash, return with error: ${response.error.message}`) + } + return undefined + } + + // ---------------- intern helper functions ----------------------------------- + + // return result on success or throw error + protected resolveResponse( + response: JsonRpcEitherResponse, + onSuccess: (result: T) => R, + ): R { + if (response.isSuccess()) { + return onSuccess(response.result) + } else if (response.isError()) { + throw new GradidoNodeRequestError(response.error.message, response) + } + throw new GradidoNodeRequestError('no success and no error') + } + + // template rpcCall, check first if port is open before executing json rpc 2.0 request + protected async rpcCall(method: string, parameter: any): Promise> { + this.logger.debug('call %s with %s', method, parameter) + await isPortOpenRetry(CONFIG.NODE_SERVER_URL) + return this.client.exec(method, parameter) + } + + // template rpcCall, check first if port is open before executing json rpc 2.0 request, + // throw error on failure, return result on success + protected async rpcCallResolved(method: string, parameter: any): Promise { + const response = await this.rpcCall>(method, parameter) + return this.resolveResponse(response, (result: WithTimeUsed) => { + if (result.timeUsed) { + this.logger.info(`call %s, used ${result.timeUsed}`, method) + } + return result as T + }) + } +} diff --git a/dlt-connector/src/client/GradidoNode/api.ts b/dlt-connector/src/client/GradidoNode/api.ts deleted file mode 100644 index 383f1c3f4..000000000 --- a/dlt-connector/src/client/GradidoNode/api.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { AddressType, ConfirmedTransaction } from 'gradido-blockchain-js' -import { getLogger } from 'log4js' -import * as v from 'valibot' -import { LOG4JS_BASE_CATEGORY } from '../../config/const' -import { Uuidv4Hash } from '../../data/Uuidv4Hash' -import { GradidoNodeErrorCodes } from '../../enum/GradidoNodeErrorCodes' -import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema' -import { Hex32, Hex32Input, HieroId, hex32Schema } from '../../schemas/typeGuard.schema' -import { - TransactionIdentifierInput, - TransactionsRangeInput, - transactionIdentifierSchema, - transactionsRangeSchema, -} from './input.schema' -import { GradidoNodeRequestError, rpcCall, rpcCallResolved } from './jsonrpc' - -const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`) - -/** - * getTransactions - * get list of confirmed transactions from a specific community - * @param input fromTransactionId is the id of the first transaction to return - * @param input maxResultCount is the max number of transactions to return - * @param input topic is the community hiero topic id - * @returns list of confirmed transactions - * @throws GradidoNodeRequestError - * @example - * ``` - * const transactions = await getTransactions({ - * fromTransactionId: 1, - * maxResultCount: 100, - * topic: communityUuid, - * }) - * ``` - */ -async function getTransactions(input: TransactionsRangeInput): Promise { - const parameter = { ...v.parse(transactionsRangeSchema, input), format: 'base64' } - const result = await rpcCallResolved<{ transactions: string[] }>('getTransactions', parameter) - return result.transactions.map((transactionBase64) => - v.parse(confirmedTransactionSchema, transactionBase64), - ) -} - -/** - * getTransaction - * get a specific confirmed transaction from a specific community - * @param transactionIdentifier - * @returns the confirmed transaction or undefined if transaction is not found - * @throws GradidoNodeRequestError - */ -async function getTransaction( - transactionIdentifier: TransactionIdentifierInput, -): Promise { - const parameter = { - ...v.parse(transactionIdentifierSchema, transactionIdentifier), - format: 'base64', - } - const response = await rpcCall<{ transaction: string }>('gettransaction', parameter) - if (response.isSuccess()) { - return v.parse(confirmedTransactionSchema, response.result.transaction) - } - if (response.isError()) { - if (response.error.code === GradidoNodeErrorCodes.TRANSACTION_NOT_FOUND) { - return undefined - } - } - throw new GradidoNodeRequestError(response.error.message, response) -} - -/** - * getLastTransaction - * get the last confirmed transaction from a specific community - * @param iotaTopic the community topic - * @returns the last confirmed transaction or undefined if blockchain for community is empty or not found - * @throws GradidoNodeRequestError - */ - -async function getLastTransaction( - iotaTopic: Uuidv4Hash, -): Promise { - const response = await rpcCall<{ transaction: string }>('getlasttransaction', { - format: 'base64', - topic: iotaTopic.getAsHexString(), - }) - if (response.isSuccess()) { - return v.parse(confirmedTransactionSchema, response.result.transaction) - } - if (response.isError()) { - if (response.error.code === GradidoNodeErrorCodes.GRADIDO_NODE_ERROR) { - return undefined - } - throw new GradidoNodeRequestError(response.error.message, response) - } -} - -/** - * getAddressType - * get the address type of a specific user - * can be used to check if user/account exists on blockchain - * look also for gmw, auf and deferred transfer accounts - * @param pubkey the public key of the user or account - * @param iotaTopic the community topic - * @returns the address type of the user/account or undefined - * @throws GradidoNodeRequestError - */ - -async function getAddressType(pubkey: Hex32Input, hieroTopic: HieroId): Promise { - const parameter = { - pubkey: v.parse(hex32Schema, pubkey), - communityId: hieroTopic, - } - const response = await rpcCallResolved<{ addressType: string }>('getaddresstype', parameter) - return v.parse(addressTypeSchema, response.addressType) -} - -/** - * findUserByNameHash - * find a user by name hash - * @param nameHash the name hash of the user - * @param iotaTopic the community topic - * @returns the public key of the user as hex32 string or undefined if user is not found - * @throws GradidoNodeRequestError - */ -async function findUserByNameHash( - nameHash: Uuidv4Hash, - hieroTopic: HieroId, -): Promise { - const parameter = { - nameHash: nameHash.getAsHexString(), - communityId: hieroTopic, - } - const response = await rpcCall<{ pubkey: string; timeUsed: string }>( - 'findUserByNameHash', - parameter, - ) - if (response.isSuccess()) { - logger.info(`call findUserByNameHash, used ${response.result.timeUsed}`) - return v.parse(hex32Schema, response.result.pubkey) - } - if ( - response.isError() && - response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND - ) { - logger.debug(`call findUserByNameHash, return with error: ${response.error.message}`) - } - return undefined -} - -/** - * getTransactionsForAccount - * get list of confirmed transactions for a specific account - * @param transactionRange the range of transactions to return - * @param pubkey the public key of the account - * @returns list of confirmed transactions - * @throws GradidoNodeRequestError - */ -async function getTransactionsForAccount( - transactionRange: TransactionsRangeInput, - pubkey: Hex32Input, -): Promise { - const parameter = { - ...v.parse(transactionsRangeSchema, transactionRange), - pubkey: v.parse(hex32Schema, pubkey), - format: 'base64', - } - const response = await rpcCallResolved<{ transactions: string[] }>( - 'listtransactionsforaddress', - parameter, - ) - return response.transactions.map((transactionBase64) => - v.parse(confirmedTransactionSchema, transactionBase64), - ) -} - -export { - getTransaction, - getLastTransaction, - getTransactions, - getAddressType, - getTransactionsForAccount, - findUserByNameHash, -} diff --git a/dlt-connector/src/client/GradidoNode/input.schema.test.ts b/dlt-connector/src/client/GradidoNode/input.schema.test.ts new file mode 100644 index 000000000..1268290fc --- /dev/null +++ b/dlt-connector/src/client/GradidoNode/input.schema.test.ts @@ -0,0 +1,60 @@ +import { beforeAll, describe, expect, it } from 'bun:test' +import { parse } from 'valibot' +import { + HieroId, + HieroTransactionId, + hieroIdSchema, + hieroTransactionIdSchema, +} from '../../schemas/typeGuard.schema' +import { transactionIdentifierSchema } from './input.schema' + +let topic: HieroId +const topicString = '0.0.261' +let hieroTransactionId: HieroTransactionId +beforeAll(() => { + topic = parse(hieroIdSchema, topicString) + hieroTransactionId = parse(hieroTransactionIdSchema, '0.0.261-1755348116-1281621') +}) + +describe('transactionIdentifierSchema ', () => { + it('valid, transaction identified by transactionNr and topic', () => { + expect( + parse(transactionIdentifierSchema, { + transactionId: 1, + topic: topicString, + }), + ).toEqual({ + transactionId: 1, + hieroTransactionId: undefined, + topic, + }) + }) + it('valid, transaction identified by hieroTransactionId and topic', () => { + expect( + parse(transactionIdentifierSchema, { + hieroTransactionId: '0.0.261-1755348116-1281621', + topic: topicString, + }), + ).toEqual({ + hieroTransactionId, + topic, + }) + }) + it('invalid, missing topic', () => { + expect(() => + parse(transactionIdentifierSchema, { + transactionId: 1, + hieroTransactionId: '0.0.261-1755348116-1281621', + }), + ).toThrowError(new Error('Invalid key: Expected "topic" but received undefined')) + }) + it('invalid, transactionNr and iotaMessageId set', () => { + expect(() => + parse(transactionIdentifierSchema, { + transactionId: 1, + hieroTransactionId: '0.0.261-1755348116-1281621', + topic, + }), + ).toThrowError(new Error('expect transactionNr or hieroTransactionId not both')) + }) +}) diff --git a/dlt-connector/src/client/GradidoNode/input.schema.ts b/dlt-connector/src/client/GradidoNode/input.schema.ts index 218805c3f..baa075baf 100644 --- a/dlt-connector/src/client/GradidoNode/input.schema.ts +++ b/dlt-connector/src/client/GradidoNode/input.schema.ts @@ -14,7 +14,7 @@ export type TransactionsRangeInput = v.InferInput= 1')), undefined, ), @@ -23,7 +23,7 @@ export const transactionIdentifierSchema = v.pipe( }), v.custom((value: any) => { const setFieldsCount = - Number(value.transactionNr !== undefined) + Number(value.hieroTransactionId !== undefined) + Number(value.transactionId !== undefined) + Number(value.hieroTransactionId !== undefined) if (setFieldsCount !== 1) { return false } diff --git a/dlt-connector/src/client/GradidoNode/jsonrpc.ts b/dlt-connector/src/client/GradidoNode/jsonrpc.ts deleted file mode 100644 index b93ff65f0..000000000 --- a/dlt-connector/src/client/GradidoNode/jsonrpc.ts +++ /dev/null @@ -1,60 +0,0 @@ -import JsonRpcClient from 'jsonrpc-ts-client' -import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc' -import { getLogger } from 'log4js' -import { CONFIG } from '../../config' -import { LOG4JS_BASE_CATEGORY } from '../../config/const' -import { isPortOpenRetry } from '../../utils/network' - -const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`) - -export const client = new JsonRpcClient({ - url: CONFIG.NODE_SERVER_URL, -}) - -export class GradidoNodeRequestError extends Error { - private response?: JsonRpcEitherResponse - constructor(message: string, response?: JsonRpcEitherResponse) { - super(message) - this.name = 'GradidoNodeRequestError' - this.response = response - } - getResponse(): JsonRpcEitherResponse | undefined { - return this.response - } -} - -// return result on success or throw error -export function resolveResponse( - response: JsonRpcEitherResponse, - onSuccess: (result: T) => R, -): R { - if (response.isSuccess()) { - return onSuccess(response.result) - } else if (response.isError()) { - throw new GradidoNodeRequestError(response.error.message, response) - } - throw new GradidoNodeRequestError('no success and no error') -} - -type WithTimeUsed = T & { timeUsed?: string } - -// template rpcCall, check first if port is open before executing json rpc 2.0 request -export async function rpcCall( - method: string, - parameter: any, -): Promise> { - logger.debug('call %s with %s', method, parameter) - await isPortOpenRetry(CONFIG.NODE_SERVER_URL) - return client.exec(method, parameter) -} - -// template rpcCall, check first if port is open before executing json rpc 2.0 request, throw error on failure, return result on success -export async function rpcCallResolved(method: string, parameter: any): Promise { - const response = await rpcCall>(method, parameter) - return resolveResponse(response, (result: WithTimeUsed) => { - if (result.timeUsed) { - logger.info(`call %s, used ${result.timeUsed}`, method) - } - return result as T - }) -} diff --git a/dlt-connector/src/client/backend/BackendClient.ts b/dlt-connector/src/client/backend/BackendClient.ts index 8545ec1f4..2d587a4d8 100644 --- a/dlt-connector/src/client/backend/BackendClient.ts +++ b/dlt-connector/src/client/backend/BackendClient.ts @@ -1,17 +1,16 @@ import { GraphQLClient } from 'graphql-request' import { SignJWT } from 'jose' - -import { CONFIG } from '../../config' -import { - communitySchema, - type Community, - homeCommunityGraphqlQuery, - setHomeCommunityTopicId -} from './community.schema' import { getLogger, Logger } from 'log4js' -import { LOG4JS_BASE_CATEGORY } from '../../config/const' import * as v from 'valibot' +import { CONFIG } from '../../config' +import { LOG4JS_BASE_CATEGORY } from '../../config/const' import { HieroId, Uuidv4 } from '../../schemas/typeGuard.schema' +import { + type Community, + communitySchema, + homeCommunityGraphqlQuery, + setHomeCommunityTopicId, +} from './community.schema' // Source: https://refactoring.guru/design-patterns/singleton/typescript/example // and ../federation/client/FederationClientFactory.ts @@ -22,7 +21,7 @@ import { HieroId, Uuidv4 } from '../../schemas/typeGuard.schema' export class BackendClient { private static instance: BackendClient client: GraphQLClient - logger: Logger + logger: Logger /** * The Singleton's constructor should always be private to prevent direct @@ -49,17 +48,19 @@ export class BackendClient { * This implementation let you subclass the Singleton class while keeping * just one instance of each subclass around. */ - public static getInstance(): BackendClient | undefined { + public static getInstance(): BackendClient { if (!BackendClient.instance) { BackendClient.instance = new BackendClient() - } + } return BackendClient.instance } public async getHomeCommunityDraft(): Promise { this.logger.info('check home community on backend') const { data, errors } = await this.client.rawRequest<{ homeCommunity: Community }>( - homeCommunityGraphqlQuery, {}, await this.getRequestHeader(), + homeCommunityGraphqlQuery, + {}, + await this.getRequestHeader(), ) if (errors) { throw errors[0] @@ -70,11 +71,13 @@ export class BackendClient { public async setHomeCommunityTopicId(uuid: Uuidv4, hieroTopicId: HieroId): Promise { this.logger.info('update home community on backend') const { data, errors } = await this.client.rawRequest<{ updateHomeCommunity: Community }>( - setHomeCommunityTopicId, { uuid, hieroTopicId }, await this.getRequestHeader(), + setHomeCommunityTopicId, + { uuid, hieroTopicId }, + await this.getRequestHeader(), ) if (errors) { throw errors[0] - } + } return v.parse(communitySchema, data.updateHomeCommunity) } diff --git a/dlt-connector/src/client/backend/community.schema.test.ts b/dlt-connector/src/client/backend/community.schema.test.ts index d39c376ba..e56056377 100644 --- a/dlt-connector/src/client/backend/community.schema.test.ts +++ b/dlt-connector/src/client/backend/community.schema.test.ts @@ -1,24 +1,23 @@ // only for IDE, bun don't need this to work import { describe, expect, it } from 'bun:test' import * as v from 'valibot' -import { uuidv4Schema } from '../../schemas/typeGuard.schema' +import { hieroIdSchema, uuidv4Schema } from '../../schemas/typeGuard.schema' import { communitySchema } from './community.schema' -import { hieroIdSchema } from '../../schemas/typeGuard.schema' describe('community.schema', () => { it('community', () => { expect( v.parse(communitySchema, { uuid: '4f28e081-5c39-4dde-b6a4-3bde71de8d65', - topicId: '0.0.4', + hieroTopicId: '0.0.4', foreign: false, - createdAt: '2021-01-01', + creationDate: '2021-01-01', }), ).toEqual({ - topicId: v.parse(hieroIdSchema, '0.0.4'), + hieroTopicId: v.parse(hieroIdSchema, '0.0.4'), uuid: v.parse(uuidv4Schema, '4f28e081-5c39-4dde-b6a4-3bde71de8d65'), foreign: false, - createdAt: new Date('2021-01-01'), + creationDate: new Date('2021-01-01'), }) }) }) diff --git a/dlt-connector/src/client/backend/community.schema.ts b/dlt-connector/src/client/backend/community.schema.ts index 0222322a4..b74ac49cf 100644 --- a/dlt-connector/src/client/backend/community.schema.ts +++ b/dlt-connector/src/client/backend/community.schema.ts @@ -4,17 +4,13 @@ import { dateSchema } from '../../schemas/typeConverter.schema' import { hieroIdSchema, uuidv4Schema } from '../../schemas/typeGuard.schema' /** - * Schema Definitions for rpc call parameter, when dlt-connector is called from backend - */ - -/** - * Schema for community, for creating new CommunityRoot Transaction on gradido blockchain + * Schema Definitions for graphql response */ export const communitySchema = v.object({ uuid: uuidv4Schema, - topicId: v.nullish(hieroIdSchema), + hieroTopicId: v.nullish(hieroIdSchema), foreign: v.boolean('expect boolean type'), - createdAt: dateSchema, + creationDate: dateSchema, }) export type CommunityInput = v.InferInput @@ -25,7 +21,7 @@ export const homeCommunityGraphqlQuery = gql` query { homeCommunity { uuid - topicId + hieroTopicId foreign creationDate } @@ -33,10 +29,10 @@ export const homeCommunityGraphqlQuery = gql` ` export const setHomeCommunityTopicId = gql` - mutation ($uuid: string, $hieroTopicId: string){ + mutation ($uuid: String!, $hieroTopicId: String){ updateHomeCommunity(uuid: $uuid, hieroTopicId: $hieroTopicId) { uuid - topicId + hieroTopicId foreign creationDate } diff --git a/dlt-connector/src/client/hiero/HieroClient.ts b/dlt-connector/src/client/hiero/HieroClient.ts index a058f1c72..f63ac650a 100644 --- a/dlt-connector/src/client/hiero/HieroClient.ts +++ b/dlt-connector/src/client/hiero/HieroClient.ts @@ -6,7 +6,7 @@ import { PrivateKey, Timestamp, TopicCreateTransaction, - TopicId, + TopicId, TopicInfoQuery, TopicMessageSubmitTransaction, TopicUpdateTransaction, @@ -14,13 +14,13 @@ import { TransactionResponse, Wallet, } from '@hashgraph/sdk' -import { parse } from 'valibot' import { GradidoTransaction, HieroTopicId } from 'gradido-blockchain-js' import { getLogger, Logger } from 'log4js' +import { parse } from 'valibot' import { CONFIG } from '../../config' import { LOG4JS_BASE_CATEGORY } from '../../config/const' import { HieroId, hieroIdSchema } from '../../schemas/typeGuard.schema' -import { topicInfoSchema, type TopicInfoOutput } from './output.schema' +import { type TopicInfoOutput, topicInfoSchema } from './output.schema' // https://docs.hedera.com/hedera/sdks-and-apis/hedera-api/consensus/consensusupdatetopic export const MIN_AUTORENEW_PERIOD = 6999999 //seconds @@ -29,7 +29,7 @@ export const MAX_AUTORENEW_PERIOD = 8000001 // seconds export class HieroClient { private static instance: HieroClient wallet: Wallet - client: Client + client: Client logger: Logger private constructor() { @@ -43,12 +43,10 @@ export class HieroClient { operatorKey = PrivateKey.fromStringECDSA(CONFIG.HIERO_OPERATOR_KEY) } this.wallet = new Wallet(CONFIG.HIERO_OPERATOR_ID, operatorKey, provider) + this.client.setOperator(CONFIG.HIERO_OPERATOR_ID, operatorKey) } public static getInstance(): HieroClient { - if (!CONFIG.HIERO_ACTIVE) { - throw new Error('hiero is disabled via config...') - } if (!HieroClient.instance) { HieroClient.instance = new HieroClient() } @@ -87,14 +85,14 @@ export class HieroClient { public async getTopicInfo(topicId: HieroId): Promise { const info = await new TopicInfoQuery() - .setTopicId(TopicId.fromString(topicId)) - .execute(this.client) + .setTopicId(TopicId.fromString(topicId)) + .execute(this.client) this.logger.debug(JSON.stringify(info, null, 2)) return parse(topicInfoSchema, { topicId: topicId.toString(), sequenceNumber: info.sequenceNumber.toNumber(), - expirationTime: info.expirationTime?.toString(), - autoRenewPeriod: info.autoRenewPeriod?.seconds, + expirationTime: info.expirationTime?.toDate(), + autoRenewPeriod: info.autoRenewPeriod?.seconds.toNumber(), autoRenewAccountId: info.autoRenewAccountId?.toString(), }) } diff --git a/dlt-connector/src/client/hiero/output.schema.ts b/dlt-connector/src/client/hiero/output.schema.ts index da1864aae..ea99f677a 100644 --- a/dlt-connector/src/client/hiero/output.schema.ts +++ b/dlt-connector/src/client/hiero/output.schema.ts @@ -1,8 +1,8 @@ import * as v from 'valibot' -import { hieroIdSchema } from '../../schemas/typeGuard.schema' import { dateSchema } from '../../schemas/typeConverter.schema' +import { hieroIdSchema } from '../../schemas/typeGuard.schema' -// schema definitions for exporting data from hiero request as json back to caller +// schema definitions for exporting data from hiero request as json back to caller /*export const dateStringSchema = v.pipe( v.enum([v.string(), v.date()], v.transform(in: string | Date) @@ -11,12 +11,11 @@ import { dateSchema } from '../../schemas/typeConverter.schema' export const positiveNumberSchema = v.pipe(v.number(), v.minValue(0)) export const topicInfoSchema = v.object({ - topicId: hieroIdSchema, - sequenceNumber: positiveNumberSchema, - expirationTime: dateSchema, - autoRenewPeriod: v.optional(positiveNumberSchema, 0), - autoRenewAccountId: v.optional(hieroIdSchema, '0.0.0'), + topicId: hieroIdSchema, + sequenceNumber: positiveNumberSchema, + expirationTime: dateSchema, + autoRenewPeriod: v.optional(positiveNumberSchema, 0), + autoRenewAccountId: v.optional(hieroIdSchema, '0.0.0'), }) export type TopicInfoOutput = v.InferOutput - diff --git a/dlt-connector/src/config/const.ts b/dlt-connector/src/config/const.ts index 91dd84707..e0dfc82a3 100644 --- a/dlt-connector/src/config/const.ts +++ b/dlt-connector/src/config/const.ts @@ -1,3 +1,3 @@ export const LOG4JS_BASE_CATEGORY = 'dlt' // 7 days -export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE = 1000 * 60 * 60 * 24 * 7 \ No newline at end of file +export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE = 1000 * 60 * 60 * 24 * 7 diff --git a/dlt-connector/src/config/index.ts b/dlt-connector/src/config/index.ts index 9a9889cda..d4184d9d6 100644 --- a/dlt-connector/src/config/index.ts +++ b/dlt-connector/src/config/index.ts @@ -1,60 +1,23 @@ -/* eslint-disable n/no-process-env */ import dotenv from 'dotenv' +import { parse, InferOutput, ValiError } from 'valibot' +import { configSchema } from './schema' dotenv.config() -const logging = { - LOG4JS_CONFIG: 'log4js-config.json', - // default log level on production should be info - LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', +type ConfigOutput = InferOutput + +let config: ConfigOutput +console.info('Config loading...') +try { + config = parse(configSchema, process.env) +} catch (error: Error | unknown) { + if (error instanceof ValiError) { + console.error(`${error.issues[0].path[0].key}: ${error.message} received: ${error.issues[0].received}`) + } else { + console.error(error) + } + // console.error('Config error:', JSON.stringify(error, null, 2)) + process.exit(1) } -const server = { - PRODUCTION: process.env.NODE_ENV === 'production', - DLT_CONNECTOR_PORT: process.env.DLT_CONNECTOR_PORT ?? 6010, -} - -const secrets = { - JWT_SECRET: process.env.JWT_SECRET ?? 'secret123', - GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET: - process.env.GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET ?? 'invalid', - GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: - process.env.GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY ?? 'invalid', -} - -const iota = { - IOTA_HOME_COMMUNITY_SEED: process.env.IOTA_HOME_COMMUNITY_SEED ?? null, -} - -const hiero = { - HIERO_ACTIVE: process.env.HIERO_ACTIVE === 'true' || false, - HIERO_HEDERA_NETWORK: process.env.HIERO_HEDERA_NETWORK ?? 'testnet', - HIERO_OPERATOR_ID: process.env.HIERO_OPERATOR_ID ?? '0.0.2', - HIERO_OPERATOR_KEY: - process.env.HIERO_OPERATOR_KEY ?? - '302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137', -} - -const apis = { - CONNECT_TIMEOUT_MS: process.env.CONNECT_TIMEOUT_MS - ? Number.parseInt(process.env.CONNECT_TIMEOUT_MS) - : 1000, - CONNECT_RETRY_COUNT: process.env.CONNECT_RETRY_COUNT - ? Number.parseInt(process.env.CONNECT_RETRY_COUNT) - : 15, - CONNECT_RETRY_DELAY_MS: process.env.CONNECT_RETRY_DELAY_MS - ? Number.parseInt(process.env.CONNECT_RETRY_DELAY_MS) - : 500, - IOTA_API_URL: process.env.IOTA_API_URL ?? 'https://chrysalis-nodes.iota.org', - NODE_SERVER_URL: process.env.NODE_SERVER_URL ?? 'http://127.0.0.1:8340', - BACKEND_SERVER_URL: process.env.BACKEND_SERVER_URL ?? 'http://127.0.0.1:4000', -} - -export const CONFIG = { - ...logging, - ...server, - ...secrets, - ...iota, - ...hiero, - ...apis, -} +export const CONFIG = config diff --git a/dlt-connector/src/config/schema.ts b/dlt-connector/src/config/schema.ts index b9a170e43..3dfe3cbe9 100644 --- a/dlt-connector/src/config/schema.ts +++ b/dlt-connector/src/config/schema.ts @@ -1,24 +1,77 @@ +import { MemoryBlock } from 'gradido-blockchain-js' import * as v from 'valibot' -export const HIERO_ACTIVE = v.nullish( - v.boolean('Flag to indicate if the Hiero (Hedera Hashgraph Ledger) service is used.'), - false, -) +const hexSchema = v.pipe(v.string('expect string type'), v.hexadecimal('expect hexadecimal string')) -export const HIERO_HEDERA_NETWORK = v.nullish( - v.union([v.literal('mainnet'), v.literal('testnet'), v.literal('previewnet')]), - 'testnet', -) +const hex16Schema = v.pipe(hexSchema, v.length(32, 'expect string length = 32')) -export const HIERO_OPERATOR_ID = v.nullish( - v.pipe(v.string('The operator ID for Hiero integration'), v.regex(/^[0-9]+\.[0-9]+\.[0-9]+$/)), - '0.0.2', -) - -export const HIERO_OPERATOR_KEY = v.nullish( - v.pipe( - v.string('The operator key for Hiero integration, default is for local default node'), - v.regex(/^[0-9a-fA-F]{64,96}$/), +export const configSchema = v.object({ + LOG4JS_CONFIG: v.optional( + v.string('The path to the log4js configuration file'), + './log4js-config.json', ), - '302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137', -) + LOG_LEVEL: v.optional(v.string('The log level'), 'info'), + DLT_CONNECTOR_PORT: v.optional( + v.pipe( + v.string('A valid port on which the DLT connector is running'), + v.transform((input: string) => Number(input)), + v.minValue(1), + v.maxValue(65535), + ), + '6010', + ), + JWT_SECRET: v.pipe( + v.string('The JWT secret for connecting to the backend'), + v.custom((input: unknown): boolean => { + if (process.env.NODE_ENV === 'production' && input === 'secret123') { + return false + } + return true + }, "Shouldn't use default value in production"), + ), + GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET: hexSchema, + GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: hex16Schema, + HOME_COMMUNITY_SEED: v.pipe( + hexSchema, + v.length(64, 'expect seed length minimum 64 characters (32 Bytes)'), + v.transform((input: string) => MemoryBlock.fromHex(input)), + ), + HIERO_HEDERA_NETWORK: v.optional( + v.union([v.literal('mainnet'), v.literal('testnet'), v.literal('previewnet')]), + 'testnet', + ), + HIERO_OPERATOR_ID: v.pipe( + v.string('The operator ID (Account id) for Hiero integration'), + v.regex(/^[0-9]+\.[0-9]+\.[0-9]+$/), + ), + HIERO_OPERATOR_KEY: v.pipe( + v.string('The operator key (Private key) for Hiero integration'), + v.hexadecimal(), + v.minLength(64), + v.maxLength(96), + ), + CONNECT_TIMEOUT_MS: v.optional( + v.pipe(v.number('The connection timeout in milliseconds'), v.minValue(200), v.maxValue(120000)), + 1000, + ), + CONNECT_RETRY_COUNT: v.optional( + v.pipe(v.number('The connection retry count'), v.minValue(1), v.maxValue(50)), + 15, + ), + CONNECT_RETRY_DELAY_MS: v.optional( + v.pipe( + v.number('The connection retry delay in milliseconds'), + v.minValue(100), + v.maxValue(10000), + ), + 500, + ), + NODE_SERVER_URL: v.optional( + v.string('The URL of the gradido node server'), + 'http://localhost:6010', + ), + BACKEND_SERVER_URL: v.optional( + v.string('The URL of the gradido backend server'), + 'http://localhost:6010', + ), +}) diff --git a/dlt-connector/src/data/KeyPairIdentifier.logic.ts b/dlt-connector/src/data/KeyPairIdentifier.logic.ts index 437d9a458..1563d4b53 100644 --- a/dlt-connector/src/data/KeyPairIdentifier.logic.ts +++ b/dlt-connector/src/data/KeyPairIdentifier.logic.ts @@ -17,7 +17,7 @@ export class KeyPairIdentifierLogic { isUserKeyPair(): boolean { return ( this.identifier.seed === undefined && - this.identifier.account != undefined && + this.identifier.account != null && this.identifier.account.accountNr === 0 ) } @@ -25,7 +25,7 @@ export class KeyPairIdentifierLogic { isAccountKeyPair(): boolean { return ( this.identifier.seed === undefined && - this.identifier.account != undefined && + this.identifier.account != null && this.identifier.account.accountNr > 0 ) } diff --git a/dlt-connector/src/errors.ts b/dlt-connector/src/errors.ts index e9cbaee98..3bedb023a 100644 --- a/dlt-connector/src/errors.ts +++ b/dlt-connector/src/errors.ts @@ -35,6 +35,20 @@ export class GradidoNodeInvalidTransactionError extends GradidoNodeError { } } +export class GradidoBlockchainError extends Error { + constructor(message: string) { + super(message) + this.name = 'GradidoBlockchainError' + } +} + +export class GradidoBlockchainCryptoError extends GradidoBlockchainError { + constructor(message: string) { + super(message) + this.name = 'GradidoBlockchainCryptoError' + } +} + export class ParameterError extends Error { constructor(message: string) { super(message) diff --git a/dlt-connector/src/index.ts b/dlt-connector/src/index.ts index de0b07729..733bbcfc0 100644 --- a/dlt-connector/src/index.ts +++ b/dlt-connector/src/index.ts @@ -1,83 +1,111 @@ import { readFileSync } from 'node:fs' import { Elysia } from 'elysia' import { loadCryptoKeys, MemoryBlock } from 'gradido-blockchain-js' -import { configure, getLogger } from 'log4js' -import * as v from 'valibot' +import { configure, getLogger, Logger } from 'log4js' +import { parse } from 'valibot' import { BackendClient } from './client/backend/BackendClient' +import { GradidoNodeClient } from './client/GradidoNode/GradidoNodeClient' import { HieroClient } from './client/hiero/HieroClient' -import { getTransaction } from './client/GradidoNode/api' import { CONFIG } from './config' -import { SendToIotaContext } from './interactions/sendToIota/SendToIota.context' -import { KeyPairCacheManager } from './KeyPairCacheManager' -import { keyGenerationSeedSchema } from './schemas/base.schema' -import { isPortOpenRetry } from './utils/network' -import { appRoutes } from './server' import { MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE } from './config/const' +import { SendToHieroContext } from './interactions/sendToHiero/SendToHiero.context' +import { KeyPairCacheManager } from './KeyPairCacheManager' +import { Community, communitySchema } from './schemas/transaction.schema' +import { appRoutes } from './server' +import { isPortOpenRetry } from './utils/network' + +type Clients = { + backend: BackendClient + hiero: HieroClient + gradidoNode: GradidoNodeClient +} async function main() { + // load everything from .env + const logger = loadConfig() + const clients = createClients() + const { hiero, gradidoNode } = clients + + // show hiero account balance, double also as check if valid hiero account was given in config + const balance = await hiero.getBalance() + logger.info(`Hiero Account Balance: ${balance.hbars.toString()}`) + + // get home community, create topic if not exist, or check topic expiration and update it if needed + const homeCommunity = await homeCommunitySetup(clients, logger) + + // ask gradido node if community blockchain was created + try { + if ( + !(await gradidoNode.getTransaction({ transactionId: 1, topic: homeCommunity.hieroTopicId })) + ) { + // if not exist, create community root transaction + await SendToHieroContext(homeCommunity) + } + } catch (e) { + logger.error(`error requesting gradido node: ${e}`) + } + // listen for rpc request from backend (graphql replaced with elysiaJS) + new Elysia().use(appRoutes).listen(CONFIG.DLT_CONNECTOR_PORT, () => { + logger.info(`Server is running at http://localhost:${CONFIG.DLT_CONNECTOR_PORT}`) + }) +} + +function loadConfig(): Logger { // configure log4js // TODO: replace late by loader from config-schema const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8')) configure(options) const logger = getLogger('dlt') - // check if valid seed for root key pair generation is present - if (!v.safeParse(keyGenerationSeedSchema, CONFIG.IOTA_HOME_COMMUNITY_SEED).success) { - logger.error('IOTA_HOME_COMMUNITY_SEED must be a valid hex string, at least 64 characters long') - process.exit(1) - } // load crypto keys for gradido blockchain lib loadCryptoKeys( MemoryBlock.fromHex(CONFIG.GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET), MemoryBlock.fromHex(CONFIG.GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY), ) + return logger +} - // ask backend for home community if we haven't one - const backend = BackendClient.getInstance() - if (!backend) { - throw new Error('cannot create backend client') - } - const hieroClient = HieroClient.getInstance() - if (!hieroClient) { - throw new Error('cannot create hiero client') +// needed to be called after loading config +function createClients(): Clients { + return { + backend: BackendClient.getInstance(), + hiero: HieroClient.getInstance(), + gradidoNode: GradidoNodeClient.getInstance(), } +} + +async function homeCommunitySetup({ backend, hiero }: Clients, logger: Logger): Promise { // wait for backend server await isPortOpenRetry(CONFIG.BACKEND_SERVER_URL) + // ask backend for home community let homeCommunity = await backend.getHomeCommunityDraft() // on missing topicId, create one - if (!homeCommunity.topicId) { - const topicId = await hieroClient.createTopic() + if (!homeCommunity.hieroTopicId) { + const topicId = await hiero.createTopic() + // update topic on backend server homeCommunity = await backend.setHomeCommunityTopicId(homeCommunity.uuid, topicId) } else { // if topic exist, check if we need to update it - let topicInfo = await hieroClient.getTopicInfo(homeCommunity.topicId) - if (topicInfo.expirationTime.getTime() - new Date().getTime() < MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE) { - await hieroClient.updateTopic(homeCommunity.topicId) - topicInfo = await hieroClient.getTopicInfo(homeCommunity.topicId) - logger.info('updated topic info, new expiration time: %s', topicInfo.expirationTime.toLocaleDateString()) + let topicInfo = await hiero.getTopicInfo(homeCommunity.hieroTopicId) + if ( + topicInfo.expirationTime.getTime() - new Date().getTime() < + MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE + ) { + await hiero.updateTopic(homeCommunity.hieroTopicId) + topicInfo = await hiero.getTopicInfo(homeCommunity.hieroTopicId) + logger.info( + `updated topic info, new expiration time: ${topicInfo.expirationTime.toLocaleDateString()}`, + ) } } - if (!homeCommunity.topicId) { + if (!homeCommunity.hieroTopicId) { throw new Error('still no topic id, after creating topic and update community in backend.') } - KeyPairCacheManager.getInstance().setHomeCommunityTopicId(homeCommunity.topicId) - logger.info('home community topic: %s', homeCommunity.topicId) - logger.info('gradido node server: %s', CONFIG.NODE_SERVER_URL) - // ask gradido node if community blockchain was created - try { - if (!(await getTransaction({ transactionNr: 1, topic: homeCommunity.topicId }))) { - // if not exist, create community root transaction - await SendToIotaContext(homeCommunity) - } - } catch (e) { - logger.error('error requesting gradido node: ', e) - } - // listen for rpc request from backend (graphql replaced with trpc and elysia) - new Elysia() - .use(appRoutes) - .listen(CONFIG.DLT_CONNECTOR_PORT, () => { - logger.info(`Server is running at http://localhost:${CONFIG.DLT_CONNECTOR_PORT}`) - }) + KeyPairCacheManager.getInstance().setHomeCommunityTopicId(homeCommunity.hieroTopicId) + logger.info(`home community topic: ${homeCommunity.hieroTopicId}`) + logger.info(`gradido node server: ${CONFIG.NODE_SERVER_URL}`) + logger.info(`gradido backend server: ${CONFIG.BACKEND_SERVER_URL}`) + return parse(communitySchema, homeCommunity) } main().catch((e) => { diff --git a/dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts index 62ce06ecf..4296c1fcf 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts @@ -1,5 +1,5 @@ import { KeyPairEd25519 } from 'gradido-blockchain-js' - +import { GradidoBlockchainCryptoError } from '../../errors' import { AbstractKeyPairRole } from './AbstractKeyPair.role' export class AccountKeyPairRole extends AbstractKeyPairRole { @@ -11,6 +11,12 @@ export class AccountKeyPairRole extends AbstractKeyPairRole { } public generateKeyPair(): KeyPairEd25519 { - return this.userKeyPair.deriveChild(this.accountNr) + const keyPair = this.userKeyPair.deriveChild(this.accountNr) + if (!keyPair) { + throw new GradidoBlockchainCryptoError( + `KeyPairEd25519 child derivation failed, has private key: ${this.userKeyPair.hasPrivateKey()}, accountNr: ${this.accountNr}`, + ) + } + return keyPair } } diff --git a/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts index 0d73d891d..8911b8851 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts @@ -1,24 +1,19 @@ import { KeyPairEd25519 } from 'gradido-blockchain-js' - -import { getTransaction } from '../../client/GradidoNode/api' +import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient' import { GradidoNodeInvalidTransactionError, GradidoNodeMissingTransactionError, } from '../../errors' -import { HieroId } from '../../schemas/typeGuard.schema' import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role' export class ForeignCommunityKeyPairRole extends AbstractRemoteKeyPairRole { - public constructor(communityTopicId: HieroId) { - super(communityTopicId) - } - public async retrieveKeyPair(): Promise { const transactionIdentifier = { - transactionNr: 1, + transactionId: 1, topic: this.topic, } - const firstTransaction = await getTransaction(transactionIdentifier) + const firstTransaction = + await GradidoNodeClient.getInstance().getTransaction(transactionIdentifier) if (!firstTransaction) { throw new GradidoNodeMissingTransactionError('Cannot find transaction', transactionIdentifier) } diff --git a/dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts index 81b9a016c..20d1ec2a1 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts @@ -1,4 +1,4 @@ -import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js' +import { KeyPairEd25519 } from 'gradido-blockchain-js' import { CONFIG } from '../../config' import { ParameterError } from '../../errors' @@ -6,16 +6,9 @@ import { AbstractKeyPairRole } from './AbstractKeyPair.role' export class HomeCommunityKeyPairRole extends AbstractKeyPairRole { public generateKeyPair(): KeyPairEd25519 { - // TODO: prevent this check with valibot test on config - if (!CONFIG.IOTA_HOME_COMMUNITY_SEED) { - throw new Error( - 'IOTA_HOME_COMMUNITY_SEED is missing either in config or as environment variable', - ) - } - const seed = MemoryBlock.fromHex(CONFIG.IOTA_HOME_COMMUNITY_SEED) - const keyPair = KeyPairEd25519.create(seed) + const keyPair = KeyPairEd25519.create(CONFIG.HOME_COMMUNITY_SEED) if (!keyPair) { - throw new ParameterError("couldn't create keyPair from IOTA_HOME_COMMUNITY_SEED") + throw new ParameterError("couldn't create keyPair from HOME_COMMUNITY_SEED") } return keyPair } diff --git a/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts index d1658d608..524c3dc1a 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts @@ -1,6 +1,5 @@ import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js' - -import { findUserByNameHash } from '../../client/GradidoNode/api' +import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient' import { Uuidv4Hash } from '../../data/Uuidv4Hash' import { GradidoNodeMissingUserError, ParameterError } from '../../errors' import { IdentifierAccount } from '../../schemas/account.schema' @@ -16,7 +15,7 @@ export class RemoteAccountKeyPairRole extends AbstractRemoteKeyPairRole { throw new ParameterError('missing account') } - const accountPublicKey = await findUserByNameHash( + const accountPublicKey = await GradidoNodeClient.getInstance().findUserByNameHash( new Uuidv4Hash(this.identifier.account.userUuid), this.topic, ) diff --git a/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts index a2f57898c..01120eceb 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts @@ -1,5 +1,5 @@ import { KeyPairEd25519 } from 'gradido-blockchain-js' - +import { GradidoBlockchainCryptoError } from '../../errors' import { hardenDerivationIndex } from '../../utils/derivationHelper' import { AbstractKeyPairRole } from './AbstractKeyPair.role' @@ -21,9 +21,14 @@ export class UserKeyPairRole extends AbstractKeyPairRole { parts[i] = hardenDerivationIndex(wholeHex.subarray(i * 4, (i + 1) * 4).readUInt32BE()) } // parts: [2206563009, 2629978174, 2324817329, 2405141782] - return parts.reduce( - (keyPair: KeyPairEd25519, node: number) => keyPair.deriveChild(node), - this.communityKeys, - ) + return parts.reduce((keyPair: KeyPairEd25519, node: number) => { + const localKeyPair = keyPair.deriveChild(node) + if (!localKeyPair) { + throw new GradidoBlockchainCryptoError( + `KeyPairEd25519 child derivation failed, has private key: ${keyPair.hasPrivateKey()} for user: ${this.userUuid}`, + ) + } + return localKeyPair + }, this.communityKeys) } } diff --git a/dlt-connector/src/interactions/sendToIota/AbstractTransaction.role.ts b/dlt-connector/src/interactions/sendToHiero/AbstractTransaction.role.ts similarity index 100% rename from dlt-connector/src/interactions/sendToIota/AbstractTransaction.role.ts rename to dlt-connector/src/interactions/sendToHiero/AbstractTransaction.role.ts diff --git a/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts b/dlt-connector/src/interactions/sendToHiero/CommunityRootTransaction.role.ts similarity index 68% rename from dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts rename to dlt-connector/src/interactions/sendToHiero/CommunityRootTransaction.role.ts index c59939ccb..34299683a 100644 --- a/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToHiero/CommunityRootTransaction.role.ts @@ -1,6 +1,7 @@ import { GradidoTransactionBuilder } from 'gradido-blockchain-js' -import { Community } from '../../client/backend/community.schema' import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic' +import { GradidoBlockchainCryptoError } from '../../errors' +import { Community } from '../../schemas/transaction.schema' import { HieroId } from '../../schemas/typeGuard.schema' import { AUF_ACCOUNT_DERIVATION_INDEX, @@ -16,7 +17,7 @@ export class CommunityRootTransactionRole extends AbstractTransactionRole { } getSenderCommunityTopicId(): HieroId { - return this.community.topicId + return this.community.hieroTopicId } getRecipientCommunityTopicId(): HieroId { @@ -26,16 +27,26 @@ export class CommunityRootTransactionRole extends AbstractTransactionRole { public async getGradidoTransactionBuilder(): Promise { const builder = new GradidoTransactionBuilder() const communityKeyPair = await KeyPairCalculation( - new KeyPairIdentifierLogic({ communityTopicId: this.community.topicId }), + new KeyPairIdentifierLogic({ communityTopicId: this.community.hieroTopicId }), ) const gmwKeyPair = communityKeyPair.deriveChild( hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX), - ) // as unknown as KeyPairEd25519 + ) + if (!gmwKeyPair) { + throw new GradidoBlockchainCryptoError( + `KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${this.community.uuid}`, + ) + } const aufKeyPair = communityKeyPair.deriveChild( hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX), - ) // as unknown as KeyPairEd25519 + ) + if (!aufKeyPair) { + throw new GradidoBlockchainCryptoError( + `KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${this.community.uuid}`, + ) + } builder - .setCreatedAt(this.community.createdAt) + .setCreatedAt(this.community.creationDate) .setCommunityRoot( communityKeyPair.getPublicKey(), gmwKeyPair.getPublicKey(), diff --git a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts b/dlt-connector/src/interactions/sendToHiero/CreationTransaction.role.ts similarity index 100% rename from dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts rename to dlt-connector/src/interactions/sendToHiero/CreationTransaction.role.ts diff --git a/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts b/dlt-connector/src/interactions/sendToHiero/DeferredTransferTransaction.role.ts similarity index 100% rename from dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts rename to dlt-connector/src/interactions/sendToHiero/DeferredTransferTransaction.role.ts diff --git a/dlt-connector/src/interactions/sendToIota/RedeemDeferredTransferTransaction.role.ts b/dlt-connector/src/interactions/sendToHiero/RedeemDeferredTransferTransaction.role.ts similarity index 95% rename from dlt-connector/src/interactions/sendToIota/RedeemDeferredTransferTransaction.role.ts rename to dlt-connector/src/interactions/sendToHiero/RedeemDeferredTransferTransaction.role.ts index 3d2574681..a91990360 100644 --- a/dlt-connector/src/interactions/sendToIota/RedeemDeferredTransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToHiero/RedeemDeferredTransferTransaction.role.ts @@ -1,6 +1,6 @@ import { GradidoTransactionBuilder, GradidoTransfer, TransferAmount } from 'gradido-blockchain-js' import { parse } from 'valibot' -import { getTransactionsForAccount } from '../../client/GradidoNode/api' +import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient' import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic' import { RedeemDeferredTransferTransaction, @@ -42,7 +42,7 @@ export class RedeemDeferredTransferTransactionRole extends AbstractTransactionRo throw new Error("redeem deferred transfer: couldn't calculate sender public key") } // load deferred transfer transaction from gradido node - const transactions = await getTransactionsForAccount( + const transactions = await GradidoNodeClient.getInstance().getTransactionsForAccount( { maxResultCount: 2, topic: this.getSenderCommunityTopicId() }, senderPublicKey.convertToHex(), ) diff --git a/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts b/dlt-connector/src/interactions/sendToHiero/RegisterAddressTransaction.role.ts similarity index 94% rename from dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts rename to dlt-connector/src/interactions/sendToHiero/RegisterAddressTransaction.role.ts index 5eee0f164..64076b920 100644 --- a/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToHiero/RegisterAddressTransaction.role.ts @@ -1,4 +1,4 @@ -import { GradidoTransactionBuilder } from 'gradido-blockchain-js' +import { AddressType, GradidoTransactionBuilder } from 'gradido-blockchain-js' import { parse } from 'valibot' import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic' import { Uuidv4Hash } from '../../data/Uuidv4Hash' @@ -53,7 +53,7 @@ export class RegisterAddressTransactionRole extends AbstractTransactionRole { .setCreatedAt(this.registerAddressTransaction.createdAt) .setRegisterAddress( userKeyPair.getPublicKey(), - this.registerAddressTransaction.accountType, + this.registerAddressTransaction.accountType as AddressType, new Uuidv4Hash(this.account.userUuid).getAsMemoryBlock(), accountKeyPair.getPublicKey(), ) diff --git a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts b/dlt-connector/src/interactions/sendToHiero/SendToHiero.context.ts similarity index 83% rename from dlt-connector/src/interactions/sendToIota/SendToIota.context.ts rename to dlt-connector/src/interactions/sendToHiero/SendToHiero.context.ts index 4b8b32c34..c363ad263 100644 --- a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts +++ b/dlt-connector/src/interactions/sendToHiero/SendToHiero.context.ts @@ -1,16 +1,25 @@ import { GradidoTransaction, - InteractionValidate, - MemoryBlock, + InteractionValidate, + MemoryBlock, ValidateType_SINGLE, } from 'gradido-blockchain-js' import { getLogger } from 'log4js' -import { safeParse, parse } from 'valibot' -import { Community, communitySchema } from '../../client/backend/community.schema' +import { parse, safeParse } from 'valibot' import { HieroClient } from '../../client/hiero/HieroClient' import { LOG4JS_BASE_CATEGORY } from '../../config/const' -import { Transaction, transactionSchema } from '../../schemas/transaction.schema' -import { HieroId, HieroTransactionId, hieroTransactionIdSchema } from '../../schemas/typeGuard.schema' +import { InputTransactionType } from '../../enum/InputTransactionType' +import { + Community, + communitySchema, + Transaction, + transactionSchema, +} from '../../schemas/transaction.schema' +import { + HieroId, + HieroTransactionId, + hieroTransactionIdSchema, +} from '../../schemas/typeGuard.schema' import { AbstractTransactionRole } from './AbstractTransaction.role' import { CommunityRootTransactionRole } from './CommunityRootTransaction.role' import { CreationTransactionRole } from './CreationTransaction.role' @@ -18,16 +27,15 @@ import { DeferredTransferTransactionRole } from './DeferredTransferTransaction.r import { RedeemDeferredTransferTransactionRole } from './RedeemDeferredTransferTransaction.role' import { RegisterAddressTransactionRole } from './RegisterAddressTransaction.role' import { TransferTransactionRole } from './TransferTransaction.role' -import { InputTransactionType } from '../../enum/InputTransactionType' -const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.interactions.sendToIota.SendToIotaContext`) +const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.interactions.sendToHiero.SendToHieroContext`) /** * @DCI-Context - * Context for sending transaction to iota - * send every transaction only once to iota! + * Context for sending transaction to hiero + * send every transaction only once to hiero! */ -export async function SendToIotaContext( +export async function SendToHieroContext( input: Transaction | Community, ): Promise { // let gradido blockchain validator run, it will throw an exception when something is wrong @@ -44,7 +52,7 @@ export async function SendToIotaContext( const client = HieroClient.getInstance() const resultMessage = await client.sendMessage(topic, gradidoTransaction) const transactionId = resultMessage.response.transactionId.toString() - logger.info('transmitted Gradido Transaction to Iota', { transactionId }) + logger.info('transmitted Gradido Transaction to Hiero', { transactionId }) return transactionId } @@ -75,7 +83,7 @@ export async function SendToIotaContext( } } - const role = chooseCorrectRole(input) + const role = chooseCorrectRole(input) const builder = await role.getGradidoTransactionBuilder() if (builder.isCrossCommunityTransaction()) { const outboundTransaction = builder.buildOutbound() @@ -92,10 +100,7 @@ export async function SendToIotaContext( } else { const transaction = builder.build() validate(transaction) - const iotaMessageId = await sendViaHiero( - transaction, - role.getSenderCommunityTopicId(), - ) + const iotaMessageId = await sendViaHiero(transaction, role.getSenderCommunityTopicId()) return parse(hieroTransactionIdSchema, iotaMessageId) } } diff --git a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts b/dlt-connector/src/interactions/sendToHiero/TransferTransaction.role.ts similarity index 90% rename from dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts rename to dlt-connector/src/interactions/sendToHiero/TransferTransaction.role.ts index f244bf472..473bfbc1e 100644 --- a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToHiero/TransferTransaction.role.ts @@ -7,13 +7,13 @@ import { import { parse } from 'valibot' import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic' import { + Transaction, TransferTransaction, transferTransactionSchema, - Transaction, } from '../../schemas/transaction.schema' +import { HieroId } from '../../schemas/typeGuard.schema' import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' import { AbstractTransactionRole } from './AbstractTransaction.role' -import { HieroId } from '../../schemas/typeGuard.schema' export class TransferTransactionRole extends AbstractTransactionRole { private transferTransaction: TransferTransaction @@ -33,7 +33,9 @@ export class TransferTransactionRole extends AbstractTransactionRole { public async getGradidoTransactionBuilder(): Promise { const builder = new GradidoTransactionBuilder() // sender + signer - const senderKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(this.transferTransaction.user)) + const senderKeyPair = await KeyPairCalculation( + new KeyPairIdentifierLogic(this.transferTransaction.user), + ) // recipient const recipientKeyPair = await KeyPairCalculation( new KeyPairIdentifierLogic(this.transferTransaction.linkedUser), @@ -56,9 +58,7 @@ export class TransferTransactionRole extends AbstractTransactionRole { const recipientCommunity = this.transferTransaction.linkedUser.communityTopicId if (senderCommunity !== recipientCommunity) { // we have a cross group transaction - builder - .setSenderCommunity(senderCommunity) - .setRecipientCommunity(recipientCommunity) + builder.setSenderCommunity(senderCommunity).setRecipientCommunity(recipientCommunity) } builder.sign(senderKeyPair) return builder diff --git a/dlt-connector/src/schemas/transaction.schema.test.ts b/dlt-connector/src/schemas/transaction.schema.test.ts index e8d8326c6..61f485db6 100644 --- a/dlt-connector/src/schemas/transaction.schema.test.ts +++ b/dlt-connector/src/schemas/transaction.schema.test.ts @@ -1,25 +1,19 @@ -import { describe, expect, it, beforeAll } from 'bun:test' +import { beforeAll, describe, expect, it } from 'bun:test' import { randomBytes } from 'crypto' import { v4 as uuidv4 } from 'uuid' import { parse } from 'valibot' import { InputTransactionType } from '../enum/InputTransactionType' import { - TransactionInput, - transactionSchema, -} from './transaction.schema' -import { transactionIdentifierSchema } from '../client/GradidoNode/input.schema' -import { - gradidoAmountSchema, - HieroId, - hieroIdSchema, - HieroTransactionId, - hieroTransactionIdSchema, - Memo, - memoSchema, - timeoutDurationSchema, - Uuidv4, - uuidv4Schema + gradidoAmountSchema, + HieroId, + hieroIdSchema, + Memo, + memoSchema, + timeoutDurationSchema, + Uuidv4, + uuidv4Schema, } from '../schemas/typeGuard.schema' +import { TransactionInput, transactionSchema } from './transaction.schema' const transactionLinkCode = (date: Date): string => { const time = date.getTime().toString(16) @@ -31,197 +25,150 @@ const transactionLinkCode = (date: Date): string => { } let topic: HieroId const topicString = '0.0.261' -let hieroTransactionId: HieroTransactionId beforeAll(() => { topic = parse(hieroIdSchema, topicString) - hieroTransactionId = parse(hieroTransactionIdSchema, '0.0.261-1755348116-1281621') }) describe('transaction schemas', () => { - describe('transactionIdentifierSchema ', () => { - it('valid, transaction identified by transactionNr and topic', () => { - expect( - parse(transactionIdentifierSchema, { - transactionNr: 1, - topic: topicString, - }), - ).toEqual({ - transactionNr: 1, - hieroTransactionId: undefined, - topic, - }) + let userUuid: Uuidv4 + let userUuidString: string + let memoString: string + let memo: Memo + beforeAll(() => { + userUuidString = uuidv4() + userUuid = parse(uuidv4Schema, userUuidString) + memoString = 'TestMemo' + memo = parse(memoSchema, memoString) + }) + it('valid, register new user address', () => { + const registerAddress: TransactionInput = { + user: { + communityTopicId: topicString, + account: { userUuid: userUuidString }, + }, + type: InputTransactionType.REGISTER_ADDRESS, + createdAt: '2022-01-01T00:00:00.000Z', + } + expect(parse(transactionSchema, registerAddress)).toEqual({ + user: { + communityTopicId: topic, + account: { + userUuid, + accountNr: 0, + }, + }, + type: registerAddress.type, + createdAt: new Date(registerAddress.createdAt), }) - it('valid, transaction identified by hieroTransactionId and topic', () => { - expect( - parse(transactionIdentifierSchema, { - hieroTransactionId: '0.0.261-1755348116-1281621', - topic: topicString, - }), - ).toEqual({ - hieroTransactionId, - topic - }) - }) - it('invalid, missing topic', () => { - expect(() => - parse(transactionIdentifierSchema, { - transactionNr: 1, - hieroTransactionId: '0.0.261-1755348116-1281621', - }), - ).toThrowError(new Error('Invalid key: Expected "topic" but received undefined')) - }) - it('invalid, transactionNr and iotaMessageId set', () => { - expect(() => - parse(transactionIdentifierSchema, { - transactionNr: 1, - hieroTransactionId: '0.0.261-1755348116-1281621', - topic - }), - ).toThrowError(new Error('expect transactionNr or hieroTransactionId not both')) + }) + it('valid, gradido transfer', () => { + const gradidoTransfer: TransactionInput = { + user: { + communityTopicId: topicString, + account: { userUuid: userUuidString }, + }, + linkedUser: { + communityTopicId: topicString, + account: { userUuid: userUuidString }, + }, + amount: '100', + memo: memoString, + type: InputTransactionType.GRADIDO_TRANSFER, + createdAt: '2022-01-01T00:00:00.000Z', + } + expect(parse(transactionSchema, gradidoTransfer)).toEqual({ + user: { + communityTopicId: topic, + account: { + userUuid, + accountNr: 0, + }, + }, + linkedUser: { + communityTopicId: topic, + account: { + userUuid, + accountNr: 0, + }, + }, + amount: parse(gradidoAmountSchema, gradidoTransfer.amount!), + memo, + type: gradidoTransfer.type, + createdAt: new Date(gradidoTransfer.createdAt), }) }) - describe('transactionSchema', () => { - let userUuid: Uuidv4 - let userUuidString: string - let memoString: string - let memo: Memo - beforeAll(() => { - userUuidString = uuidv4() - userUuid = parse(uuidv4Schema, userUuidString) - memoString = 'TestMemo' - memo = parse(memoSchema, memoString) + it('valid, gradido creation', () => { + const gradidoCreation: TransactionInput = { + user: { + communityTopicId: topicString, + account: { userUuid: userUuidString }, + }, + linkedUser: { + communityTopicId: topicString, + account: { userUuid: userUuidString }, + }, + amount: '1000', + memo: memoString, + type: InputTransactionType.GRADIDO_CREATION, + createdAt: '2022-01-01T00:00:00.000Z', + targetDate: '2021-11-01T10:00', + } + expect(parse(transactionSchema, gradidoCreation)).toEqual({ + user: { + communityTopicId: topic, + account: { userUuid, accountNr: 0 }, + }, + linkedUser: { + communityTopicId: topic, + account: { userUuid, accountNr: 0 }, + }, + amount: parse(gradidoAmountSchema, gradidoCreation.amount!), + memo, + type: gradidoCreation.type, + createdAt: new Date(gradidoCreation.createdAt), + targetDate: new Date(gradidoCreation.targetDate!), }) - it('valid, register new user address', () => { - const registerAddress: TransactionInput = { - user: { - communityTopicId: topicString, - account: { userUuid: userUuidString }, + }) + it('valid, gradido transaction link / deferred transfer', () => { + const gradidoTransactionLink: TransactionInput = { + user: { + communityTopicId: topicString, + account: { + userUuid: userUuidString, }, - type: InputTransactionType.REGISTER_ADDRESS, - createdAt: '2022-01-01T00:00:00.000Z', - } - expect(parse(transactionSchema, registerAddress)).toEqual({ - user: { - communityTopicId: topic, - account: { - userUuid, - accountNr: 0, - }, + }, + linkedUser: { + communityTopicId: topicString, + seed: { + seed: transactionLinkCode(new Date()), }, - type: registerAddress.type, - createdAt: new Date(registerAddress.createdAt), - }) - }) - it('valid, gradido transfer', () => { - const gradidoTransfer: TransactionInput = { - user: { - communityTopicId: topicString, - account: { userUuid: userUuidString }, + }, + amount: '100', + memo: memoString, + type: InputTransactionType.GRADIDO_DEFERRED_TRANSFER, + createdAt: '2022-01-01T00:00:00.000Z', + timeoutDuration: 60 * 60 * 24 * 30, + } + expect(parse(transactionSchema, gradidoTransactionLink)).toEqual({ + user: { + communityTopicId: topic, + account: { + userUuid, + accountNr: 0, }, - linkedUser: { - communityTopicId: topicString, - account: { userUuid: userUuidString }, + }, + linkedUser: { + communityTopicId: topic, + seed: { + seed: gradidoTransactionLink.linkedUser!.seed!.seed, }, - amount: '100', - memo: memoString, - type: InputTransactionType.GRADIDO_TRANSFER, - createdAt: '2022-01-01T00:00:00.000Z', - } - expect(parse(transactionSchema, gradidoTransfer)).toEqual({ - user: { - communityTopicId: topic, - account: { - userUuid, - accountNr: 0, - }, - }, - linkedUser: { - communityTopicId: topic, - account: { - userUuid, - accountNr: 0, - }, - }, - amount: parse(gradidoAmountSchema, gradidoTransfer.amount!), - memo, - type: gradidoTransfer.type, - createdAt: new Date(gradidoTransfer.createdAt), - }) - }) - - it('valid, gradido creation', () => { - const gradidoCreation: TransactionInput = { - user: { - communityTopicId: topicString, - account: { userUuid: userUuidString }, - }, - linkedUser: { - communityTopicId: topicString, - account: { userUuid: userUuidString }, - }, - amount: '1000', - memo: memoString, - type: InputTransactionType.GRADIDO_CREATION, - createdAt: '2022-01-01T00:00:00.000Z', - targetDate: '2021-11-01T10:00', - } - expect(parse(transactionSchema, gradidoCreation)).toEqual({ - user: { - communityTopicId: topic, - account: { userUuid, accountNr: 0 }, - }, - linkedUser: { - communityTopicId: topic, - account: { userUuid, accountNr: 0 }, - }, - amount: parse(gradidoAmountSchema, gradidoCreation.amount!), - memo, - type: gradidoCreation.type, - createdAt: new Date(gradidoCreation.createdAt), - targetDate: new Date(gradidoCreation.targetDate!), - }) - }) - it('valid, gradido transaction link / deferred transfer', () => { - const gradidoTransactionLink: TransactionInput = { - user: { - communityTopicId: topicString, - account: { - userUuid: userUuidString, - }, - }, - linkedUser: { - communityTopicId: topicString, - seed: { - seed: transactionLinkCode(new Date()), - }, - }, - amount: '100', - memo: memoString, - type: InputTransactionType.GRADIDO_DEFERRED_TRANSFER, - createdAt: '2022-01-01T00:00:00.000Z', - timeoutDuration: 60 * 60 * 24 * 30, - } - expect(parse(transactionSchema, gradidoTransactionLink)).toEqual({ - user: { - communityTopicId: topic, - account: { - userUuid, - accountNr: 0, - }, - }, - linkedUser: { - communityTopicId: topic, - seed: { - seed: gradidoTransactionLink.linkedUser!.seed!.seed, - }, - }, - amount: parse(gradidoAmountSchema, gradidoTransactionLink.amount!), - memo, - type: gradidoTransactionLink.type, - createdAt: new Date(gradidoTransactionLink.createdAt), - timeoutDuration: parse(timeoutDurationSchema, gradidoTransactionLink.timeoutDuration!), - }) + }, + amount: parse(gradidoAmountSchema, gradidoTransactionLink.amount!), + memo, + type: gradidoTransactionLink.type, + createdAt: new Date(gradidoTransactionLink.createdAt), + timeoutDuration: parse(timeoutDurationSchema, gradidoTransactionLink.timeoutDuration!), }) }) }) diff --git a/dlt-connector/src/schemas/transaction.schema.ts b/dlt-connector/src/schemas/transaction.schema.ts index de75bda89..4345ec901 100644 --- a/dlt-connector/src/schemas/transaction.schema.ts +++ b/dlt-connector/src/schemas/transaction.schema.ts @@ -11,8 +11,22 @@ import { hieroIdSchema, memoSchema, timeoutDurationSchema, + uuidv4Schema, } from './typeGuard.schema' +/** + * Schema for community, for creating new CommunityRoot Transaction on gradido blockchain + */ +export const communitySchema = v.object({ + uuid: uuidv4Schema, + hieroTopicId: hieroIdSchema, + foreign: v.boolean('expect boolean type'), + creationDate: dateSchema, +}) + +export type CommunityInput = v.InferInput +export type Community = v.InferOutput + export const transactionSchema = v.object({ user: identifierAccountSchema, linkedUser: v.nullish(identifierAccountSchema, undefined), diff --git a/dlt-connector/src/schemas/typeConverter.schema.test.ts b/dlt-connector/src/schemas/typeConverter.schema.test.ts index 2ec02d12e..71cae3619 100644 --- a/dlt-connector/src/schemas/typeConverter.schema.test.ts +++ b/dlt-connector/src/schemas/typeConverter.schema.test.ts @@ -51,10 +51,10 @@ describe('basic.schema', () => { it('confirmedTransactionSchema', () => { const confirmedTransaction = v.parse( confirmedTransactionSchema, - 'CAcSAgoAGgYIwvK5/wUiAzMuNCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'CAcS5AEKZgpkCiCBZwMplGmI7fRR9MQkaR2Dz1qQQ5BCiC1btyJD71Ue9BJABODQ9sS70th9yHn8X3K+SNv2gsiIdX/V09baCvQCb+yEj2Dd/fzShIYqf3pooIMwJ01BkDJdNGBZs5MDzEAkChJ6ChkIAhIVRGFua2UgZnVlciBkZWluIFNlaW4hEggIgMy5/wUQABoDMy41IAAyTAooCiDbDtYSWhTwMKvtG/yDHgohjPn6v87n7NWBwMDniPAXxxCUmD0aABIgJE0o18xb6P6PsNjh0bkN52AzhggteTzoh09jV+blMq0aCAjC8rn/BRAAIgMzLjUqICiljeEjGHifWe4VNzoe+DN9oOLNZvJmv3VlkP+1RH7MMiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADomCiDbDtYSWhTwMKvtG/yDHgohjPn6v87n7NWBwMDniPAXxxDAhD06JwogJE0o18xb6P6PsNjh0bkN52AzhggteTzoh09jV+blMq0Q65SlBA==', ) expect(confirmedTransaction.getId()).toBe(7) expect(confirmedTransaction.getConfirmedAt().getSeconds()).toBe(1609464130) - expect(confirmedTransaction.getVersionNumber()).toBe('3.4') + expect(confirmedTransaction.getVersionNumber()).toBe('3.5') }) }) diff --git a/dlt-connector/src/schemas/typeConverter.schema.ts b/dlt-connector/src/schemas/typeConverter.schema.ts index 3fb6e55ba..4410b54be 100644 --- a/dlt-connector/src/schemas/typeConverter.schema.ts +++ b/dlt-connector/src/schemas/typeConverter.schema.ts @@ -1,6 +1,7 @@ -import { AddressType, ConfirmedTransaction } from 'gradido-blockchain-js' +import { ConfirmedTransaction } from 'gradido-blockchain-js' import * as v from 'valibot' import { AccountType } from '../enum/AccountType' +import { AddressType } from '../enum/AddressType' import { confirmedTransactionFromBase64, isAddressType, diff --git a/dlt-connector/src/server/index.ts b/dlt-connector/src/server/index.ts index 7bf2a85c6..c3a00d4e3 100644 --- a/dlt-connector/src/server/index.ts +++ b/dlt-connector/src/server/index.ts @@ -1,50 +1,27 @@ +import { TypeBoxFromValibot } from '@sinclair/typemap' import { Elysia, status } from 'elysia' import { AddressType_NONE } from 'gradido-blockchain-js' import { getLogger } from 'log4js' import { parse } from 'valibot' -import { getAddressType } from '../client/GradidoNode/api' +import { GradidoNodeClient } from '../client/GradidoNode/GradidoNodeClient' import { LOG4JS_BASE_CATEGORY } from '../config/const' import { KeyPairIdentifierLogic } from '../data/KeyPairIdentifier.logic' import { KeyPairCalculation } from '../interactions/keyPairCalculation/KeyPairCalculation.context' -import { SendToIotaContext } from '../interactions/sendToIota/SendToIota.context' +import { SendToHieroContext } from '../interactions/sendToHiero/SendToHiero.context' import { IdentifierAccount, identifierAccountSchema } from '../schemas/account.schema' +import { transactionSchema } from '../schemas/transaction.schema' import { hieroTransactionIdSchema } from '../schemas/typeGuard.schema' import { accountIdentifierSeedSchema, accountIdentifierUserSchema, existSchema, } from './input.schema' -import { TypeBoxFromValibot } from '@sinclair/typemap' -import { transactionSchema } from '../schemas/transaction.schema' const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.server`) -async function isAccountExist(identifierAccount: IdentifierAccount): Promise { - const startTime = Date.now() - const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(identifierAccount)) - const publicKey = accountKeyPair.getPublicKey() - if (!publicKey) { - throw status(404, "couldn't calculate account key pair") - } - - // ask gradido node server for account type, if type !== NONE account exist - const addressType = await getAddressType( - publicKey.convertToHex(), - identifierAccount.communityTopicId, - ) - const endTime = Date.now() - logger.info( - `isAccountExist: ${addressType !== AddressType_NONE}, time used: ${endTime - startTime}ms`, - ) - if (logger.isDebugEnabled()) { - logger.debug('params', identifierAccount) - } - return addressType !== AddressType_NONE -} - export const appRoutes = new Elysia() .get( - '/isAccountExist/:communityTopicId/:userUuid/:accountNr', + '/isAccountExist/by-user/:communityTopicId/:userUuid/:accountNr', async ({ params: { communityTopicId, userUuid, accountNr } }) => { const accountIdentifier = parse(identifierAccountSchema, { communityTopicId, @@ -56,7 +33,7 @@ export const appRoutes = new Elysia() { params: accountIdentifierUserSchema, response: existSchema }, ) .get( - '/isAccountExist/:communityTopicId/:seed', + '/isAccountExist/by-seed/:communityTopicId/:seed', async ({ params: { communityTopicId, seed } }) => { const accountIdentifier = parse(identifierAccountSchema, { communityTopicId, @@ -69,7 +46,33 @@ export const appRoutes = new Elysia() ) .post( '/sendTransaction', - async ({ body }) => await SendToIotaContext(parse(transactionSchema, body)), + async ({ body }) => await SendToHieroContext(parse(transactionSchema, body)), // validation schemas - { body: TypeBoxFromValibot(transactionSchema), response: TypeBoxFromValibot(hieroTransactionIdSchema) }, + { + body: TypeBoxFromValibot(transactionSchema), + response: TypeBoxFromValibot(hieroTransactionIdSchema), + }, ) + +async function isAccountExist(identifierAccount: IdentifierAccount): Promise { + const startTime = Date.now() + const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(identifierAccount)) + const publicKey = accountKeyPair.getPublicKey() + if (!publicKey) { + throw status(404, "couldn't calculate account key pair") + } + + // ask gradido node server for account type, if type !== NONE account exist + const addressType = await GradidoNodeClient.getInstance().getAddressType( + publicKey.convertToHex(), + identifierAccount.communityTopicId, + ) + const endTime = Date.now() + logger.info( + `isAccountExist: ${addressType !== AddressType_NONE}, time used: ${endTime - startTime}ms`, + ) + if (logger.isDebugEnabled()) { + logger.debug('params', identifierAccount) + } + return addressType !== AddressType_NONE +} diff --git a/dlt-connector/src/utils/typeConverter.ts b/dlt-connector/src/utils/typeConverter.ts index 6ab72b069..5b1279beb 100644 --- a/dlt-connector/src/utils/typeConverter.ts +++ b/dlt-connector/src/utils/typeConverter.ts @@ -1,19 +1,11 @@ import { - AddressType, - AddressType_COMMUNITY_AUF, - AddressType_COMMUNITY_GMW, - AddressType_COMMUNITY_HUMAN, - AddressType_COMMUNITY_PROJECT, - AddressType_CRYPTO_ACCOUNT, - AddressType_DEFERRED_TRANSFER, - AddressType_NONE, - AddressType_SUBACCOUNT, ConfirmedTransaction, DeserializeType_CONFIRMED_TRANSACTION, InteractionDeserialize, MemoryBlock, } from 'gradido-blockchain-js' import { AccountType } from '../enum/AccountType' +import { AddressType } from '../enum/AddressType' export const confirmedTransactionFromBase64 = (base64: string): ConfirmedTransaction => { const confirmedTransactionBinaryPtr = MemoryBlock.createPtr(MemoryBlock.fromBase64(base64)) @@ -34,14 +26,14 @@ export const confirmedTransactionFromBase64 = (base64: string): ConfirmedTransac * AccountType is the enum defined in TypeScript but with the same options */ const accountToAddressMap: Record = { - [AccountType.COMMUNITY_AUF]: AddressType_COMMUNITY_AUF, - [AccountType.COMMUNITY_GMW]: AddressType_COMMUNITY_GMW, - [AccountType.COMMUNITY_HUMAN]: AddressType_COMMUNITY_HUMAN, - [AccountType.COMMUNITY_PROJECT]: AddressType_COMMUNITY_PROJECT, - [AccountType.CRYPTO_ACCOUNT]: AddressType_CRYPTO_ACCOUNT, - [AccountType.SUBACCOUNT]: AddressType_SUBACCOUNT, - [AccountType.DEFERRED_TRANSFER]: AddressType_DEFERRED_TRANSFER, - [AccountType.NONE]: AddressType_NONE, + [AccountType.COMMUNITY_AUF]: AddressType.COMMUNITY_AUF, + [AccountType.COMMUNITY_GMW]: AddressType.COMMUNITY_GMW, + [AccountType.COMMUNITY_HUMAN]: AddressType.COMMUNITY_HUMAN, + [AccountType.COMMUNITY_PROJECT]: AddressType.COMMUNITY_PROJECT, + [AccountType.CRYPTO_ACCOUNT]: AddressType.CRYPTO_ACCOUNT, + [AccountType.SUBACCOUNT]: AddressType.SUBACCOUNT, + [AccountType.DEFERRED_TRANSFER]: AddressType.DEFERRED_TRANSFER, + [AccountType.NONE]: AddressType.NONE, } const addressToAccountMap: Record = Object.entries( @@ -66,7 +58,7 @@ export function toAddressType(input: AccountType | AddressType): AddressType { if (isAddressType(input)) { return input } - return accountToAddressMap[input as AccountType] ?? AddressType_NONE + return accountToAddressMap[input as AccountType] ?? AddressType.NONE } export function toAccountType(input: AccountType | AddressType): AccountType {