change for hiero

This commit is contained in:
einhornimmond 2025-09-06 12:53:51 +02:00
parent c06f82bf29
commit 18879cf207
45 changed files with 865 additions and 769 deletions

View File

@ -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`)

View File

@ -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<T extends ObjectLiteral> {
protected self: T | null
@ -14,6 +14,9 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
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<T extends ObjectLiteral> {
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}`,
)
}

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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<Transaction | User | TransactionLink>
> {
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

View File

@ -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<void> {
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()

View File

@ -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]

View File

@ -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=="],

View File

@ -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,

View File

@ -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<T> extends Error {
private response?: JsonRpcEitherResponse<T>
constructor(message: string, response?: JsonRpcEitherResponse<T>) {
super(message)
this.name = 'GradidoNodeRequestError'
this.response = response
}
getResponse(): JsonRpcEitherResponse<T> | undefined {
return this.response
}
}
type WithTimeUsed<T> = 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<ConfirmedTransaction | undefined> {
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<ConfirmedTransaction | undefined> {
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<ConfirmedTransaction[]> {
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<ConfirmedTransaction[]> {
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<AddressType> {
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<Hex32 | undefined> {
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<T, R>(
response: JsonRpcEitherResponse<T>,
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<T>(method: string, parameter: any): Promise<JsonRpcEitherResponse<T>> {
this.logger.debug('call %s with %s', method, parameter)
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
return this.client.exec<T>(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<T>(method: string, parameter: any): Promise<T> {
const response = await this.rpcCall<WithTimeUsed<T>>(method, parameter)
return this.resolveResponse(response, (result: WithTimeUsed<T>) => {
if (result.timeUsed) {
this.logger.info(`call %s, used ${result.timeUsed}`, method)
}
return result as T
})
}
}

View File

@ -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<ConfirmedTransaction[]> {
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<ConfirmedTransaction | undefined> {
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<ConfirmedTransaction | undefined> {
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<AddressType> {
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<Hex32 | undefined> {
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<ConfirmedTransaction[]> {
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,
}

View File

@ -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'))
})
})

View File

@ -14,7 +14,7 @@ export type TransactionsRangeInput = v.InferInput<typeof transactionsRangeSchema
// allow TransactionIdentifier to only contain either transactionNr or iotaMessageId
export const transactionIdentifierSchema = v.pipe(
v.object({
transactionNr: v.nullish(
transactionId: v.nullish(
v.pipe(v.number('expect number type'), v.minValue(1, 'expect number >= 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
}

View File

@ -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<T> extends Error {
private response?: JsonRpcEitherResponse<T>
constructor(message: string, response?: JsonRpcEitherResponse<T>) {
super(message)
this.name = 'GradidoNodeRequestError'
this.response = response
}
getResponse(): JsonRpcEitherResponse<T> | undefined {
return this.response
}
}
// return result on success or throw error
export function resolveResponse<T, R>(
response: JsonRpcEitherResponse<T>,
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> = T & { timeUsed?: string }
// template rpcCall, check first if port is open before executing json rpc 2.0 request
export async function rpcCall<T>(
method: string,
parameter: any,
): Promise<JsonRpcEitherResponse<T>> {
logger.debug('call %s with %s', method, parameter)
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
return client.exec<T>(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<T>(method: string, parameter: any): Promise<T> {
const response = await rpcCall<WithTimeUsed<T>>(method, parameter)
return resolveResponse(response, (result: WithTimeUsed<T>) => {
if (result.timeUsed) {
logger.info(`call %s, used ${result.timeUsed}`, method)
}
return result as T
})
}

View File

@ -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<Community> {
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<Community> {
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)
}

View File

@ -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'),
})
})
})

View File

@ -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<typeof communitySchema>
@ -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
}

View File

@ -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<TopicInfoOutput> {
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(),
})
}

View File

@ -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<typeof topicInfoSchema>

View File

@ -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
export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE = 1000 * 60 * 60 * 24 * 7

View File

@ -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<typeof configSchema>
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

View File

@ -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<string, number>((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<string, MemoryBlock>((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',
),
})

View File

@ -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
)
}

View File

@ -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)

View File

@ -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<Community> {
// 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) => {

View File

@ -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
}
}

View File

@ -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<KeyPairEd25519> {
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)
}

View File

@ -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
}

View File

@ -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,
)

View File

@ -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)
}
}

View File

@ -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<GradidoTransactionBuilder> {
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(),

View File

@ -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(),
)

View File

@ -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(),
)

View File

@ -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<HieroTransactionId> {
// 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)
}
}

View File

@ -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<GradidoTransactionBuilder> {
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

View File

@ -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!),
})
})
})

View File

@ -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<typeof communitySchema>
export type Community = v.InferOutput<typeof communitySchema>
export const transactionSchema = v.object({
user: identifierAccountSchema,
linkedUser: v.nullish(identifierAccountSchema, undefined),

View File

@ -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')
})
})

View File

@ -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,

View File

@ -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<boolean> {
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<boolean> {
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
}

View File

@ -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, AddressType> = {
[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<AddressType, AccountType> = 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 {